From 09e067af023c19047da477f75892f4a3cdba7651 Mon Sep 17 00:00:00 2001 From: swrring Date: Tue, 15 Sep 2026 09:59:49 +0900 Subject: [PATCH] =?UTF-8?q?[WL-816r]=20=ED=9B=84=EC=86=8D=20A/B=20?= =?UTF-8?q?=EC=8B=A4=EC=B8=A1=20=E2=80=94=20=ED=92=80=20=ED=9A=8C=EA=B7=80?= =?UTF-8?q?=20=EC=97=86=EC=9D=8C(816q=2012.44%=20vs=20816r=2010.69%)=20?= =?UTF-8?q?=C2=B7=20=EA=B3=B5=EC=A0=95=20=EC=BA=A1=EC=B2=98=20=EC=9E=AC?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#816)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 같은 PD 구도·같은 구름 위상·풀 인스턴스 1155 동일에서 816q(원본 mat/텍스처) vs 816r(Arena2) A/B - 보이는 풀 12.44% -> 10.69% · 에지% 1.65 -> 1.69(노이즈) · HF 1.58 -> 1.43 · 바닥초록 #89B074 -> #89B375 - 원인 후보 전부 실측 기각: 머티리얼 값 차이 0건 · 텍스처 임포트 동일(8x8/RGB24/Point/mip1) · 풀 인스턴스 동일 - 기존 a_demo_vs_island.png 이 서로 다른 프레임(구름 위상)을 나란히 붙여 불공정했던 것 -> 같은 프레임쌍으로 재작성 Co-Authored-By: Claude Opus 5 (1M context) --- AgentScripts/WL816r_Ab.cs | 217 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 AgentScripts/WL816r_Ab.cs diff --git a/AgentScripts/WL816r_Ab.cs b/AgentScripts/WL816r_Ab.cs new file mode 100644 index 000000000..d7cfcfa3a --- /dev/null +++ b/AgentScripts/WL816r_Ab.cs @@ -0,0 +1,217 @@ +// WL-816r 후속 — 816q 상태(원본 mat/텍스처) vs 816r 상태(Arena2) 를 **같은 조건**에서 A/B. +// 지표 = 816q 정의: 보이는 풀 %(풀 ON vs OFF 픽셀 차) · 에지 % · HF · 바닥 초록 화면색. +public static class WL816r_Ab +{ + public static void Start() + { + var go = UnityEngine.GameObject.Find("~WL816rAb"); + if (go != null) UnityEngine.Object.DestroyImmediate(go); + go = new UnityEngine.GameObject("~WL816rAb"); + go.AddComponent(); + } +} + +public class WL816r_AbRun : UnityEngine.MonoBehaviour +{ + static System.Text.StringBuilder sb; + const int W = 1080, H = 1920; + static string Dir = "Screenshots_WL/WL816r"; + + static int s_instQ, s_instR; + void Start() { StartCoroutine(Co()); } + + System.Collections.IEnumerator Co() + { + sb = new System.Text.StringBuilder(); + System.IO.Directory.CreateDirectory(Dir); + if (UnityEngine.SceneManagement.SceneManager.sceneCount < 2) + { + var op = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Level01", UnityEngine.SceneManagement.LoadSceneMode.Additive); + while (op != null && !op.isDone) yield return null; + } + for (int i = 0; i < 7; i++) yield return new UnityEngine.WaitForSeconds(1f); + + var g = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); + if (g == null) { L("WLIslandGrass 없음 — 중단"); Flush(); yield break; } + + var src = WL.Look.Farm.WLIslandLookSettings.Instance; + var rt = UnityEngine.Object.Instantiate(src); // 에셋 무변경 — 런타임 복제본 + if (rt.scatter != null) + { + var arr = new WL.Look.Farm.WLScatterDef[rt.scatter.Length]; + for (int i = 0; i < arr.Length; i++) + { + var s = rt.scatter[i]; + arr[i] = new WL.Look.Farm.WLScatterDef { enabled_ = s.enabled_, label = s.label, mesh = s.mesh, material = s.material, probability = s.probability, scale = s.scale, normalOffset = s.normalOffset, density = s.density }; + } + rt.scatter = arr; + } + g.cfg = rt; + + var matR = src.islandTopMaterial; // 816r (Arena2) + var matQ = UnityEditor.AssetDatabase.LoadAssetAtPath( + "Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena.mat"); // 816q (원본) + L("816r mat=" + (matR ? matR.name : "-") + " tex=" + TexName(matR) + " | 816q mat=" + (matQ ? matQ.name : "-") + " tex=" + TexName(matQ)); + if (matQ == null) { L("원본 머티리얼 로드 실패 — 중단"); Flush(); yield break; } + L("머티리얼 값 대조 " + Cmp(matQ, matR)); + + var topR = new System.Collections.Generic.List(); + foreach (var r in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None)) + foreach (var m in r.sharedMaterials) if (m != null && m == matR) { topR.Add(r); break; } + L("섬윗면 렌더러=" + topR.Count); + + var cam = MakePd(); + + // ── A: 816q 상태 ────────────────────────────────────────────────── + SwapMat(topR, matR, matQ); + yield return null; yield return null; + rt.grassEnabled = 1; g.Rebuild(); yield return null; yield return null; yield return null; + var qOn = Shot(cam); s_instQ = WL.Look.Farm.WLIslandGrass.Instances; + rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null; yield return null; + var qOff = Shot(cam); + L("[816q] " + Metrics(qOn, qOff) + " · 풀 인스턴스(ON시)=" + s_instQ); + System.IO.File.WriteAllBytes(Dir + "/ab_816q.png", UnityEngine.ImageConversion.EncodeToPNG(ToTex(qOn))); + + // ── B: 816r 상태 ────────────────────────────────────────────────── + SwapMat(topR, matQ, matR); + yield return null; yield return null; + rt.grassEnabled = 1; g.Rebuild(); yield return null; yield return null; yield return null; + var rOn = Shot(cam); s_instR = WL.Look.Farm.WLIslandGrass.Instances; + rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null; yield return null; + var rOff = Shot(cam); + L("[816r] " + Metrics(rOn, rOff) + " · 풀 인스턴스(ON시)=" + s_instR); + System.IO.File.WriteAllBytes(Dir + "/ab_816r.png", UnityEngine.ImageConversion.EncodeToPNG(ToTex(rOn))); + + // 풀 다시 켜고 마무리 + rt.grassEnabled = 1; g.Rebuild(); + Flush(); + } + + UnityEngine.Color32[] Shot(UnityEngine.Camera cam) + { + var tex = Grab(cam, W, H); + var px = tex.GetPixels32(); + UnityEngine.Object.DestroyImmediate(tex); + return px; + } + static UnityEngine.Texture2D ToTex(UnityEngine.Color32[] px) + { + var t = new UnityEngine.Texture2D(W, H, UnityEngine.TextureFormat.RGB24, false); + t.SetPixels32(px); t.Apply(); + return t; + } + + // 816q 지표 + static bool Ground(UnityEngine.Color32 p) { return p.g > p.b + 8 && p.g > 45; } + static float Lum(UnityEngine.Color32 p) { return 0.299f * p.r + 0.587f * p.g + 0.114f * p.b; } + + static string Metrics(UnityEngine.Color32[] on, UnityEngine.Color32[] off) + { + int gn = 0, vis = 0; + long rr = 0, gg = 0, bb = 0; int cn = 0; + for (int i = 0; i < off.Length; i++) + { + if (!Ground(off[i])) continue; + gn++; + rr += off[i].r; gg += off[i].g; bb += off[i].b; cn++; + if (System.Math.Abs(Lum(on[i]) - Lum(off[i])) > 8f) vis++; + } + // 에지 % (풀 ON · 지면 픽셀) + int ed = 0, edn = 0; + double hf = 0; int hfn = 0; + for (int y = 0; y < H - 1; y++) + for (int x = 0; x < W - 1; x++) + { + int i = y * W + x; + if (!Ground(on[i])) continue; + edn++; + var r0 = on[i + 1]; var d0 = on[i + W]; + if ((Ground(r0) && System.Math.Abs(Lum(on[i]) - Lum(r0)) > 8) || (Ground(d0) && System.Math.Abs(Lum(on[i]) - Lum(d0)) > 8)) ed++; + } + // HF = 8×8 블록 내 편차 평균 + int bs = 8; + for (int by = 0; by + bs <= H; by += bs) + for (int bx = 0; bx + bs <= W; bx += bs) + { + double s = 0; int n = 0; + for (int y = 0; y < bs; y++) for (int x = 0; x < bs; x++) + { var p = on[(by + y) * W + bx + x]; if (!Ground(p)) continue; s += Lum(p); n++; } + if (n < bs * bs / 2) continue; + double mean = s / n; + for (int y = 0; y < bs; y++) for (int x = 0; x < bs; x++) + { var p = on[(by + y) * W + bx + x]; if (!Ground(p)) continue; hf += System.Math.Abs(Lum(p) - mean); hfn++; } + } + string col = cn == 0 ? "-" : string.Format("#{0:X2}{1:X2}{2:X2}", rr / cn, gg / cn, bb / cn); + return string.Format("지면px(풀off)={0} · 보이는 풀={1:F2}% · 에지%={2:F2} · HF={3:F2} · 바닥초록={4}", + gn, gn == 0 ? 0f : 100f * vis / gn, edn == 0 ? 0f : 100f * ed / edn, hfn == 0 ? 0 : hf / hfn, col); + } + + static UnityEngine.Material FindMat(string name) + { + foreach (var m in UnityEngine.Resources.FindObjectsOfTypeAll()) + if (m != null && m.name == name) return m; + return null; + } + static string TexName(UnityEngine.Material m) + { + if (m == null) return "-"; + var t = m.GetTexture("_BaseMap"); + return t ? t.name : "-"; + } + static string Cmp(UnityEngine.Material a, UnityEngine.Material b) + { + string[] fs = { "_Shades", "_Brightness", "_MinimumDarkness", "_Cloud_Strength", "_Cloud_Density", "_Cloud_Cover", "_Cloud_Change" }; + string[] cs = { "_DiffuseColor", "_ShadowDiffuseColor" }; + var s = new System.Text.StringBuilder(); + int diff = 0; + foreach (var f in fs) { float x = a.GetFloat(f), y = b.GetFloat(f); if (UnityEngine.Mathf.Abs(x - y) > 0.0001f) { diff++; s.Append(" " + f + " " + x + "→" + y); } } + foreach (var c in cs) { var x = a.GetColor(c); var y = b.GetColor(c); if (x != y) { diff++; s.Append(" " + c + " " + x + "→" + y); } } + var ka = string.Join(",", a.shaderKeywords); var kb = string.Join(",", b.shaderKeywords); + if (ka != kb) { diff++; s.Append(" kw [" + ka + "]→[" + kb + "]"); } + // 텍스처 임포트 성질 + var ta = a.GetTexture("_BaseMap") as UnityEngine.Texture2D; + var tb = b.GetTexture("_BaseMap") as UnityEngine.Texture2D; + if (ta != null && tb != null) + s.Append(" | tex A " + ta.width + "x" + ta.height + "/" + ta.format + "/" + ta.filterMode + "/mip" + ta.mipmapCount + + " B " + tb.width + "x" + tb.height + "/" + tb.format + "/" + tb.filterMode + "/mip" + tb.mipmapCount); + return (diff == 0 ? "값 차이 0" : "차이 " + diff + "건:") + s; + } + + static void SwapMat(System.Collections.Generic.List rs, UnityEngine.Material from, UnityEngine.Material to) + { + foreach (var r in rs) + { + var ms = r.sharedMaterials; bool hit = false; + for (int i = 0; i < ms.Length; i++) if (ms[i] == from) { ms[i] = to; hit = true; } + if (hit) r.sharedMaterials = ms; + } + } + static UnityEngine.Camera MakePd() + { + UnityEngine.Vector3 tgt = UnityEngine.Vector3.zero; + var pc = UnityEngine.GameObject.FindGameObjectWithTag("Player"); if (pc != null) tgt = pc.transform.position; + var go = UnityEngine.GameObject.Find("~816rAbPd"); if (go == null) go = new UnityEngine.GameObject("~816rAbPd"); + var c = go.GetComponent(); if (c == null) c = go.AddComponent(); + c.orthographic = false; c.fieldOfView = 60f; c.nearClipPlane = 0.1f; c.farClipPlane = 300f; + c.transform.rotation = UnityEngine.Quaternion.Euler(45f, 45f, 0f); + c.transform.position = tgt - c.transform.forward * 12f; + c.enabled = false; return c; + } + static UnityEngine.Texture2D Grab(UnityEngine.Camera cam, int w, int h) + { + var rt = new UnityEngine.RenderTexture(w, h, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.Create(); cam.targetTexture = rt; cam.Render(); + var prev = UnityEngine.RenderTexture.active; UnityEngine.RenderTexture.active = rt; + var tex = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false); + tex.ReadPixels(new UnityEngine.Rect(0, 0, w, h), 0, 0); tex.Apply(); + UnityEngine.RenderTexture.active = prev; cam.targetTexture = null; + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + return tex; + } + static void L(string s) { sb.AppendLine(s); Flush(); } + static void Flush() + { + System.IO.File.WriteAllText("AgentScripts/WL816r_AB.txt", sb.ToString()); + UnityEngine.Debug.Log("[816r ab]\n" + sb.ToString()); + } +}