- ToonPostProcess.shader: 횃불 고딕 스타일 후처리 쉐이더 (Built-in RP) - ToonCameraEffect.cs: 카메라 자동 부착 후처리 스크립트 - 중복 UI 스크립트 제거 (MenuIntroController, ToggleCustom) - 씬, 프리팹, 애니메이션 등 전체 업데이트 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
265 lines
12 KiB
Plaintext
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
|
|
}
|