Projext/Assets/Resources/Shaders/ToonPostProcess.shader
hydrozen e989d20668 카툰 쉐이더 추가 + 중복 스크립트 수정 + 전체 업데이트
- ToonPostProcess.shader: 횃불 고딕 스타일 후처리 쉐이더 (Built-in RP)
- ToonCameraEffect.cs: 카메라 자동 부착 후처리 스크립트
- 중복 UI 스크립트 제거 (MenuIntroController, ToggleCustom)
- 씬, 프리팹, 애니메이션 등 전체 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:31:16 +09:00

265 lines
12 KiB
Plaintext

// ══════════════════════════════════════════════════════════════════
// 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
}