// WL-814x2 — 🔴 **Play 모드에서만** 재는 색 측정/촬영 하니스 // 편집 모드 렌더는 스킨 메시가 갱신되지 않아 무의미하다(814x 에서 실측). // run_script --file AgentScripts/WL814x2_Play.cs --entry WL814x2.Run (Play 중에 부른다) using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEngine; using WL.Look.Arena; using WL.Look.Character; public static class WL814x4 { public const string RAW = "Screenshots_WL/WL814x/4th/raw/"; const int W = 1080, H = 1920; static readonly StringBuilder sb = new StringBuilder(); static void L(string s) { sb.AppendLine(s); } static GameObject s_root; static Renderer[] s_rends; static Material[][] s_orig; static Camera s_cam; static Vector3 s_camPos, s_center, s_pos; static Quaternion s_rot, s_camRot; static GameObject s_ovr; static Vector4 s_shad0; static bool s_shadOv0, s_shadSaved; static Animator s_an; static float s_camSize, s_ts; static void Setup() { s_root = GameObject.Find("PC_LH_M05"); if (s_root == null) foreach (var g in UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None)) if (g.name.Contains("LH_M05")) { s_root = g.gameObject; break; } // 🔴 아레나에는 캐릭터를 걷게 하는 리그가 있다 — 샷마다 위치/포즈가 달라지면 색 비교가 무의미하다. s_ts = Time.timeScale; Time.timeScale = 0f; s_an = s_root.GetComponentInChildren(); if (s_an != null) { s_an.speed = 0f; try { s_an.Play("idle", 0, 0.25f); s_an.Update(0f); } catch { } } s_cam = Camera.main ?? UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None).First(c => c.orthographic); s_camPos = s_cam.transform.position; s_camSize = s_cam.orthographicSize; s_camRot = s_cam.transform.rotation; s_root.transform.rotation = Quaternion.Euler(0, s_cam.transform.eulerAngles.y + 180f, 0); s_pos = s_root.transform.position; s_rot = s_root.transform.rotation; s_rends = s_root.GetComponentsInChildren(true).Where(r => r.enabled && r.gameObject.activeInHierarchy).ToArray(); s_orig = s_rends.Select(r => r.sharedMaterials).ToArray(); var b = new Bounds(s_root.transform.position, Vector3.zero); foreach (var r in s_rends) b.Encapsulate(r.bounds); s_center = b.center; L("renderers " + string.Join(",", s_rends.Select(r => r.name)) + " · center " + s_center.ToString("F2") + " · h " + b.size.y.ToString("F3")); foreach (var r in s_rends) { var m = r.sharedMaterial; L(" " + r.name + " mat=" + m.name + " shader=" + m.shader.name + " base=" + (m.HasProperty("_BaseMap") && m.GetTexture("_BaseMap") ? m.GetTexture("_BaseMap").name : "-") + " diff=" + (m.HasProperty("_DiffuseColor") ? m.GetColor("_DiffuseColor").ToString("F3") : "-") + " shadow=" + (m.HasProperty("_ShadowDiffuseColor") ? m.GetColor("_ShadowDiffuseColor").ToString("F4") : "-") + " shades=" + (m.HasProperty("_Shades") ? m.GetFloat("_Shades").ToString("F0") : "-") + " minDark=" + (m.HasProperty("_MinimumDarkness") ? m.GetFloat("_MinimumDarkness").ToString("F2") : "-")); } var rc = WLReferenceLookSettings.Instance; L("ReferenceLook: rimOn=" + rc.rimOn + " moodOn=" + rc.moodOn + " mood=" + rc.moodStrength.ToString("F2") + " contrast=" + rc.contrastOn + " minDark=" + rc.minimumDarkness.ToString("F2") + " shadowScale=" + rc.shadowColorScale.ToString("F2") + " shadowTintAmt=" + rc.shadowTintAmount.ToString("F2") + " shadowTint=" + rc.shadowTint.ToString("F3") + " charShades=" + rc.charShades + " key=" + rc.keyLightColor.ToString("F2") + "×" + rc.keyLightIntensity.ToString("F2") + " amb=" + rc.ambientColor.ToString("F2")); } static void Teardown() { for (int i = 0; i < s_rends.Length; i++) { s_rends[i].sharedMaterials = s_orig[i]; s_rends[i].enabled = true; } s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot); s_cam.orthographicSize = s_camSize; Time.timeScale = s_ts; } static int[] s_layers; static int s_cull; static CameraClearFlags s_clear; static Color s_bg; static bool s_post, s_postSaved; /// 캐릭터만 남기고 배경·그림자·포스트를 모두 끈 렌더 상태(마스크·덩어리지도 전용). static void Isolate(bool on) { var ts = s_root.GetComponentsInChildren(true); var ucd = s_cam.GetComponent(); if (on) { s_layers = ts.Select(t => t.gameObject.layer).ToArray(); foreach (var t in ts) t.gameObject.layer = 31; s_cull = s_cam.cullingMask; s_clear = s_cam.clearFlags; s_bg = s_cam.backgroundColor; s_cam.cullingMask = 1 << 31; s_cam.clearFlags = CameraClearFlags.SolidColor; s_cam.backgroundColor = Color.black; if (ucd != null) { s_post = ucd.renderPostProcessing; s_postSaved = true; ucd.renderPostProcessing = false; } } else { for (int i = 0; i < ts.Length && i < s_layers.Length; i++) ts[i].gameObject.layer = s_layers[i]; s_cam.cullingMask = s_cull; s_cam.clearFlags = s_clear; s_cam.backgroundColor = s_bg; if (ucd != null && s_postSaved) { ucd.renderPostProcessing = s_post; s_postSaved = false; } } } class Shot { public Color32[] px; } static Shot Render(float ortho) { // 매 렌더마다 같은 위치·같은 포즈로 고정한다 s_root.transform.SetPositionAndRotation(s_pos, s_rot); if (s_an != null) { s_an.speed = 0f; try { s_an.Play("idle", 0, 0.25f); s_an.Update(0f); } catch { } } // 🔴 카메라도 매 프레임 따라다니는 스크립트가 있다 — 회전까지 고정해야 프레이밍이 같다. s_cam.orthographicSize = ortho; s_cam.transform.SetPositionAndRotation(s_center - (s_camRot * Vector3.forward) * 30f, s_camRot); var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create(); var prev = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.targetTexture = prev; var pa = RenderTexture.active; RenderTexture.active = rt; var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, W, H), 0, 0); t.Apply(); RenderTexture.active = pa; var s = new Shot { px = t.GetPixels32() }; UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt); return s; } static void Save(Shot s, string name) { var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.SetPixels32(s.px); t.Apply(); Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + name + ".png", t.EncodeToPNG()); UnityEngine.Object.Destroy(t); } static void Shoot(string name, float ortho) { Render(ortho); Save(Render(ortho), name); } static void SaveMask(string name, float ortho, params Renderer[] on) { Isolate(true); foreach (var r in s_rends) r.enabled = on.Contains(r); var a = Render(ortho); foreach (var r in s_rends) r.enabled = true; Isolate(false); var t = new Texture2D(W, H, TextureFormat.RGBA32, false); var px = new Color32[W * H]; int n = 0; for (int i = 0; i < px.Length; i++) { int d = a.px[i].r + a.px[i].g + a.px[i].b; bool hit = d > 12; if (hit) n++; px[i] = hit ? new Color32(255, 255, 255, 255) : new Color32(0, 0, 0, 255); } t.SetPixels32(px); t.Apply(); Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + "mask_" + name + ".png", t.EncodeToPNG()); UnityEngine.Object.Destroy(t); L("mask " + name + " @" + ortho.ToString("F2") + " = " + n + " px"); } /// 덩어리(팔레트 칸) 지도 — 빛 0. 부위별 색을 재기 위한 마스크. static void ShootIdMap(string name, float ortho) { var pal = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/WL/Look/Character/Textures/WLPalette_FINAL.png"); int S = pal.width, G = S / 8; var t = new Texture2D(S, S, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point, wrapMode = TextureWrapMode.Clamp }; var px = new Color32[S * S]; for (int i = 0; i < px.Length; i++) px[i] = new Color32(0, 0, 0, 255); for (int j = 0; j < G * G; j++) { int gx = j % G, gy = j / G; var c = new Color32((byte)(9 + j * 15), (byte)(240 - j * 11), (byte)(40 + j * 7), 255); for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++) px[(gy * 8 + y) * S + gx * 8 + x] = c; } t.SetPixels32(px); t.Apply(); Isolate(true); var sh = Shader.Find("Unlit/Texture"); var keep = new List(); for (int i = 0; i < s_rends.Length; i++) { if (s_rends[i].name == "Face") { s_rends[i].enabled = false; keep.Add(s_rends[i]); continue; } var m = new Material(sh); m.SetTexture("_MainTex", t); s_rends[i].sharedMaterials = Enumerable.Repeat(m, s_orig[i].Length).ToArray(); } Shoot(name, ortho); foreach (var r in keep) r.enabled = true; for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; Isolate(false); UnityEngine.Object.Destroy(t); // 팔레트 칸 색도 적어 둔다(인덱스 → 팔레트 RGB) var cols = pal.GetPixels32(); for (int j = 0; j < G * G; j++) { int gx = j % G, gy = j / G; var c = cols[(gy * 8 + 4) * S + gx * 8 + 4]; L(string.Format("PAL {0,2} id={1},{2},{3} rgb={4},{5},{6}", j, 9 + j * 15, 240 - j * 11, 40 + j * 7, c.r, c.g, c.b)); } } /// ToonP 머티리얼의 그림자색만 바꾼 임시 복제본으로 교체. static void SetShadow(Color shadow, float shades) { for (int i = 0; i < s_rends.Length; i++) { var mats = (Material[])s_orig[i].Clone(); for (int m = 0; m < mats.Length; m++) { if (mats[m] == null || mats[m].shader == null || mats[m].shader.name != "Shader Graphs/Toon") continue; var c = new Material(mats[m]) { name = "TMP_" + mats[m].name }; c.SetColor("_ShadowDiffuseColor", shadow); if (shades > 0) c.SetFloat("_Shades", shades); mats[m] = c; } s_rends[i].sharedMaterials = mats; } } static void ResetMats() { for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; } static void Look(float mood, float shadowTintAmount, int rim) { var cfg = WLReferenceLookSettings.Instance; var o = WLReferenceLook.Opt.FromSettings(cfg); o.mood = mood > 0f; o.moodStrength = mood; o.shadowTintAmount = shadowTintAmount; o.rim = rim != 0; WLReferenceLook.Apply(o, cfg); } public static string Run() { sb.Length = 0; try { Setup(); SaveMask("all", 0.95f, s_rends); foreach (var r in s_rends) SaveMask(r.name, 0.95f, r); SaveMask("all10", 10f, s_rends); foreach (var r in s_rends) SaveMask(r.name + "10", 10f, r); ShootIdMap("idmap", 0.95f); ShootIdMap("idmap10", 10f); var cfg = WLReferenceLookSettings.Instance; float mood0 = cfg.moodStrength, tint0 = cfg.shadowTintAmount; Action V = (name, shadow, shades, mood, tintAmt, rim) => { ResetMats(); WLReferenceLook.Restore(); if (shadow.a >= 0f && shadow != Color.clear) SetShadow(shadow, shades); Look(mood, tintAmt, rim); Shoot(name, 0.95f); Shoot(name + "_g", 10f); L("shot " + name + " shadow=" + shadow.ToString("F3") + " shades=" + shades + " mood=" + mood.ToString("F2") + " tintAmt=" + tintAmt.ToString("F2") + " · " + WLReferenceLook.LastLog); }; var purple = new Color(0.2164f, 0.2092f, 0.3713f, 0f); V("a_now", purple, 3f, mood0, tint0, 0); V("b_neutral", new Color(0.45f, 0.45f, 0.45f, 0f), 3f, mood0, tint0, 0); V("c_neutral_notint", new Color(0.45f, 0.45f, 0.45f, 0f), 3f, mood0, 0f, 0); V("d_neutral_notint_mood0", new Color(0.45f, 0.45f, 0.45f, 0f), 3f, 0f, 0f, 0); V("e_warm_notint", new Color(0.52f, 0.45f, 0.40f, 0f), 3f, mood0, 0f, 0); V("f_neutral_notint_s7", new Color(0.45f, 0.45f, 0.45f, 0f), 7f, mood0, 0f, 0); V("g_dark_notint", new Color(0.32f, 0.32f, 0.34f, 0f), 3f, mood0, 0f, 0); ResetMats(); WLReferenceLook.Restore(); Look(mood0, tint0, 0); L("DONE"); } catch (Exception e) { L("EXCEPTION " + e); } finally { try { Teardown(); } catch { } Directory.CreateDirectory("AgentScripts"); File.WriteAllText("AgentScripts/WL814x4_PLAY.txt", sb.ToString(), new UTF8Encoding(true)); } return sb.ToString(); } /// 아레나 씬을 연다(Play 진입 전 · 편집 모드에서 1회). public static string Open() { UnityEditor.SceneManagement.EditorSceneManager.OpenScene("Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); return "opened " + UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; } /// 무드(포스트 그레이딩·앰비언트·키라이트)가 화면 색을 얼마나 틀어놓는지 — 에셋 수정 0(런타임 프로파일 인스턴스). public static string Mood() { sb.Length = 0; try { Setup(); SaveMask("all", 0.95f, s_rends); foreach (var r in s_rends) SaveMask(r.name, 0.95f, r); SaveMask("all10", 10f, s_rends); ShootIdMap("idmap", 0.95f); var cfg = WLReferenceLookSettings.Instance; Action V = (name, mood, killWB, killSMH) => { WLReferenceLook.Restore(); var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false; o.mood = mood > 0f; o.moodStrength = mood; WLReferenceLook.Apply(o, cfg); var vgo = GameObject.Find("WL814t_ReferenceLook_Volume"); // killWB = 포스트 그레이딩 Volume 의 weight 만 0 으로 (조명 무드는 그대로 둔다) if (vgo != null && killWB) vgo.GetComponent().weight = 0f; Shoot(name, 0.95f); Shoot(name + "_g", 10f); L("mood shot " + name + " mood=" + mood.ToString("F2") + " killWB=" + killWB + " killSMH=" + killSMH); }; V("m30", 0.30f, false, false); V("m15", 0.15f, false, false); V("m00", 0.00f, false, false); V("n_novol30", 0.30f, true, false); V("n_novol15", 0.15f, true, false); WLReferenceLook.Restore(); var o2 = WLReferenceLook.Opt.FromSettings(cfg); o2.rim = false; WLReferenceLook.Apply(o2, cfg); L("DONE"); } catch (Exception e) { L("EXCEPTION " + e); } finally { try { Teardown(); } catch { } File.WriteAllText("AgentScripts/WL814x4_MOOD.txt", sb.ToString(), new UTF8Encoding(true)); } return sb.ToString(); } /// 🔴 캐릭터 전용 광원 색조 상쇄 — _DiffuseColor 에 휘도보존 역이득을 넣는다. /// 씬·조명·814t 설정·ProjectSettings 는 하나도 안 건드린다. public static string SetTint() { var g = new Color(0.994f, 1.024f, 0.894f, 1f); var sb2 = new StringBuilder(); foreach (var n in new[] { "M05_ToonP", "Skin_ToonP", "Hair05_ToonP" }) { var p = "Assets/WL/Look/Character/Materials/" + n + ".mat"; var m = UnityEditor.AssetDatabase.LoadAssetAtPath(p); if (m == null) { sb2.AppendLine("!! " + p); continue; } m.SetColor("_DiffuseColor", g); UnityEditor.EditorUtility.SetDirty(m); sb2.AppendLine(n + " _DiffuseColor = " + g.ToString("F3")); } UnityEditor.AssetDatabase.SaveAssets(); return sb2.ToString(); } /// 3차 — 앰비언트/키라이트 색조 3+1안. 🔴 SO·씬·에셋은 안 건드린다(런타임 RenderSettings·Light.color 만). public static string Amb() { sb.Length = 0; try { Setup(); SaveMask("all", 0.95f, s_rends); foreach (var r in s_rends) SaveMask(r.name, 0.95f, r); SaveMask("all10", 10f, s_rends); ShootIdMap("idmap", 0.95f); var cfg = WLReferenceLookSettings.Instance; var lights = UnityEngine.Object.FindObjectsByType(FindObjectsSortMode.None) .Where(l => l.type == LightType.Directional).ToArray(); // 측정은 «보정 없는» 상태에서 — _DiffuseColor 를 흰색으로 되돌린 임시 복제본 Action white = () => { for (int i = 0; i < s_rends.Length; i++) { var mats = (Material[])s_orig[i].Clone(); for (int m = 0; m < mats.Length; m++) { if (mats[m] == null || mats[m].shader == null || mats[m].shader.name != "Shader Graphs/Toon") continue; var c = new Material(mats[m]) { name = "TMP_w" }; c.SetColor("_DiffuseColor", Color.white); mats[m] = c; } s_rends[i].sharedMaterials = mats; } }; Action V = (name, amb, key) => { for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; WLReferenceLook.Restore(); var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false; WLReferenceLook.Apply(o, cfg); if (amb.a > 0f) RenderSettings.ambientLight = new Color(amb.r, amb.g, amb.b, 1f); if (key.a > 0f) foreach (var l in lights) l.color = new Color(key.r, key.g, key.b, 1f); white(); Shoot(name, 0.95f); Shoot(name + "_g", 10f); L(string.Format("amb shot {0} ambient={1} key={2}", name, RenderSettings.ambientLight.ToString("F3"), lights.Length > 0 ? lights[0].color.ToString("F3") : "-")); }; Color no = new Color(0, 0, 0, 0); // 현재 무드가 만드는 앰비언트 = lerp(원래, (0.28,0.17,0.40), 0.30) → 그대로 두면 v0 V("v0_purple", no, no); V("v1_gray", new Color(0.229f, 0.229f, 0.229f, 1f), no); V("v2_cool", new Color(0.205f, 0.228f, 0.262f, 1f), no); V("v3_warm", new Color(0.262f, 0.226f, 0.193f, 1f), no); V("v4_gray_keysoft", new Color(0.229f, 0.229f, 0.229f, 1f), new Color(1f, 0.95f, 0.90f, 1f)); V("v5_gray_keywhite", new Color(0.229f, 0.229f, 0.229f, 1f), new Color(1f, 1f, 1f, 1f)); // 배경 포함 넓은 구도(게임 실제 카메라 위치·직교 10) — 배경이 얼마나 바뀌는지 for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; Action WIDE = (name, amb, key) => { WLReferenceLook.Restore(); var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false; WLReferenceLook.Apply(o, cfg); if (amb.a > 0f) RenderSettings.ambientLight = new Color(amb.r, amb.g, amb.b, 1f); if (key.a > 0f) foreach (var l in lights) l.color = new Color(key.r, key.g, key.b, 1f); s_cam.orthographicSize = 10f; s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot); var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create(); var pv = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.Render(); s_cam.targetTexture = pv; var pa = RenderTexture.active; RenderTexture.active = rt; var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, W, H), 0, 0); t.Apply(); RenderTexture.active = pa; Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + name + ".png", t.EncodeToPNG()); UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt); L("wide " + name); }; WIDE("w0_purple", no, no); WIDE("w1_gray", new Color(0.229f, 0.229f, 0.229f, 1f), no); WIDE("w4_gray_keysoft", new Color(0.229f, 0.229f, 0.229f, 1f), new Color(1f, 0.95f, 0.90f, 1f)); WIDE("w5_gray_keywhite", new Color(0.229f, 0.229f, 0.229f, 1f), new Color(1f, 1f, 1f, 1f)); for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; WLReferenceLook.Restore(); var o2 = WLReferenceLook.Opt.FromSettings(cfg); o2.rim = false; WLReferenceLook.Apply(o2, cfg); L("DONE"); } catch (Exception e) { L("EXCEPTION " + e); } finally { try { Teardown(); } catch { } File.WriteAllText("AgentScripts/WL814x4_AMB.txt", sb.ToString(), new UTF8Encoding(true)); } return sb.ToString(); } /// 3차 채택값 쓰기 — 앰비언트 중성화(814t SO) + 캐릭터 _DiffuseColor 흰색 복귀. public static string Adopt() { var sb2 = new StringBuilder(); var rc = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/WL/Look/Arena/Resources/WL/WLReferenceLookSettings.asset"); sb2.AppendLine("ambientColor " + rc.ambientColor.ToString("F3") + " → 중성 (0.229,0.229,0.229)"); rc.ambientColor = new Color(0.229f, 0.229f, 0.229f, 1f); UnityEditor.EditorUtility.SetDirty(rc); foreach (var n in new[] { "M05_ToonP", "Skin_ToonP", "Hair05_ToonP" }) { var m = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/WL/Look/Character/Materials/" + n + ".mat"); var dc = new Color(0.993f, 1.028f, 0.874f, 1f); m.SetColor("_DiffuseColor", dc); UnityEditor.EditorUtility.SetDirty(m); sb2.AppendLine(n + " _DiffuseColor → " + dc.ToString("F3")); } UnityEditor.AssetDatabase.ImportAsset("Assets/WL/Look/Character/Textures/WLPalette_FINAL.png", UnityEditor.ImportAssetOptions.ForceUpdate); UnityEditor.AssetDatabase.SaveAssets(); return sb2.ToString(); } /// 최종 = 실제 배선된 머티리얼 그대로(보정 포함) 근접·게임크기·넓은 구도. public static string Final() { sb.Length = 0; try { Setup(); var cfg = WLReferenceLookSettings.Instance; L("ambient(SO)=" + cfg.ambientColor.ToString("F3") + " diffuse=" + s_rends[0].sharedMaterial.GetColor("_DiffuseColor").ToString("F3")); WLReferenceLook.Restore(); var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false; WLReferenceLook.Apply(o, cfg); Shoot("f_final", 0.95f); Shoot("f_final_g", 10f); s_cam.orthographicSize = 10f; s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot); var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create(); var pv = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.Render(); s_cam.targetTexture = pv; var pa = RenderTexture.active; RenderTexture.active = rt; var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, W, H), 0, 0); t.Apply(); RenderTexture.active = pa; Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + "f_wide.png", t.EncodeToPNG()); UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt); L("DONE"); } catch (Exception e) { L("EXCEPTION " + e); } finally { try { Teardown(); } catch { } File.WriteAllText("AgentScripts/WL814x4_FINAL.txt", sb.ToString(), new UTF8Encoding(true)); } return sb.ToString(); } /// 어두운 면(피부)의 잔여 색조 — 그림자 알베도 색을 바꿔 비교(에셋 무수정·임시 복제본). public static string Shad() { sb.Length = 0; try { Setup(); SaveMask("all", 0.95f, s_rends); foreach (var r in s_rends) SaveMask(r.name, 0.95f, r); ShootIdMap("idmap", 0.95f); var cfg = WLReferenceLookSettings.Instance; Action V = (name, sh) => { for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; WLReferenceLook.Restore(); if (sh.a > 0f) for (int i = 0; i < s_rends.Length; i++) { var mats = (Material[])s_orig[i].Clone(); for (int m = 0; m < mats.Length; m++) { if (mats[m] == null || mats[m].shader == null || mats[m].shader.name != "Shader Graphs/Toon") continue; var c = new Material(mats[m]) { name = "TMP_s" }; c.SetColor("_ShadowDiffuseColor", new Color(sh.r, sh.g, sh.b, 0f)); mats[m] = c; } s_rends[i].sharedMaterials = mats; } var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false; WLReferenceLook.Apply(o, cfg); Shoot(name, 0.95f); Shoot(name + "_g", 10f); L("shadow shot " + name + " = " + sh.ToString("F3")); }; V("s0_now", new Color(0, 0, 0, 0)); V("s1_gray", new Color(0.38f, 0.38f, 0.38f, 1f)); V("s2_warm", new Color(0.42f, 0.38f, 0.32f, 1f)); V("s3_graylight", new Color(0.50f, 0.50f, 0.50f, 1f)); V("s4_warmlight", new Color(0.55f, 0.50f, 0.42f, 1f)); for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; WLReferenceLook.Restore(); var o2 = WLReferenceLook.Opt.FromSettings(cfg); o2.rim = false; WLReferenceLook.Apply(o2, cfg); L("DONE"); } catch (Exception e) { L("EXCEPTION " + e); } finally { try { Teardown(); } catch { } File.WriteAllText("AgentScripts/WL814x4_SHAD.txt", sb.ToString(), new UTF8Encoding(true)); } return sb.ToString(); } /// 4차 — WL_ArenaMood 의 ShadowsMidtonesHighlights.shadows 를 런타임 프로파일 복제본에서 조정. /// 🔴 프로파일 에셋은 안 건드린다(Volume.profile 은 인스턴스 복제본). static void RestoreShadows() { var cfg = WLReferenceLookSettings.Instance; UnityEngine.Rendering.Universal.ShadowsMidtonesHighlights smh; if (s_shadSaved && cfg != null && cfg.profile != null && cfg.profile.asset != null && cfg.profile.asset.TryGet(out smh)) { smh.shadows.value = s_shad0; smh.shadows.overrideState = s_shadOv0; s_shadSaved = false; } } public static string Shadows() { sb.Length = 0; try { Setup(); SaveMask("all", 0.95f, s_rends); foreach (var r in s_rends) SaveMask(r.name, 0.95f, r); SaveMask("all10", 10f, s_rends); ShootIdMap("idmap", 0.95f); var cfg = WLReferenceLookSettings.Instance; Action white = () => { for (int i = 0; i < s_rends.Length; i++) { var mats = (Material[])s_orig[i].Clone(); for (int m = 0; m < mats.Length; m++) { if (mats[m] == null || mats[m].shader == null || mats[m].shader.name != "Shader Graphs/Toon") continue; var c = new Material(mats[m]) { name = "TMP_w" }; c.SetColor("_DiffuseColor", Color.white); mats[m] = c; } s_rends[i].sharedMaterials = mats; } }; Action V = (name, sh, useWhite) => { for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; WLReferenceLook.Restore(); var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false; WLReferenceLook.Apply(o, cfg); string applied = "-"; // 🔴 Volume 오버라이드(우선순위/인스턴스)는 둘 다 실패했다(전자는 안 먹고, 후자는 프로파일 전체를 // weight 1 로 얹어 버린다). → 프로파일 «에셋의 값»을 직접 바꾸고 끝에 되돌린다(SaveAssets 0). UnityEngine.Rendering.Universal.ShadowsMidtonesHighlights smh; if (cfg.profile != null && cfg.profile.asset != null && cfg.profile.asset.TryGet(out smh)) { if (!s_shadSaved) { s_shad0 = smh.shadows.value; s_shadOv0 = smh.shadows.overrideState; s_shadSaved = true; } smh.shadows.overrideState = true; smh.shadows.value = (sh.w > -900f) ? sh : s_shad0; applied = smh.shadows.value.ToString("F2"); } if (useWhite) white(); Shoot(name, 0.95f); Shoot(name + "_g", 10f); // 넓은 구도(배경 확인) s_cam.orthographicSize = 10f; s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot); var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create(); var pv = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.Render(); s_cam.targetTexture = pv; var pa = RenderTexture.active; RenderTexture.active = rt; var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, W, H), 0, 0); t.Apply(); RenderTexture.active = pa; Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + "wide_" + name + ".png", t.EncodeToPNG()); UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt); L("shadows " + name + " = " + applied); }; var none = new Vector4(0, 0, 0, -1000f); V("a_now", none, true); // 지금 (0.78,0.58,1.25) V("b_g075", new Vector4(0.78f, 0.75f, 1.25f, -0.06f), true); V("c_g085", new Vector4(0.78f, 0.85f, 1.25f, -0.06f), true); V("d_g095", new Vector4(0.78f, 0.95f, 1.25f, -0.06f), true); V("e_g085b110", new Vector4(0.82f, 0.85f, 1.10f, -0.06f), true); V("f_g095b100", new Vector4(0.85f, 0.95f, 1.00f, -0.06f), true); V("g_neutral", new Vector4(0.92f, 0.92f, 1.00f, -0.06f), true); V("z_sanity", new Vector4(0.78f, 1.80f, 1.25f, -0.06f), true); // 오버라이드가 먹는지 확인용(극단값) for (int i = 0; i < s_rends.Length; i++) s_rends[i].sharedMaterials = s_orig[i]; WLReferenceLook.Restore(); var o2 = WLReferenceLook.Opt.FromSettings(cfg); o2.rim = false; WLReferenceLook.Apply(o2, cfg); L("DONE"); } catch (Exception e) { L("EXCEPTION " + e); } finally { try { RestoreShadows(); Teardown(); } catch { } File.WriteAllText("AgentScripts/WL814x4_SHADOWS.txt", sb.ToString(), new UTF8Encoding(true)); } return sb.ToString(); } /// 4차 채택 — 부위 그룹별 _DiffuseColor 역이득(전역 1개로는 피부를 못 맞춘다). public static string Adopt4() { var sb2 = new StringBuilder(); var g = new System.Collections.Generic.Dictionary { { "Skin_ToonP", new Color(0.993f, 1.028f, 0.874f, 1f) }, { "M05_ToonP", new Color(0.993f, 1.028f, 0.874f, 1f) }, { "Hair05_ToonP", new Color(0.993f, 1.028f, 0.874f, 1f) }, }; foreach (var kv in g) { var m = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/WL/Look/Character/Materials/" + kv.Key + ".mat"); m.SetColor("_DiffuseColor", kv.Value); UnityEditor.EditorUtility.SetDirty(m); sb2.AppendLine(kv.Key + " _DiffuseColor = " + kv.Value.ToString("F3")); } UnityEditor.AssetDatabase.SaveAssets(); return sb2.ToString(); } /// 최종 촬영 — 실제 배선 그대로(근접 · 게임 크기 · 넓은 구도). public static string Final4() { sb.Length = 0; try { Setup(); SaveMask("all", 0.95f, s_rends); foreach (var r in s_rends) SaveMask(r.name, 0.95f, r); SaveMask("all10", 10f, s_rends); ShootIdMap("idmap", 0.95f); var cfg = WLReferenceLookSettings.Instance; WLReferenceLook.Restore(); var o = WLReferenceLook.Opt.FromSettings(cfg); o.rim = false; WLReferenceLook.Apply(o, cfg); L("diffuse Skin=" + s_rends.First(r => r.name == "Head").sharedMaterial.GetColor("_DiffuseColor").ToString("F3") + " M05=" + s_rends.First(r => r.name == "Body_m05").sharedMaterial.GetColor("_DiffuseColor").ToString("F3") + " Hair=" + s_rends.First(r => r.name == "Hair05").sharedMaterial.GetColor("_DiffuseColor").ToString("F3")); Shoot("fin", 0.95f); Shoot("fin_g", 10f); s_cam.orthographicSize = 10f; s_cam.transform.SetPositionAndRotation(s_camPos, s_camRot); var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); rt.antiAliasing = 1; rt.filterMode = FilterMode.Point; rt.Create(); var pv = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.Render(); s_cam.targetTexture = pv; var pa = RenderTexture.active; RenderTexture.active = rt; var t = new Texture2D(W, H, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, W, H), 0, 0); t.Apply(); RenderTexture.active = pa; Directory.CreateDirectory(RAW); File.WriteAllBytes(RAW + "fin_wide.png", t.EncodeToPNG()); UnityEngine.Object.Destroy(t); rt.Release(); UnityEngine.Object.Destroy(rt); L("DONE"); } catch (Exception e) { L("EXCEPTION " + e); } finally { try { Teardown(); } catch { } File.WriteAllText("AgentScripts/WL814x4_FINAL.txt", sb.ToString(), new UTF8Encoding(true)); } return sb.ToString(); } }