// ───────────────────────────────────────────────────────────────────────────── // WLEnvLookSettings.cs — Critter Environment 룩(툰 램프 + 1px 픽셀 외곽선 + 구름 그림자 // + 인스턴싱 잔디 + 거품 물)의 값 SOT. PD 지시 #814 · 발주서 WL-814c (2026-09-10) // // 🔴 코드 상수 0(C45) — 여기 있는 값만 바꾸면 룩이 바뀐다. // 🔴 originals / toons 는 에디터 스크립트(AgentScripts/WL814c_Convert.cs)가 채우는 스왑 표다. // 런타임은 이 표에 **있는 머티리얼만** 갈아끼운다 → 파티클·UI·이펙트는 절대 안 바뀐다. // 🔴 원본 .mat 파일은 어떤 경로로도 수정되지 않는다(참조만 보관). // ───────────────────────────────────────────────────────────────────────────── using UnityEngine; namespace WL.Look.Env { public sealed class WLEnvLookSettings : ScriptableObject { public const string kResourcePath = "WL/WLEnvLookSettings"; // ── C8 롤백 ────────────────────────────────────────────────────────── [Tooltip("0 = 이 기능 전체 정지(러너 오브젝트 0 · 스왑 0 · 피처 0 · 잔디 0)")] public bool enabled = true; [Tooltip("적용/복원 1줄 로그")] public bool verboseLog = false; [Tooltip("🔴 WL-814e — 1 이면 814d 룩 토글 허브(LookModeHub.EnvLookOn)를 따른다: 맵 진입 자동 적용도 허브가 ON 일 때만 · [배경] 버튼으로 즉시 적용/원복. 0 이면 814c 원래 동작(맵 진입 자동 적용)")] public bool followLookModeHub = true; // ── 맵 경계 ────────────────────────────────────────────────────────── [Tooltip("인게임 맵에서만 적용(로비/타이틀 제외)")] public bool applyOnlyInBattleMap = true; [Tooltip("맵 이탈·종료에서 원복")] public bool restoreOnExit = true; [Tooltip("맵 경계 폴링 주기(초)")] public float pollSeconds = 0.5f; [Tooltip("새로 스폰된 몹을 훑는 주기(초) · 0 이면 새 스폰 적용 안 함")] public float respawnSweepSeconds = 0.5f; // ── 스왑 대상 ──────────────────────────────────────────────────────── [Tooltip("맵 렌더러를 Toon 으로 스왑")] public bool swapMap = true; [Tooltip("캐릭터(PC·몹·보스) 렌더러를 Toon 으로 스왑 — PD 결정용(캡처 ⓔ/ⓕ 비교)")] public bool swapCharacters = true; // ── 스왑 표(에디터 스크립트가 채운다 · 손으로 만지지 말 것) ─────────── public Material[] originals; public Material[] toons; // ── 머티리얼 생성 값(에디터 스크립트가 읽는다) ──────────────────────── [Tooltip("캐릭터 머티리얼도 Toon 으로 만들 것인가(생성 단계)")] public bool convertCharacters = true; [Tooltip("툰 램프 단계 수 4~8 (데모 7 · 제안 5)")] public int shades = 5; public float brightness = 0.25f; public float minimumDarkness = 0.2f; public float ambientStrength = 0.1f; [Tooltip("그림자 색 = 원본 색 × 이 계수(데모 머티리얼 관계 근사)")] public Color shadowTint = new Color(0.42f, 0.40f, 0.55f, 1f); // ── 1px 픽셀 외곽선(오브젝트 셰이더 내부 계산) ──────────────────────── public bool outlinesEnabled = true; [Tooltip("🔴 캐릭터에도 외곽선을 줄 것인가. 실측: 스킨드 메시는 폴리곤이 촘촘해 270px 에서 노이즈가 된다 → 기본 off")] public bool characterOutlines = false; [Tooltip("깊이 외곽선 임계 = (외곽선을 낼 최소 단차 m) ÷ (직교 카메라 far − near). 데모 0.01 은 원근 카메라 기준")] public float depthThreshold = 0.004f; [Tooltip("노멀 외곽선 임계. 데모 1 · 낮출수록 면 안쪽까지 선이 생겨 노이즈가 된다")] public float normalThreshold = 1f; public Vector4 normalBias = new Vector4(1f, 1f, 1f, 0f); public float depthEdgeStrength = 0.5f; public float normalEdgeStrength = 0.3f; public Color outlineColor = new Color(0f, 0f, 0f, 0f); // ── 구름 그림자 ────────────────────────────────────────────────────── // 🔴 실측: Critter Toon 셰이더에 `_CLOUDSENABLED` 키워드는 **없다**(데모 .mat 의 그 키워드는 구버전 잔재). // on/off 는 `_Cloud_Strength` 로 한다(0 = 끔). public bool cloudShadows = true; public float cloudDensity = 0.01f; public Vector4 cloudMovement = new Vector4(1f, 1f, 0f, 0f); public float cloudStrength = 1f; public float cloudCover = 0.5f; public float cloudChange = 0.005f; public Vector4 cloudStep = new Vector4(13f, 17f, 0f, 0f); // ── 렌더러 피처(깊이·노멀 텍스처) ───────────────────────────────────── [Tooltip("PixelOutlineSetupFeature 를 런타임에 켠다")] public bool outlineFeature = true; [Tooltip("인게임 맵에서만 피처 활성(타이틀·로비 비용 0)")] public bool outlineFeatureOnlyInBattleMap = true; // ── 잔디 인스턴싱 ──────────────────────────────────────────────────── public bool grassEnabled = true; [Tooltip("데모 대비 밀도 비율. 1 = 데모(Critter Density 2.5) · 0.5 = 인스턴스 절반")] public float grassDensity = 0.5f; [Tooltip("데모 실측 Density(면적/인스턴스). 만지지 말 것")] public float demoDensity = 2.5f; [Tooltip("모바일 상한 — 이 수를 넘으면 밀도를 자동으로 낮춘다")] public int maxInstances = 20000; [Tooltip("지면 후보 판정: 수평 투영 면적(m²) 하한")] public float minGroundFootprint = 100f; [Tooltip("지면 후보 판정: 바운드 높이(m) 상한 — 배경 산·절벽을 뺀다(WL_Nature 실측 산 58~200 m · 지면 2~16 m)")] public float maxGroundBoundsHeight = 30f; [Tooltip("지면 후보 판정: 평평함 = 높이 ÷ 짧은 변. 실측 지면 0.02~0.15 · 나무/바위 0.65~0.90")] public float groundFlatness = 0.35f; [Tooltip("지면 후보 판정: 메시 면적 ÷ 수평 투영 면적. 실측 지면 ≈1.0 · 나무 수관 ≈0.3 (성긴 면은 지면이 아니다)")] public float groundAreaRatio = 0.5f; [Tooltip("지면 후보 최대 개수(플레이어에 가까운 순)")] public int maxGroundMeshes = 4; [Tooltip("잔디 한 포기의 목표 높이(m) — 메시 크기로 Scale 을 자동 계산한다(0 이면 아래 grassScale 을 그대로 쓴다)")] public float grassHeightMeters = 0.3f; [Tooltip("grassHeightMeters 가 0 일 때 쓰는 원시 Scale(데모 0.35)")] public float grassScale = 0.35f; public float grassNormalOffset = 0.04f; [Tooltip("잔디 : 꽃 가중치(데모 100 : 1)")] public float grassProbability = 100f; public float flowerProbability = 1f; [Tooltip("지면 메시가 읽기 불가일 때 레이캐스트로 만드는 대체 격자 한 변 분할 수")] public int groundProbeGrid = 48; // ── 물 ─────────────────────────────────────────────────────────────── [Tooltip("물 렌더러를 Critter Water 로 스왑(반사 카메라는 붙이지 않는다)")] public bool waterEnabled = true; public Material waterMaterial; // ── 에셋 참조(에디터 스크립트가 채움) ───────────────────────────────── public Mesh grassMesh; public Material grassMaterial; public Mesh flowerMesh; public Material flowerMaterial; // ───────────────────────────────────────────────────────────────────── static WLEnvLookSettings s_ins; static bool s_tried; /// 프로브·C8 용 런타임 강제 정지(에셋 파일은 건드리지 않는다). public static bool RuntimeDisabled; public static WLEnvLookSettings Instance { get { if (s_ins == null && !s_tried) { s_tried = true; s_ins = Resources.Load(kResourcePath); } return s_ins; } } /// 기능이 살아 있는가 = 에셋 존재 · enabled · RuntimeDisabled off. public static bool Enabled { get { var c = Instance; return c != null && c.enabled && !RuntimeDisabled; } } /// 프로브 전용 — 캐시를 비운다(SO 를 코드로 바꿔치기할 때). public static void ClearCacheForProbe() { s_ins = null; s_tried = false; } } }