// ══════════════════════════════════════════════════════════════════ // ToonPostProcess v3 — "횃불 고딕" 스타일 (Curse of the Dead Gods 풍) // // [컨셉] 어둠 속 따뜻한 빛 vs 차가운 그림자 // 1. 화면 전체를 어둡게 깔기 (Dark Crush) // 2. 밝은 영역만 따뜻한 톤 (금색/주황 하이라이트) // 3. 어두운 영역은 차가운 톤 (남색/보라 그림자) // 4. 강한 비네팅 (모서리 어둡게) // 5. 중간 두께 외곽선 // 6. 낮은 채도 (탈색된 분위기) // ══════════════════════════════════════════════════════════════════ Shader "Hidden/ToonPostProcess" { Properties { _MainTex ("Main Texture", 2D) = "white" {} [Header(Darkness)] _DarkCrush ("Dark Crush", Range(0, 0.3)) = 0.08 // 전체 어둡게 깔기 _ContrastBoost ("Contrast Boost", Range(1.0, 2.5)) = 1.4 // 명암 대비 강화 _BlackPoint ("Black Point", Range(0, 0.15)) = 0.03 // 최소 밝기 (완전 검정 방지) [Header(Color Grading)] _WarmColor ("Warm Highlight", Color) = (1.0, 0.85, 0.55, 1) // 따뜻한 하이라이트 (금색) _CoolColor ("Cool Shadow", Color) = (0.25, 0.2, 0.45, 1) // 차가운 그림자 (남보라) _ColorSplit ("Color Split Point", Range(0.1, 0.7)) = 0.35 // 따뜻/차가운 경계점 _GradingStrength ("Grading Strength", Range(0, 1)) = 0.5 // 색조 적용 강도 _Desaturation ("Desaturation", Range(0, 0.7)) = 0.3 // 탈색 정도 [Header(Vignette)] _VignetteIntensity ("Vignette Intensity", Range(0, 2)) = 1.2 // 비네팅 강도 _VignetteRadius ("Vignette Radius", Range(0.3, 1.5)) = 0.75 // 비네팅 반경 _VignetteSoftness ("Vignette Softness", Range(0.1, 1.0)) = 0.45 // 비네팅 부드러움 _VignetteColor ("Vignette Color", Color) = (0.02, 0.01, 0.05, 1) // 비네팅 색상 (짙은 남색) [Header(Edge Detection)] _EdgeColor ("Edge Color", Color) = (0.03, 0.01, 0.02, 1) // 외곽선 색상 _EdgeThickness ("Edge Thickness", Range(0.5, 5.0)) = 1.8 // 외곽선 두께 _DepthThreshold ("Depth Sensitivity", Range(0.0001, 0.05)) = 0.003 // 깊이 감도 _NormalThreshold ("Normal Sensitivity", Range(0.1, 1.5)) = 0.5 // 노멀 감도 [Header(Posterize)] _ColorSteps ("Color Steps", Range(2, 20)) = 8 // 색상 단계 (약하게) _LuminanceSteps ("Luminance Steps", Range(2, 12)) = 5 // 밝기 단계 _PosterizeStrength ("Posterize Strength", Range(0, 1)) = 0.35 // 포스터라이즈 강도 (약하게) } SubShader { Tags { "RenderType"="Opaque" } Pass { ZTest Always ZWrite Off Cull Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" // ── 유니폼 ── sampler2D _MainTex; float4 _MainTex_TexelSize; sampler2D _CameraDepthNormalsTexture; float _DarkCrush; float _ContrastBoost; float _BlackPoint; fixed4 _WarmColor; fixed4 _CoolColor; float _ColorSplit; float _GradingStrength; float _Desaturation; float _VignetteIntensity; float _VignetteRadius; float _VignetteSoftness; fixed4 _VignetteColor; fixed4 _EdgeColor; float _EdgeThickness; float _DepthThreshold; float _NormalThreshold; float _ColorSteps; float _LuminanceSteps; float _PosterizeStrength; // ── 구조체 ── struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } // ══════════════════════════════════════════════════════════ // 1. Dark Crush — 전체적으로 어둡게 깔기 // ══════════════════════════════════════════════════════════ fixed3 ApplyDarkCrush(fixed3 color) // 어둠 적용 { // 밝기 계산 float lum = dot(color, float3(0.299, 0.587, 0.114)); // 어두운 영역을 더 어둡게 밀어넣기 float darkMask = 1.0 - smoothstep(0.0, 0.5, lum); // 어두운 영역 마스크 color *= (1.0 - _DarkCrush * darkMask); // 어두운 곳을 더 어둡게 // 대비 강화 (중간톤 기준) color = saturate((color - 0.5) * _ContrastBoost + 0.5); // S-커브 대비 // 최소 밝기 보장 (완전 검정 방지) color = max(color, _BlackPoint); // 바닥값 return color; } // ══════════════════════════════════════════════════════════ // 2. Color Grading — 따뜻한 빛 vs 차가운 그림자 // ══════════════════════════════════════════════════════════ fixed3 ApplyColorGrading(fixed3 color) // 색조 분리 { float lum = dot(color, float3(0.299, 0.587, 0.114)); // 밝기 // 밝기에 따라 따뜻/차가운 톤 블렌딩 float warmMask = smoothstep(_ColorSplit - 0.15, _ColorSplit + 0.15, lum); // 부드러운 경계 // 하이라이트 → 따뜻한 금색 틴트 fixed3 warmTinted = color * _WarmColor.rgb; // 따뜻한 틴트 // 그림자 → 차가운 남보라 틴트 fixed3 coolTinted = color * _CoolColor.rgb * 2.0; // 차가운 틴트 (2배로 보정) // 밝기 기준 블렌딩 fixed3 graded = lerp(coolTinted, warmTinted, warmMask); // 톤 합성 // 원본과 블렌딩 color = lerp(color, graded, _GradingStrength); // 강도 적용 // 탈색 (채도 낮추기) float grayLum = dot(color, float3(0.299, 0.587, 0.114)); // 새 밝기 color = lerp(color, fixed3(grayLum, grayLum, grayLum), _Desaturation); // 탈색 return saturate(color); } // ══════════════════════════════════════════════════════════ // 3. Vignette — 화면 모서리 어둡게 // ══════════════════════════════════════════════════════════ fixed3 ApplyVignette(fixed3 color, float2 uv) // 비네팅 { // 화면 중앙으로부터 거리 float2 center = uv - 0.5; // 중앙 기준 좌표 float dist = length(center); // 거리 // 비네팅 마스크 (부드러운 감쇠) float vignette = smoothstep(_VignetteRadius, _VignetteRadius - _VignetteSoftness, dist); // 감쇠 커브 vignette = pow(vignette, _VignetteIntensity); // 강도 적용 // 비네팅 색상과 블렌딩 (단순 검정이 아닌 짙은 남색) color = lerp(_VignetteColor.rgb, color, vignette); // 모서리를 비네팅 색으로 return color; } // ══════════════════════════════════════════════════════════ // 4. Edge Detection — 깊이+노멀 외곽선 // ══════════════════════════════════════════════════════════ void SampleDepthNormal(float2 uv, out float depth, out float3 normal) // 깊이+노멀 샘플링 { float4 cdn = tex2D(_CameraDepthNormalsTexture, uv); DecodeDepthNormal(cdn, depth, normal); } float DetectEdge(float2 uv) // 엣지 검출 { float2 texel = _MainTex_TexelSize.xy * _EdgeThickness; float dc; float3 nc; SampleDepthNormal(uv, dc, nc); // 중심 float d0; float3 n0; SampleDepthNormal(uv + float2( texel.x, 0), d0, n0); float d1; float3 n1; SampleDepthNormal(uv + float2(-texel.x, 0), d1, n1); float d2; float3 n2; SampleDepthNormal(uv + float2(0, texel.y), d2, n2); float d3; float3 n3; SampleDepthNormal(uv + float2(0, -texel.y), d3, n3); // 깊이 엣지 float depthDiff = abs(d0 - dc) + abs(d1 - dc) + abs(d2 - dc) + abs(d3 - dc); float depthEdge = step(_DepthThreshold, depthDiff); // 노멀 엣지 float normalDiff = 0; normalDiff += 1.0 - dot(nc, n0); normalDiff += 1.0 - dot(nc, n1); normalDiff += 1.0 - dot(nc, n2); normalDiff += 1.0 - dot(nc, n3); float normalEdge = step(_NormalThreshold, normalDiff); return saturate(depthEdge + normalEdge); } // ══════════════════════════════════════════════════════════ // 5. 약한 포스터라이즈 // ══════════════════════════════════════════════════════════ fixed3 LightPosterize(fixed3 color) // 약한 색상 단계화 { float lum = dot(color, float3(0.299, 0.587, 0.114)); float lumBanded = floor(lum * _LuminanceSteps + 0.5) / _LuminanceSteps; float lumScale = (lum > 0.05) ? saturate(lumBanded / lum) : 1.0; fixed3 posterized = floor(color * _ColorSteps + 0.5) / _ColorSteps; fixed3 result = saturate(posterized * lumScale); return lerp(color, result, _PosterizeStrength); // 약하게만 적용 } // ══════════════════════════════════════════════════════════ // 메인 // ══════════════════════════════════════════════════════════ fixed4 frag(v2f i) : SV_Target { float2 uv = i.uv; fixed3 color = tex2D(_MainTex, uv).rgb; // 원본 색상 // 1. 약한 포스터라이즈 (카툰 느낌 살짝) color = LightPosterize(color); // 2. Dark Crush (전체 어둡게) color = ApplyDarkCrush(color); // 3. Color Grading (따뜻한 빛 + 차가운 그림자) color = ApplyColorGrading(color); // 4. 외곽선 float edge = DetectEdge(uv); color = lerp(color, _EdgeColor.rgb, edge * _EdgeColor.a); // 5. 비네팅 (마지막에 적용) color = ApplyVignette(color, uv); return fixed4(color, 1.0); } ENDCG } } FallBack Off }