// ───────────────────────────────────────────────────────────────────────────── // WLCharacterBacklightSettings.cs — 캐릭터 역광(림·라이트 랩·피부 투과) + 번짐(Bloom) 값 (C45 · 코드 상수 금지) // // 2026-09-17 PD 「캐릭터가 조금 더 돋보이게」 + 팁(차가운 역광 · SSS · Bloom). // enabled_ = 0 → 아무 것도 덧붙이지 않는다(캐릭터 재질 배열 원래대로) · bloomEnabled = 0 → 카메라/볼륨 0 변경. // 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(813z 규칙). // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; namespace WL.Look.Character { public class WLCharacterBacklightSettings : ScriptableObject { [Header("스위치")] [Tooltip("0 = 역광 패스 전체 off(재질 배열 원래대로).")] public int enabled_ = 1; [Tooltip("1 이면 적용 내역을 로그로 남긴다.")] public int verboseLog = 0; [Header("대상")] [Tooltip("이 셰이더 이름의 재질을 가진 렌더러에만 덧붙인다(얼굴 Unlit/Transparent · 파티클 제외). 비우면 파티클 외 전부.")] public string shaderNameFilter = "Shader Graphs/Toon"; [Tooltip("재질 이름에 이 글자가 들어 있으면 피부로 보고 피부 투과(SSS)를 켠 재질을 물린다.")] public string[] skinMaterialHints = { "Skin" }; [Header("⓪ 본체 밝히기 — 2026-09-17 PD 「캐릭터가 어둡게 보인다 · 원한 건 더 밝고 화사하게」")] [Tooltip("1 = 캐릭터 본체(Toon) 재질 값을 렌더러별 프로퍼티 블록으로 덮는다(재질 에셋 0줄 · 0 = 덮지 않음).")] public int baseBrighten = 1; [Tooltip("그늘의 최소 밝기(원본 0.2). 클수록 그늘이 밝다.")] [Range(0f, 1f)] public float baseMinimumDarkness = 0.38f; [Tooltip("그늘 색(원본 (0.216,0.209,0.371) = 어두운 남색). 밝고 따뜻하게.")] public Color baseShadowColor = new Color(0.46f, 0.38f, 0.44f, 0f); [Tooltip("구름 그늘 세기(원본은 지형과 같은 값). 0 = 캐릭터는 구름 그늘을 안 받는다(항상 화사). 음수 = 재질 값 그대로.")] public float baseCloudStrength = 0f; [Header("① 역광 림 — 2026-09-17 PD 「너무 푸른색 느낌」 → 태양색 계열의 따뜻한 흰빛(차가운 하늘색 (0.28,0.62,1) 에서 변경) · 「라이팅이 과하다」 → 약하게")] [ColorUsage(false, true)] public Color rimColor = new Color(1f, 0.96f, 0.88f, 1f); [Tooltip("림 밝기 배수(1 초과 = HDR · Bloom 이 집어낸다).")] public float rimStrength = 1.0f; [Header("①-b 화면 공간 깊이 림(기본) — 실루엣 바깥쪽에만 · 화면 픽셀 굵기 일정")] [Tooltip("1 = 깊이 림 사용(URP 에셋의 Depth Texture 가 꺼져 있으면 자동으로 프레넬만 쓴다). 실측: 프레넬만 쓰면 머리카락 안쪽 가닥마다 줄이 생긴다.")] public int rimUseDepth = 1; [Tooltip("띠 굵기 = 화면 짧은 변 × 이 값(0.0045 → 1080 기준 ≈ 5 px).")] [Range(0f, 0.02f)] public float rimWidth = 0.003f; [Tooltip("역광 쪽 이웃 픽셀이 이만큼(m) 더 멀면 실루엣으로 본다(작으면 머리카락 가닥에도 생긴다).")] public float rimDepthThreshold = 0.12f; [Tooltip("깊이 림에 섞는 프레넬 림의 양 — 가까울 때(두께감).")] [Range(0f, 1f)] public float rimFresnelNear = 0.25f; [Tooltip("깊이 림에 섞는 프레넬 림의 양 — 멀 때(0 = 깊이 림만 · 가닥 줄 없음).")] [Range(0f, 1f)] public float rimFresnelFar = 0f; [Header("①-a 프레넬 림(보조 · 깊이 텍스처가 없을 때는 이것만)")] [Tooltip("가까울 때(rimNearDistance 이하)의 띠 굵기 — 클수록 가는 띠(0.4 = 굵게 · 0.7 = 가늘게).")] [Range(0f, 1f)] public float rimThreshold = 0.52f; [Tooltip("멀 때(rimFarDistance 이상)의 띠 굵기 — 작게 줘서 굵게. 게임 카메라(섬 12 m · 던전 11 m)에서 캐릭터가 ~70 px 라 가는 띠는 1 px 로 사라진다(실측).")] [Range(0f, 1f)] public float rimThresholdFar = 0.36f; [Tooltip("이 거리(m) 이하 = 가까움.")] public float rimNearDistance = 3f; [Tooltip("이 거리(m) 이상 = 멂.")] public float rimFarDistance = 12f; [Tooltip("띠 가장자리 부드러움(작을수록 셀 느낌).")] [Range(0.001f, 0.5f)] public float rimSoftness = 0.05f; [Tooltip("0 = 둘레 전부 · 1 = 가상 역광 쪽 윤곽만.")] [Range(0f, 1f)] public float rimDirectional = 0.85f; [Tooltip("림이 바탕을 덮는 정도(1 = 림 색 그대로 · 0 = 순수 가산). 실측: 순수 가산은 노란 머리·살색 위에서 흰 테가 돼 차가운 색이 안 보였다.")] [Range(0f, 1f)] public float rimOpacity = 0.5f; [Header("가상 역광 위치(카메라 기준) — 캐릭터 뒤 + 위 + 옆")] [Tooltip("위쪽 성분(클수록 머리·어깨 윗선에 맺힌다).")] public float backlightUp = 0.7f; [Tooltip("옆 성분(+ = 화면 오른쪽 · − = 왼쪽). 메인 조명이 왼쪽 앞에서 오므로 오른쪽 뒤가 자연스럽다.")] public float backlightSide = 0.8f; [Header("② 라이트 랩 — 빛이 안쪽으로 스며드는 옅은 번짐")] [ColorUsage(false, true)] public Color wrapColor = new Color(1f, 0.95f, 0.88f, 1f); public float wrapStrength = 0.06f; public float wrapPower = 1.6f; [Header("③ 피부 투과(SSS 흉내) — 피부 재질에만")] [ColorUsage(false, true)] public Color sssColor = new Color(1f, 0.32f, 0.22f, 1f); public float sssStrength = 1.0f; public float sssPower = 1.7f; [Header("④ 얼굴 포인트 라이트 — 얼굴 주변만 살짝 밝히는 빛(2026-09-17 PD 「얼굴 쪽을 살짝 포인트로 하이라이트」)")] [Tooltip("0 = 끔.")] public int faceEnabled = 1; [ColorUsage(false, true)] public Color faceColor = new Color(1f, 0.93f, 0.85f, 1f); [Tooltip("밝기(가산). 0.2 = 은은 · 0.5 = 뚜렷.")] public float faceStrength = 0.16f; [Tooltip("빛이 닿는 반지름 = 얼굴(머리) 렌더러 경계 크기 × 이 값. 3 = 얼굴에서 가장 밝고 발끝으로 갈수록 옅어지는 몸 전체 채움빛(「밝고 화사」).")] public float faceRadiusScale = 3.0f; [Tooltip("0 = 빛 방향을 향한 면만 · 1 = 둥글게 감싸는 빛.")] [Range(0f, 1f)] public float faceWrap = 0.8f; [Tooltip("빛 위치(카메라 기준): 위쪽 성분.")] public float faceLightUp = 0.6f; [Tooltip("빛 위치(카메라 기준): 옆 성분(− = 화면 왼쪽 · 역광의 반대쪽).")] public float faceLightSide = -0.4f; [Tooltip("얼굴 중심으로 쓸 렌더러 이름 힌트(앞에서부터 찾는다 · 못 찾으면 얼굴 빛 없음).")] public string[] faceRendererHints = { "Face", "Head" }; [Header("등급 — 2026-09-17 PD 「톤은 전체 공유 · 플레이어 1등급 · 몬스터와 NPC 2등급 · 배경(프랍) 3등급」")] [Tooltip("1 = 2·3등급도 적용한다. 0 = 플레이어(1등급)만.")] public int tiersEnabled = 1; [Tooltip("2등급(몬스터·NPC·동료) — 림 세기/불투명도(1등급보다 약하게).")] public float tier2RimStrength = 0.7f; [Range(0f, 1f)] public float tier2RimOpacity = 0.35f; [Tooltip("2등급 — 몸 전체 채움빛 세기(얼굴 중심 없음).")] public float tier2FillStrength = 0.07f; [Tooltip("2등급 — 본체가 Toon 그래프일 때: 그늘 최소 밝기에 더하는 양 · 그늘 색을 공유 그늘 색 쪽으로 섞는 비율(재질마다 원래 값이 달라 상대값으로).")] [Range(0f, 0.5f)] public float tier2MinimumDarknessAdd = 0.10f; [Range(0f, 1f)] public float tier2ShadowBlend = 0.6f; public Color tier2ShadowColor = new Color(0.36f, 0.31f, 0.40f, 0f); [Tooltip("2등급 — 본체가 MK Toon(힘민지 몬스터)일 때 그늘 색 `_GoochDarkColor`(원본 검정). 전체가 공유하는 따뜻한 보랏빛 그늘 계열.")] public Color tier2MkShadowColor = new Color(0.30f, 0.25f, 0.34f, 1f); [Tooltip("2등급 대상 역할(원본 eRole 이름). PC 는 1등급이라 제외.")] public string[] tier2Roles = { "Mob", "Pet", "Worker", "Fairy", "EnemyPC", "Farmer", "Summon", "BattleNPC", "EnemyPet", "EnemySummon" }; [Tooltip("2등급 재검색 주기(s) — 새로 나온 몬스터에 붙인다.")] public float tier2ScanSeconds = 1.5f; [Tooltip("3등급(배경 프랍) — 덧그리기 없음(비용 0) · 공유 재질의 런타임 복사본에 그늘 값만 살짝: 그늘 최소 밝기에 더하는 양 · 공유 그늘 색 쪽으로 섞는 비율.")] [Range(0f, 0.5f)] public float tier3MinimumDarknessAdd = 0.06f; [Range(0f, 1f)] public float tier3ShadowBlend = 0.35f; public Color tier3ShadowColor = new Color(0.29f, 0.27f, 0.39f, 0f); [Tooltip("3등급 대상 셰이더 이름(이 셰이더를 쓰는 MeshRenderer · 캐릭터/몬스터/지형/풀 제외).")] public string[] tier3ShaderNames = { "Shader Graphs/Toon" }; [Tooltip("3등급 재검색 주기(s) — 섬 확장·설치물처럼 나중에 생기는 프랍.")] public float tier3ScanSeconds = 5f; [Header("⑤ 번짐(Bloom) — 역광이 화사하게 번지게(전체 화면 패스 · 모바일 비용 있음)")] [Tooltip("0 = 카메라·볼륨을 건드리지 않는다.")] public int bloomEnabled = 1; [Tooltip("이 씬이 떠 있을 때만 켠다(전투 맵의 기존 후처리는 그대로).")] public string[] bloomScenes = { "Level01" }; [Tooltip("이 밝기 이상만 번진다(HDR 1 초과 = 림·반짝임만).")] public float bloomThreshold = 1.2f; public float bloomIntensity = 0.4f; [Range(0f, 1f)] public float bloomScatter = 0.55f; [Tooltip("1 = 카메라 HDR 을 켠다(림만 골라 번지게 · 끄면 흰 거품·밝은 모래도 같이 번진다).")] public int bloomHdr = 1; [Tooltip("1 = 1/4 해상도에서 시작(모바일 권장).")] public int bloomQuarter = 1; [Tooltip("블러 단계 수(작을수록 쌈).")] public int bloomMaxIterations = 4; // ───────────────────────────────────────── 싱글턴(Resources) public const string ResourcePath = "WL/WLCharacterBacklightSettings"; static WLCharacterBacklightSettings s_inst; static bool s_tried; public static WLCharacterBacklightSettings Instance { get { if (s_inst == null && !s_tried) { s_tried = true; s_inst = Resources.Load(ResourcePath); } return s_inst; } } public static void Invalidate() { s_inst = null; s_tried = false; } } }