362 lines
14 KiB
Plaintext
362 lines
14 KiB
Plaintext
// NiloToon 캐릭터 전용 색조 보정 셰이더 (YAMO)
|
|
//
|
|
// Pass 구성:
|
|
// Pass 0 "Copy" : 스텐실 없음 — 카메라 컬러를 임시 RT로 단순 복사
|
|
// Pass 1 "ToneAdjust" : Stencil Equal(128) — 캐릭터 픽셀에만 색조 보정
|
|
// Pass 2 "ToneAdjust_Full" : 스텐실 없음 — 전체 화면에 색조 보정 (디버그용)
|
|
// Pass 3 "StencilView" : Stencil Equal(128) — 마스크 시각화 (디버그용)
|
|
// Pass 4 "DebugStencilFill" : 전체 화면 stencil=128 기록 (진단용)
|
|
// Pass 5 "MeshStencilFill" : 메시 기반 stencil=128 기록 (ColorMask 0)
|
|
// Pass 6 "MeshMaskFill" : 메시 → 마스크=1 + stencil=128 (Auto Match용)
|
|
// Pass 7 "MaskedDownsample" : 마스크 기반 가중 다운샘플 (첫 단계)
|
|
// Pass 8 "WeightedDownsample": 알파 기반 가중 다운샘플 (후속 단계)
|
|
|
|
Shader "YAMO/NiloToonCharToneAdjust"
|
|
{
|
|
Properties { }
|
|
|
|
SubShader
|
|
{
|
|
Tags
|
|
{
|
|
"RenderType" = "Opaque"
|
|
"RenderPipeline" = "UniversalPipeline"
|
|
}
|
|
|
|
HLSLINCLUDE
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
|
|
// _MainTex는 Properties에 선언하지 않습니다.
|
|
// Properties에 선언하면 Material 기본값이 Global 값보다 우선합니다.
|
|
TEXTURE2D(_MainTex);
|
|
SAMPLER(sampler_MainTex);
|
|
|
|
float4 _CharToneAdjust_Lift;
|
|
float4 _CharToneAdjust_Gamma;
|
|
float4 _CharToneAdjust_Gain;
|
|
float4 _CharToneAdjust_Shadows;
|
|
float4 _CharToneAdjust_Midtones;
|
|
float4 _CharToneAdjust_Highlights;
|
|
float4 _CharToneAdjust_SMHRange;
|
|
float _CharToneAdjust_Saturation;
|
|
float _CharToneAdjust_PostExposure;
|
|
float _CharToneAdjust_BlendAmount;
|
|
|
|
#define LUMA_WEIGHTS float3(0.2126729, 0.7151522, 0.0721750)
|
|
|
|
struct Varyings
|
|
{
|
|
float4 positionCS : SV_POSITION;
|
|
float2 uv : TEXCOORD0;
|
|
};
|
|
|
|
Varyings Vert(uint vertexID : SV_VertexID)
|
|
{
|
|
Varyings output;
|
|
float2 uv = float2((vertexID << 1u) & 2u, vertexID & 2u);
|
|
output.positionCS = float4(uv * 2.0 - 1.0, 0.0, 1.0);
|
|
#if UNITY_UV_STARTS_AT_TOP
|
|
uv.y = 1.0 - uv.y;
|
|
#endif
|
|
output.uv = uv;
|
|
return output;
|
|
}
|
|
|
|
float3 ApplyLiftGammaGain(float3 color, float3 lift, float3 gamma, float3 gain)
|
|
{
|
|
color = color * gain + lift;
|
|
gamma = max(gamma, 1e-4);
|
|
color = sign(color) * pow(abs(color) + 1e-5, 1.0 / gamma);
|
|
return max(color, 0.0);
|
|
}
|
|
|
|
float3 ApplySMH(float3 color,
|
|
float3 shadows, float3 midtones, float3 highlights,
|
|
float shadowStart, float shadowEnd,
|
|
float highlightStart, float highlightEnd)
|
|
{
|
|
float lum = dot(color, LUMA_WEIGHTS);
|
|
float shadowW = 1.0 - smoothstep(shadowStart, shadowEnd, lum);
|
|
float highlightW = smoothstep(highlightStart, highlightEnd, lum);
|
|
float midtoneW = saturate(1.0 - shadowW - highlightW);
|
|
color += shadows * shadowW;
|
|
color += midtones * midtoneW;
|
|
color += highlights * highlightW;
|
|
return max(color, 0.0);
|
|
}
|
|
|
|
float3 ApplySaturation(float3 color, float sat)
|
|
{
|
|
float lum = dot(color, LUMA_WEIGHTS);
|
|
return max(lerp(lum.xxx, color, sat), 0.0);
|
|
}
|
|
|
|
half4 ToneAdjustFrag(Varyings input) : SV_Target
|
|
{
|
|
half4 original = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
|
|
float3 color = original.rgb;
|
|
color *= _CharToneAdjust_PostExposure;
|
|
color = ApplyLiftGammaGain(color,
|
|
_CharToneAdjust_Lift.rgb, _CharToneAdjust_Gamma.rgb, _CharToneAdjust_Gain.rgb);
|
|
color = ApplySMH(color,
|
|
_CharToneAdjust_Shadows.rgb, _CharToneAdjust_Midtones.rgb, _CharToneAdjust_Highlights.rgb,
|
|
_CharToneAdjust_SMHRange.x, _CharToneAdjust_SMHRange.y,
|
|
_CharToneAdjust_SMHRange.z, _CharToneAdjust_SMHRange.w);
|
|
color = ApplySaturation(color, _CharToneAdjust_Saturation);
|
|
color = lerp(original.rgb, color, _CharToneAdjust_BlendAmount);
|
|
return half4(color, original.a);
|
|
}
|
|
ENDHLSL
|
|
|
|
// ── Pass 0: Copy ──────────────────────────────────────────────
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_Copy"
|
|
ZTest Always ZWrite Off Cull Off
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment Frag
|
|
half4 Frag(Varyings input) : SV_Target
|
|
{
|
|
return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
|
|
}
|
|
ENDHLSL
|
|
}
|
|
|
|
// ── Pass 1: ToneAdjust (Stencil Equal 128) ───────────────────
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_ToneAdjust"
|
|
ZTest Always ZWrite Off Cull Off
|
|
Stencil
|
|
{
|
|
Ref 128 ReadMask 128 WriteMask 0
|
|
Comp Equal Pass Keep Fail Keep
|
|
}
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment ToneAdjustFrag
|
|
ENDHLSL
|
|
}
|
|
|
|
// ── Pass 2: ToneAdjust_Full (스텐실 없음, 전체 화면) ─────────
|
|
// 디버그 및 스텐실 마킹 우회용.
|
|
// Feature의 FullScreen 모드에서 사용됩니다.
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_ToneAdjustFull"
|
|
ZTest Always ZWrite Off Cull Off
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment ToneAdjustFrag
|
|
ENDHLSL
|
|
}
|
|
|
|
// ── Pass 3: StencilView (스텐실 마스크 시각화) ───────────────
|
|
// 스텐실 재마킹이 정상 동작하는지 확인하는 디버그용 패스.
|
|
// stencil=128 인 픽셀을 마젠타 오버레이로 표시합니다.
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_StencilView"
|
|
ZTest Always ZWrite Off Cull Off
|
|
Stencil
|
|
{
|
|
Ref 128 ReadMask 128 WriteMask 0
|
|
Comp Equal Pass Keep Fail Keep
|
|
}
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment StencilViewFrag
|
|
|
|
half4 StencilViewFrag(Varyings input) : SV_Target
|
|
{
|
|
half4 c = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
|
|
return half4(lerp(c.rgb, half3(1.0, 0.0, 0.3), 0.6), 1.0);
|
|
}
|
|
ENDHLSL
|
|
}
|
|
// ── Pass 4: DebugStencilFill (전체 화면 stencil=128 기록) ────
|
|
// 진단용 풀스크린 패스. DrawProcedural로 전체 화면에 stencil=128 강제 기록.
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_DebugStencilFill"
|
|
ZTest Always ZWrite Off Cull Off ColorMask 0
|
|
Stencil
|
|
{
|
|
Ref 128 WriteMask 128
|
|
Comp Always Pass Replace
|
|
}
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment FragEmpty
|
|
half4 FragEmpty(Varyings input) : SV_Target { return 0; }
|
|
ENDHLSL
|
|
}
|
|
|
|
// ── Pass 5: MeshStencilFill (메시 기반 stencil=128 기록) ─────
|
|
// DrawRendererList + overrideMaterial로 캐릭터 메시 픽셀에 stencil=128 기록.
|
|
// NiloToon 자체 스텐실 패스 대신 이 패스를 사용합니다.
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_MeshStencilFill"
|
|
ZTest Always ZWrite Off Cull Off ColorMask 0
|
|
Stencil
|
|
{
|
|
Ref 128 WriteMask 128
|
|
Comp Always Pass Replace
|
|
}
|
|
HLSLPROGRAM
|
|
#pragma vertex MeshVert
|
|
#pragma fragment MeshFrag
|
|
|
|
struct MeshAttributes
|
|
{
|
|
float4 positionOS : POSITION;
|
|
};
|
|
struct MeshVaryings
|
|
{
|
|
float4 positionCS : SV_POSITION;
|
|
};
|
|
|
|
MeshVaryings MeshVert(MeshAttributes input)
|
|
{
|
|
MeshVaryings o;
|
|
o.positionCS = TransformObjectToHClip(input.positionOS.xyz);
|
|
return o;
|
|
}
|
|
half4 MeshFrag(MeshVaryings input) : SV_Target { return 0; }
|
|
ENDHLSL
|
|
}
|
|
// ── Pass 6: MeshMaskFill (메시 → 마스크 + 스텐실) ──────────
|
|
// DrawRendererList + overrideMaterial로 캐릭터 메시 픽셀에
|
|
// color=1.0(마스크) + stencil=128 동시 기록. Auto Match 분석용.
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_MeshMaskFill"
|
|
ZTest Always ZWrite Off Cull Off
|
|
Stencil
|
|
{
|
|
Ref 128 WriteMask 128
|
|
Comp Always Pass Replace
|
|
}
|
|
HLSLPROGRAM
|
|
#pragma vertex MeshVert2
|
|
#pragma fragment MaskFrag
|
|
|
|
struct MeshAttr2 { float4 positionOS : POSITION; };
|
|
struct MeshVary2 { float4 positionCS : SV_POSITION; };
|
|
|
|
MeshVary2 MeshVert2(MeshAttr2 input)
|
|
{
|
|
MeshVary2 o;
|
|
o.positionCS = TransformObjectToHClip(input.positionOS.xyz);
|
|
return o;
|
|
}
|
|
half4 MaskFrag(MeshVary2 input) : SV_Target { return 1; }
|
|
ENDHLSL
|
|
}
|
|
|
|
// ── Pass 7: MaskedDownsample (마스크 기반 가중 다운샘플) ─────
|
|
// _MainTex(카메라 컬러) + _MaskTex(캐릭터 마스크)를 읽고
|
|
// 마스크 조건에 맞는 픽셀만 4x4 블록 평균. 첫 단계 축소용.
|
|
// _InvertMask: 0 = 캐릭터(mask>0.5), 1 = 배경(mask<0.5)
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_MaskedDownsample"
|
|
ZTest Always ZWrite Off Cull Off
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment MaskedDownsampleFrag
|
|
|
|
TEXTURE2D(_MaskTex);
|
|
SAMPLER(sampler_MaskTex);
|
|
float _InvertMask;
|
|
float4 _MainTex_TexelSize;
|
|
|
|
half4 MaskedDownsampleFrag(Varyings input) : SV_Target
|
|
{
|
|
float3 colorSum = 0;
|
|
float validCount = 0;
|
|
float2 texelSize = _MainTex_TexelSize.xy;
|
|
|
|
for (int y = 0; y < 4; y++)
|
|
{
|
|
for (int x = 0; x < 4; x++)
|
|
{
|
|
float2 offset = (float2(x, y) - 1.5) * texelSize;
|
|
float2 uv = input.uv + offset;
|
|
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
|
|
half mask = SAMPLE_TEXTURE2D(_MaskTex, sampler_MaskTex, uv).r;
|
|
float valid = _InvertMask > 0.5 ? step(mask, 0.499) : step(0.5, mask);
|
|
colorSum += color.rgb * valid;
|
|
validCount += valid;
|
|
}
|
|
}
|
|
|
|
if (validCount < 0.5)
|
|
return half4(0, 0, 0, 0);
|
|
|
|
return half4(colorSum / validCount, validCount / 16.0);
|
|
}
|
|
ENDHLSL
|
|
}
|
|
|
|
// ── Pass 8: WeightedDownsample (가중 다운샘플) ──────────────
|
|
// 4x4 블록의 유효 픽셀(alpha>0)만 평균하여 1/4 크기로 축소.
|
|
// alpha 채널에 유효 비율을 저장합니다.
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_WeightedDownsample"
|
|
ZTest Always ZWrite Off Cull Off
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment WeightedDownsampleFrag
|
|
|
|
float4 _MainTex_TexelSize; // (1/w, 1/h, w, h)
|
|
|
|
half4 WeightedDownsampleFrag(Varyings input) : SV_Target
|
|
{
|
|
float3 colorSum = 0;
|
|
float validCount = 0;
|
|
float2 texelSize = _MainTex_TexelSize.xy;
|
|
|
|
// 4x4 블록 샘플링 (현재 UV 중심으로)
|
|
for (int y = 0; y < 4; y++)
|
|
{
|
|
for (int x = 0; x < 4; x++)
|
|
{
|
|
float2 offset = (float2(x, y) - 1.5) * texelSize;
|
|
half4 s = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + offset);
|
|
float valid = step(0.5, s.a);
|
|
colorSum += s.rgb * valid;
|
|
validCount += valid;
|
|
}
|
|
}
|
|
|
|
if (validCount < 0.5)
|
|
return half4(0, 0, 0, 0);
|
|
|
|
return half4(colorSum / validCount, validCount / 16.0);
|
|
}
|
|
ENDHLSL
|
|
}
|
|
// ── Pass 9: StencilToMask (스텐실 → 마스크 변환) ────────────
|
|
// stencil=128 픽셀에 1.0 기록. Auto Match 분석용 마스크 생성.
|
|
Pass
|
|
{
|
|
Name "NiloToonCharToneAdjust_StencilToMask"
|
|
ZTest Always ZWrite Off Cull Off
|
|
Stencil
|
|
{
|
|
Ref 128 ReadMask 128 WriteMask 0
|
|
Comp Equal Pass Keep Fail Keep
|
|
}
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment StencilToMaskFrag
|
|
half4 StencilToMaskFrag(Varyings input) : SV_Target { return 1; }
|
|
ENDHLSL
|
|
}
|
|
}
|
|
}
|