// 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()); } }