// WL-814t 캡처 — 레퍼런스 느낌 4요소 단독/단계/종합 (Play 중 한 프레임 안에서 전부 렌더) // 한 프레임 안에서 찍는 이유: 구름 그림자(_Time)·포즈가 프레임마다 움직여 세션 간 비교가 노이즈투성이가 된다(814s 미확인⑤). // run_script --file AgentScripts/WL814t_Capture.cs --entry WL814t_Capture.Run using System.Collections.Generic; using UnityEngine; using WL.Look.Arena; public static class WL814t_Capture { const string Dir = @"E:\NerdNavis\WL_wt\gameplay\Screenshots_WL\WL814t"; const int W = 1080, H = 1920; const float Close = 3.4f; // 도트A 프리셋과 같은 직교 반높이 = 비교 기준 const float Wide = 10f; // 아레나 씬의 실제 카메라 static Camera s_cam; static Vector3 s_camPos, s_charCenter; static float s_camSize; static System.Text.StringBuilder s_log; public static string Run() { s_log = new System.Text.StringBuilder(); System.IO.Directory.CreateDirectory(Dir); s_cam = Camera.main; if (s_cam == null) { var cs = Object.FindObjectsByType(FindObjectsSortMode.None); if (cs.Length > 0) s_cam = cs[0]; } if (s_cam == null) return "no camera"; s_camPos = s_cam.transform.position; s_camSize = s_cam.orthographicSize; var pc = GameObject.Find("PC_LH_M05"); var rs = pc != null ? pc.GetComponentsInChildren(false) : new Renderer[0]; Bounds b = new Bounds(pc != null ? pc.transform.position : Vector3.zero, Vector3.zero); bool first = true; foreach (var r in rs) { if (r == null || !r.enabled) continue; if (first) { b = r.bounds; first = false; } else b.Encapsulate(r.bounds); } s_charCenter = b.center; s_log.AppendLine("charCenter=" + s_charCenter.ToString("F3") + " bodyH=" + b.size.y.ToString("F3") + " · 화면 캐릭터 키 @ortho3.4 = " + (b.size.y / (2f * Close) * H).ToString("F0") + " px · @ortho10 = " + (b.size.y / (2f * Wide) * H).ToString("F0") + " px"); var cfg = WLReferenceLookSettings.Instance; if (cfg == null) return "no settings asset"; // ── ⓐ 지금(814s) — 아무것도 안 얹은 상태 Shot2("a_now", Nothing(cfg), Close, 0); Shot2("a_now_wide", Nothing(cfg), Wide, 0); Shot2("a_now_pixel", Nothing(cfg), Close, 240); // ── 4요소 단독 Shot2("b_outline_only", Only(cfg, true, false, false, false), Close, 0); Shot2("c_contrast_only", Only(cfg, false, true, false, false), Close, 0); Shot2("d_mood_only", Only(cfg, false, false, true, false), Close, 0); Shot2("e_rim_only", Only(cfg, false, false, false, true), Close, 0); // ── Ⓐ 외곽선 4단 — 두께는 **아레나 실제 카메라(직교 10)** 기준 px (814u 실측 기준과 같은 축) float[] ws = { 1f, 2f, 3f, 4f }; for (int i = 0; i < ws.Length; i++) { var o = Only(cfg, true, false, false, false); o.outlineWidth = ws[i]; o.outlineWidthBackground = Mathf.Max(1f, ws[i] * 2.5f); Shot2("o" + (i + 1) + "_outline_" + ws[i].ToString("F0") + "px", o, Close, 0); } // 외곽선 색 — 균일 검정 vs 표면색 연동(레퍼런스는 부위마다 선 색이 다르다) { var o = Only(cfg, true, false, false, false); o.outlineWidth = 1f; o.outlineColor = Color.black; o.outlineTintAmount = 0f; Shot2("o5_color_uniformblack", o, Close, 0); o.outlineTintAmount = 1f; Shot2("o6_color_surfacetint", o, Close, 0); o.outlineColor = new Color(0.043f, 0.031f, 0.067f, 1f); o.outlineTintAmount = 0f; Shot2("o7_color_darkviolet", o, Close, 0); } // ── Ⓐ 면적 실측용 (직교 10 = 인게임 크기 · 외곽선을 마젠타로 칠해 픽셀을 센다) MeasureOutlineArea(cfg, pc); // ── Ⓑ 대비 3단 float[] mds = { 0.10f, 0.05f, 0.0f }; for (int i = 0; i < mds.Length; i++) { var o = Only(cfg, false, true, false, false); o.minimumDarkness = mds[i]; o.charShades = 0; Shot2("c" + (i + 1) + "_darkness_" + mds[i].ToString("F2"), o, Close, 0); } // ── Ⓑ2 캐릭터 색단계 7 / 4 / 3 (레퍼런스2 = 부위당 2~3단) int[] shades = { 0, 4, 3 }; for (int i = 0; i < shades.Length; i++) { var o = Only(cfg, false, true, false, false); o.charShades = shades[i]; Shot2("s" + (i + 1) + "_shades_" + (shades[i] == 0 ? 7 : shades[i]), o, Close, 0); } // ── Ⓒ 분위기 3단 float[] ms = { 0.35f, 0.65f, 1.0f }; for (int i = 0; i < ms.Length; i++) { var o = Only(cfg, false, false, true, false); o.moodStrength = ms[i]; Shot2("m" + (i + 1) + "_mood_" + ms[i].ToString("F2"), o, Close, 0); } // ── Ⓓ 림 3단 float[] rss = { 0.6f, 1.25f, 2.2f }; for (int i = 0; i < rss.Length; i++) { var o = Only(cfg, false, false, false, true); o.rimStrength = rss[i]; Shot2("r" + (i + 1) + "_rim_" + rss[i].ToString("F2"), o, Close, 0); } // ── ⓕ 종합안 (비픽셀) var fin = WLReferenceLook.Opt.FromSettings(cfg); Shot2("f_final", fin, Close, 0); Shot2("f_final_wide", fin, Wide, 0); // ── ⓕ2 종합안 + 도트 — 도트A 프리셋(orthoSize 3.4 · pixelHeight 240) var fp = fin; fp.outlineWidth = cfg.outlineWidthPixelMode; fp.outlineWidthBackground = Mathf.Max(0.5f, cfg.outlineWidthPixelMode * 0.5f); Shot2("f_final_pixel", fp, Close, 240); var fp2 = fp; fp2.outlineWidth = cfg.outlineWidthPixelMode * 2f; fp2.outlineWidthBackground = cfg.outlineWidthPixelMode; Shot2("f_final_pixel_w2", fp2, Close, 240); Shot2("f_final_pixel_thick", fin, Close, 240); // 비픽셀 두께 그대로 → 저해상도에서 뭉개지는지 // ── 원복 확인 Shot2("z_restored_check", Nothing(cfg), Close, 0); s_cam.transform.position = s_camPos; s_cam.orthographicSize = s_camSize; s_log.AppendLine("DONE"); return s_log.ToString(); } /// 외곽선 띠가 캐릭터 면적의 몇 %를 먹는지 재기 위한 렌더 묶음(파이썬이 픽셀을 센다). static void MeasureOutlineArea(WLReferenceLookSettings cfg, GameObject pc) { var rs = pc != null ? pc.GetComponentsInChildren(true) : new Renderer[0]; var on = new List(); foreach (var r in rs) if (r != null && r.enabled) on.Add(r); // 배경만(캐릭터 끔) WLReferenceLook.Restore(); foreach (var r in on) r.enabled = false; s_cam.orthographicSize = Wide; s_cam.transform.position = s_charCenter - s_cam.transform.forward * 30f; Shot("mx_bg", 0); foreach (var r in on) r.enabled = true; // 외곽선 없음 Shot2("mx_w0", Nothing(cfg), Wide, 0); // 마젠타 외곽선 1~4 px (배경엔 안 넣는다 = 캐릭터만 센다) float[] ws = { 1f, 2f, 3f, 4f }; for (int i = 0; i < ws.Length; i++) { var o = Only(cfg, true, false, false, false); o.outlineWidth = ws[i]; o.outlineOnBackground = false; o.outlineColor = new Color(1f, 0f, 1f, 1f); o.outlineTintAmount = 0f; Shot2("mx_w" + ws[i].ToString("F0"), o, Wide, 0); } } static WLReferenceLook.Opt Nothing(WLReferenceLookSettings cfg) { var o = WLReferenceLook.Opt.FromSettings(cfg); o.outline = false; o.contrast = false; o.mood = false; o.rim = false; return o; } static WLReferenceLook.Opt Only(WLReferenceLookSettings cfg, bool a, bool b, bool c, bool d) { var o = WLReferenceLook.Opt.FromSettings(cfg); o.outline = a; o.contrast = b; o.mood = c; o.rim = d; return o; } /// 카메라를 먼저 맞춘 뒤(줌 비례 두께가 카메라를 읽는다) 적용 → 렌더. static void Shot2(string name, WLReferenceLook.Opt o, float orthoSize, int pixelHeight) { s_cam.orthographicSize = orthoSize; s_cam.transform.position = s_charCenter - s_cam.transform.forward * 30f; WLReferenceLook.Apply(o, WLReferenceLookSettings.Instance); Shot(name, pixelHeight); } /// 한 장 렌더. pixelHeight 0 = 1080×1920 그대로, >0 = 저해상도 RT 로 찍어 점보간 확대(도트). static void Shot(string name, int pixelHeight) { int rw = W, rh = H; if (pixelHeight > 0) { rh = pixelHeight; rw = Mathf.RoundToInt(pixelHeight * (float)W / H); } var rt = new RenderTexture(rw, rh, 24, RenderTextureFormat.ARGB32); rt.antiAliasing = 1; rt.filterMode = pixelHeight > 0 ? FilterMode.Point : FilterMode.Bilinear; rt.Create(); var prevTarget = s_cam.targetTexture; s_cam.targetTexture = rt; s_cam.Render(); s_cam.targetTexture = prevTarget; var prevActive = RenderTexture.active; RenderTexture.active = rt; var src = new Texture2D(rw, rh, TextureFormat.RGB24, false); src.ReadPixels(new Rect(0, 0, rw, rh), 0, 0); src.Apply(); RenderTexture.active = prevActive; Texture2D outTex = src; if (pixelHeight > 0) { outTex = new Texture2D(W, H, TextureFormat.RGB24, false); var sp = src.GetPixels32(); var dp = new Color32[W * H]; for (int y = 0; y < H; y++) { int sy = Mathf.Min(rh - 1, y * rh / H); for (int x = 0; x < W; x++) { int sx = Mathf.Min(rw - 1, x * rw / W); dp[y * W + x] = sp[sy * rw + sx]; } } outTex.SetPixels32(dp); outTex.Apply(); } System.IO.File.WriteAllBytes(System.IO.Path.Combine(Dir, name + ".png"), outTex.EncodeToPNG()); s_log.AppendLine("shot " + name + " " + rw + "x" + rh + " · ortho " + s_cam.orthographicSize.ToString("F2") + " · " + WLReferenceLook.LastLog); if (outTex != src) Object.Destroy(outTex); Object.Destroy(src); rt.Release(); Object.Destroy(rt); } }