- ToonPostProcess.shader: 횃불 고딕 스타일 후처리 쉐이더 (Built-in RP) - ToonCameraEffect.cs: 카메라 자동 부착 후처리 스크립트 - 중복 UI 스크립트 제거 (MenuIntroController, ToggleCustom) - 씬, 프리팹, 애니메이션 등 전체 업데이트 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
241 lines
12 KiB
C#
241 lines
12 KiB
C#
using UnityEngine; // 유니티 엔진 기본 기능
|
|
|
|
// ══════════════════════════════════════════════════════════════════
|
|
// ToonCameraEffect v3 — "횃불 고딕" 스타일 후처리
|
|
//
|
|
// [컨셉] Curse of the Dead Gods 풍
|
|
// 어둠 속 따뜻한 빛 + 차가운 그림자 + 강한 비네팅 + 외곽선
|
|
// ══════════════════════════════════════════════════════════════════
|
|
[ExecuteInEditMode] // 에디터 미리보기
|
|
[RequireComponent(typeof(Camera))] // Camera 필수
|
|
public class ToonCameraEffect : MonoBehaviour
|
|
{
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 런타임 자동 부착
|
|
// ─────────────────────────────────────────────────────────────
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
|
private static void AutoAttachToMainCamera() // 메인 카메라에 자동 부착
|
|
{
|
|
Camera mainCam = Camera.main; // 메인 카메라 탐색
|
|
if (mainCam == null) return; // 없으면 중단
|
|
|
|
if (mainCam.GetComponent<ToonCameraEffect>() == null) // 안 붙어있으면
|
|
{
|
|
mainCam.gameObject.AddComponent<ToonCameraEffect>(); // 부착
|
|
Debug.Log("[ToonCameraEffect] 메인 카메라에 횃불 고딕 이펙트 자동 부착!");
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 어둠 설정
|
|
// ─────────────────────────────────────────────────────────────
|
|
[Header("=== 어둠 (Darkness) ===")]
|
|
|
|
[Tooltip("전체 어둡게 깔기 (높을수록 어두움)")]
|
|
[Range(0f, 0.3f)]
|
|
[SerializeField] private float darkCrush = 0.08f;
|
|
|
|
[Tooltip("명암 대비 강화")]
|
|
[Range(1.0f, 2.5f)]
|
|
[SerializeField] private float contrastBoost = 1.4f;
|
|
|
|
[Tooltip("최소 밝기 (완전 검정 방지)")]
|
|
[Range(0f, 0.15f)]
|
|
[SerializeField] private float blackPoint = 0.03f;
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 색조 분리 설정
|
|
// ─────────────────────────────────────────────────────────────
|
|
[Header("=== 색조 (Color Grading) ===")]
|
|
|
|
[Tooltip("밝은 영역 색상 (따뜻한 금색)")]
|
|
[SerializeField] private Color warmColor = new Color(1.0f, 0.85f, 0.55f, 1f);
|
|
|
|
[Tooltip("어두운 영역 색상 (차가운 남보라)")]
|
|
[SerializeField] private Color coolColor = new Color(0.25f, 0.2f, 0.45f, 1f);
|
|
|
|
[Tooltip("따뜻/차가운 경계점")]
|
|
[Range(0.1f, 0.7f)]
|
|
[SerializeField] private float colorSplit = 0.35f;
|
|
|
|
[Tooltip("색조 적용 강도")]
|
|
[Range(0f, 1f)]
|
|
[SerializeField] private float gradingStrength = 0.5f;
|
|
|
|
[Tooltip("탈색 정도 (높을수록 무채색)")]
|
|
[Range(0f, 0.7f)]
|
|
[SerializeField] private float desaturation = 0.3f;
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 비네팅 설정
|
|
// ─────────────────────────────────────────────────────────────
|
|
[Header("=== 비네팅 (Vignette) ===")]
|
|
|
|
[Tooltip("비네팅 강도")]
|
|
[Range(0f, 2f)]
|
|
[SerializeField] private float vignetteIntensity = 1.2f;
|
|
|
|
[Tooltip("비네팅 반경 (작을수록 좁은 시야)")]
|
|
[Range(0.3f, 1.5f)]
|
|
[SerializeField] private float vignetteRadius = 0.75f;
|
|
|
|
[Tooltip("비네팅 부드러움")]
|
|
[Range(0.1f, 1.0f)]
|
|
[SerializeField] private float vignetteSoftness = 0.45f;
|
|
|
|
[Tooltip("비네팅 색상 (짙은 남색)")]
|
|
[SerializeField] private Color vignetteColor = new Color(0.02f, 0.01f, 0.05f, 1f);
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 외곽선 설정
|
|
// ─────────────────────────────────────────────────────────────
|
|
[Header("=== 외곽선 (Edge) ===")]
|
|
|
|
[Tooltip("외곽선 색상")]
|
|
[SerializeField] private Color edgeColor = new Color(0.03f, 0.01f, 0.02f, 1f);
|
|
|
|
[Tooltip("외곽선 두께")]
|
|
[Range(0.5f, 5.0f)]
|
|
[SerializeField] private float edgeThickness = 1.8f;
|
|
|
|
[Tooltip("깊이 엣지 감도")]
|
|
[Range(0.0001f, 0.05f)]
|
|
[SerializeField] private float depthThreshold = 0.003f;
|
|
|
|
[Tooltip("노멀 엣지 감도")]
|
|
[Range(0.1f, 1.5f)]
|
|
[SerializeField] private float normalThreshold = 0.5f;
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 포스터라이즈 설정
|
|
// ─────────────────────────────────────────────────────────────
|
|
[Header("=== 포스터라이즈 (약하게) ===")]
|
|
|
|
[Tooltip("색상 단계")]
|
|
[Range(2, 20)]
|
|
[SerializeField] private int colorSteps = 8;
|
|
|
|
[Tooltip("밝기 단계")]
|
|
[Range(2, 12)]
|
|
[SerializeField] private int luminanceSteps = 5;
|
|
|
|
[Tooltip("포스터라이즈 강도 (약하게 권장)")]
|
|
[Range(0f, 1f)]
|
|
[SerializeField] private float posterizeStrength = 0.35f;
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 내부 변수
|
|
// ─────────────────────────────────────────────────────────────
|
|
private Material _material;
|
|
private Shader _shader;
|
|
|
|
// 쉐이더 프로퍼티 ID 캐싱
|
|
private static readonly int PropDarkCrush = Shader.PropertyToID("_DarkCrush");
|
|
private static readonly int PropContrastBoost = Shader.PropertyToID("_ContrastBoost");
|
|
private static readonly int PropBlackPoint = Shader.PropertyToID("_BlackPoint");
|
|
private static readonly int PropWarmColor = Shader.PropertyToID("_WarmColor");
|
|
private static readonly int PropCoolColor = Shader.PropertyToID("_CoolColor");
|
|
private static readonly int PropColorSplit = Shader.PropertyToID("_ColorSplit");
|
|
private static readonly int PropGradingStrength = Shader.PropertyToID("_GradingStrength");
|
|
private static readonly int PropDesaturation = Shader.PropertyToID("_Desaturation");
|
|
private static readonly int PropVignetteIntensity = Shader.PropertyToID("_VignetteIntensity");
|
|
private static readonly int PropVignetteRadius = Shader.PropertyToID("_VignetteRadius");
|
|
private static readonly int PropVignetteSoftness = Shader.PropertyToID("_VignetteSoftness");
|
|
private static readonly int PropVignetteColor = Shader.PropertyToID("_VignetteColor");
|
|
private static readonly int PropEdgeColor = Shader.PropertyToID("_EdgeColor");
|
|
private static readonly int PropEdgeThickness = Shader.PropertyToID("_EdgeThickness");
|
|
private static readonly int PropDepthThreshold = Shader.PropertyToID("_DepthThreshold");
|
|
private static readonly int PropNormalThreshold = Shader.PropertyToID("_NormalThreshold");
|
|
private static readonly int PropColorSteps = Shader.PropertyToID("_ColorSteps");
|
|
private static readonly int PropLuminanceSteps = Shader.PropertyToID("_LuminanceSteps");
|
|
private static readonly int PropPosterizeStrength = Shader.PropertyToID("_PosterizeStrength");
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 초기화
|
|
// ─────────────────────────────────────────────────────────────
|
|
private void Awake()
|
|
{
|
|
Camera cam = GetComponent<Camera>();
|
|
if (cam != null)
|
|
{
|
|
cam.depthTextureMode |= DepthTextureMode.Depth | DepthTextureMode.DepthNormals; // 깊이+노멀 활성화
|
|
}
|
|
}
|
|
|
|
private bool EnsureMaterial()
|
|
{
|
|
if (_material != null) return true;
|
|
|
|
if (_shader == null)
|
|
_shader = Shader.Find("Hidden/ToonPostProcess");
|
|
|
|
if (_shader == null)
|
|
_shader = Resources.Load<Shader>("Shaders/ToonPostProcess");
|
|
|
|
if (_shader == null || !_shader.isSupported)
|
|
{
|
|
Debug.LogError("[ToonCameraEffect] 쉐이더를 찾을 수 없습니다: Hidden/ToonPostProcess");
|
|
enabled = false;
|
|
return false;
|
|
}
|
|
|
|
_material = new Material(_shader);
|
|
_material.hideFlags = HideFlags.HideAndDontSave;
|
|
return true;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// 후처리 적용
|
|
// ─────────────────────────────────────────────────────────────
|
|
private void OnRenderImage(RenderTexture source, RenderTexture destination)
|
|
{
|
|
if (!EnsureMaterial())
|
|
{
|
|
Graphics.Blit(source, destination);
|
|
return;
|
|
}
|
|
|
|
// 어둠
|
|
_material.SetFloat(PropDarkCrush, darkCrush);
|
|
_material.SetFloat(PropContrastBoost, contrastBoost);
|
|
_material.SetFloat(PropBlackPoint, blackPoint);
|
|
|
|
// 색조
|
|
_material.SetColor(PropWarmColor, warmColor);
|
|
_material.SetColor(PropCoolColor, coolColor);
|
|
_material.SetFloat(PropColorSplit, colorSplit);
|
|
_material.SetFloat(PropGradingStrength, gradingStrength);
|
|
_material.SetFloat(PropDesaturation, desaturation);
|
|
|
|
// 비네팅
|
|
_material.SetFloat(PropVignetteIntensity, vignetteIntensity);
|
|
_material.SetFloat(PropVignetteRadius, vignetteRadius);
|
|
_material.SetFloat(PropVignetteSoftness, vignetteSoftness);
|
|
_material.SetColor(PropVignetteColor, vignetteColor);
|
|
|
|
// 외곽선
|
|
_material.SetColor(PropEdgeColor, edgeColor);
|
|
_material.SetFloat(PropEdgeThickness, edgeThickness);
|
|
_material.SetFloat(PropDepthThreshold, depthThreshold);
|
|
_material.SetFloat(PropNormalThreshold, normalThreshold);
|
|
|
|
// 포스터라이즈
|
|
_material.SetFloat(PropColorSteps, colorSteps);
|
|
_material.SetFloat(PropLuminanceSteps, luminanceSteps);
|
|
_material.SetFloat(PropPosterizeStrength, posterizeStrength);
|
|
|
|
Graphics.Blit(source, destination, _material);
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_material != null)
|
|
{
|
|
if (Application.isPlaying)
|
|
Destroy(_material);
|
|
else
|
|
DestroyImmediate(_material);
|
|
}
|
|
}
|
|
}
|