// WL-816q — 「그라데이션(구름 띠)이 왜 안 보이나」 요소별 ON/OFF 실측. // 에셋 무변경(SO·머티리얼은 전부 런타임 복제본). Debug.LogError 금지. public static class WL816q_Play { public static void Start() { var go = UnityEngine.GameObject.Find("~WL816qPlay"); if (go != null) UnityEngine.Object.DestroyImmediate(go); go = new UnityEngine.GameObject("~WL816qPlay"); go.AddComponent(); } } public class WL816q_PlayRunner : UnityEngine.MonoBehaviour { static System.Text.StringBuilder sb; const int W = 1080, H = 1920; const int TS = 768; static string Dir = "Screenshots_WL/WL816q"; 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); // scatter 정의를 새 인스턴스로 깊은 복사 — 에셋 쪽 객체를 절대 만지지 않는다 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; // ── 요소 진단 ──────────────────────────────────────────────── L("=== 요소 진단 === t=" + UnityEngine.Time.timeSinceLevelLoad); L("ⓑ WLReferenceLook.IsApplied=" + WL.Look.Arena.WLReferenceLook.IsApplied + " · Outlined=" + WL.Look.Arena.WLReferenceLook.OutlinedRenderers + " · Contrast=" + WL.Look.Arena.WLReferenceLook.ContrastMaterials); int hull = 0, toonR = 0; var rends = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None); for (int i = 0; i < rends.Length; i++) { var ms = rends[i].sharedMaterials; for (int m = 0; m < ms.Length; m++) { if (ms[m] == null || ms[m].shader == null) continue; if (ms[m].shader.name == "WL/HullOutline") hull++; if (ms[m].shader.name.Contains("Toon")) toonR++; } } L("ⓑ 씬 안 HullOutline 머티리얼 슬롯=" + hull + " · Toon 슬롯=" + toonR); var vols = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None); L("ⓓ Volume 개수=" + vols.Length + (vols.Length > 0 ? " 첫 프로필=" + (vols[0].sharedProfile ? vols[0].sharedProfile.name : "-") : "")); var sun = UnityEngine.RenderSettings.sun; var lights = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None); for (int i = 0; i < lights.Length; i++) if (lights[i].type == UnityEngine.LightType.Directional) L("ⓐ Directional " + lights[i].name + " shadows=" + lights[i].shadows + " int=" + lights[i].intensity + " rot=" + lights[i].transform.eulerAngles); var top = rt.islandTopMaterial; var grassMat = (rt.scatter != null && rt.scatter.Length > 0) ? rt.scatter[0].material : null; L("ⓒⓔ 바닥=" + (top ? top.name : "-") + " sh=" + (top && top.shader ? top.shader.name : "-") + " " + MatDump(top)); L("ⓒⓔ 풀 =" + (grassMat ? grassMat.name : "-") + " sh=" + (grassMat && grassMat.shader ? grassMat.shader.name : "-") + " " + MatDump(grassMat)); L("ⓔ 그림자비(Shadow/Diffuse) 바닥=" + RatioStr(top) + " · 풀=" + RatioStr(grassMat)); var b = g.CalculateInstancesBounds(); L("섬 바운즈 center=" + b.center + " size=" + b.size); var camTop = MakeTop(b.center.x, b.center.z, 6f); var camPd = MakePd(); L("탑다운 = ortho6 @(" + b.center.x.ToString("F2") + ",60," + b.center.z.ToString("F2") + ") · PD = 12m/45°/fov60"); // ── A: 풀 끔 (바닥만) ─────────────────────────────────────── rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null; L("[A 바닥만] " + Shot(camTop, TS, TS, "A_top_groundonly") + " | " + Stat()); Shot(camPd, W, H, "A_pd_groundonly"); // ── B: 현재 풀 (816n 상태) ────────────────────────────────── rt.grassEnabled = 1; g.Rebuild(); yield return null; yield return null; L("[B 현재풀] " + Shot(camTop, TS, TS, "B_top_before") + " | " + Stat()); Shot(camPd, W, H, "B_pd_before"); // ── C: 풀 그림자색만 데모 비율로 (다른 값 전부 동일) ──────── if (grassMat != null) { var clone = new UnityEngine.Material(grassMat); clone.name = grassMat.name + "_816qTest"; var dif = clone.GetColor("_DiffuseColor"); clone.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(dif.r * 0.3232f, dif.g * 0.3414f, dif.b * 0.3948f, dif.a)); for (int i = 0; i < rt.scatter.Length; i++) if (rt.scatter[i].material == grassMat) rt.scatter[i].material = clone; g.Rebuild(); yield return null; yield return null; L("[C 풀그림자=데모비율] " + Shot(camTop, TS, TS, "C_top_shadowfix") + " | " + Stat()); Shot(camPd, W, H, "C_pd_shadowfix"); } // ── D: 참고 — 데모 기준 캡처에 같은 지표 ──────────────────── Ref("Screenshots_WL/WL816f/x_demo_ground.png", "D 데모참고(스케일 미상)"); Ref("Screenshots_WL/WL816f/x_ours_ground.png", "D 우리참고(스케일 미상)"); Flush(); } // ─────────────────────────────────────────── 지표 // LF = 8×8 블록 평균 밝기의 표준편차(= 띠 세기) · HF = 블록 내 편차 평균(= 포기 잡음) // levels = 블록 밝기를 8단계로 양자화했을 때 면적 0.5% 이상인 단계 수(= 띠 단계) static string Metric(UnityEngine.Color32[] px, int w, int h) { int bs = 8, bw = w / bs, bh = h / bs; var bm = new float[bw * bh]; var bok = new bool[bw * bh]; double hf = 0; int hfn = 0; for (int by = 0; by < bh; by++) for (int bx = 0; bx < bw; bx++) { double s = 0; int n = 0; for (int y = 0; y < bs; y++) for (int x = 0; x < bs; x++) { var p = px[(by * bs + y) * w + (bx * bs + x)]; if (!Ground(p)) continue; s += Lum(p); n++; } if (n < bs * bs / 2) continue; float mean = (float)(s / n); bm[by * bw + bx] = mean; bok[by * bw + bx] = true; for (int y = 0; y < bs; y++) for (int x = 0; x < bs; x++) { var p = px[(by * bs + y) * w + (bx * bs + x)]; if (!Ground(p)) continue; hf += System.Math.Abs(Lum(p) - mean); hfn++; } } double bs1 = 0, bs2 = 0; int bn = 0; var lv = new int[40]; for (int i = 0; i < bm.Length; i++) { if (!bok[i]) continue; bs1 += bm[i]; bs2 += (double)bm[i] * bm[i]; bn++; int q = (int)(bm[i] / 8f); if (q >= 0 && q < 40) lv[q]++; } if (bn == 0 || hfn == 0) return "지면 픽셀 0"; double mu = bs1 / bn; double lf = System.Math.Sqrt(System.Math.Max(0, bs2 / bn - mu * mu)); int levels = 0; for (int i = 0; i < 40; i++) if (lv[i] >= bn * 0.005) levels++; // 에지% — 인접 픽셀 밝기차 8 이상 int ed = 0, edn = 0; for (int y = 0; y < h - 1; y++) for (int x = 0; x < w - 1; x++) { var p = px[y * w + x]; if (!Ground(p)) continue; var r = px[y * w + x + 1]; var d = px[(y + 1) * w + x]; edn++; if ((Ground(r) && System.Math.Abs(Lum(p) - Lum(r)) > 8) || (Ground(d) && System.Math.Abs(Lum(p) - Lum(d)) > 8)) ed++; } return string.Format("지면 {0:F1}% · 띠세기LF={1:F2} · 포기잡음HF={2:F2} · LF/HF={3:F2} · 띠단계={4} · 에지%={5:F2} · 평균밝기={6:F1}", 100.0 * edn / (w * h), lf, hf / hfn, lf / System.Math.Max(0.01, hf / hfn), levels, 100.0 * ed / System.Math.Max(1, edn), mu); } 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 void Ref(string path, string tag) { if (!System.IO.File.Exists(path)) { L(tag + " : 파일 없음 " + path); return; } var tex = new UnityEngine.Texture2D(2, 2, UnityEngine.TextureFormat.RGB24, false); if (!UnityEngine.ImageConversion.LoadImage(tex, System.IO.File.ReadAllBytes(path))) { L(tag + " : 읽기 실패"); return; } L(tag + " " + tex.width + "x" + tex.height + " : " + Metric(tex.GetPixels32(), tex.width, tex.height)); UnityEngine.Object.DestroyImmediate(tex); } static string MatDump(UnityEngine.Material m) { if (m == null) return ""; return "Shades=" + F(m, "_Shades") + " Bright=" + F(m, "_Brightness") + " MinDark=" + F(m, "_MinimumDarkness") + " CloudStr=" + F(m, "_Cloud_Strength") + " CloudDens=" + F(m, "_Cloud_Density") + " Cover=" + F(m, "_Cloud_Cover") + " Change=" + F(m, "_Cloud_Change") + " Move=" + (m.HasProperty("_Cloud_Movement") ? m.GetVector("_Cloud_Movement").ToString() : "-") + " Dif=" + C(m, "_DiffuseColor") + " Shd=" + C(m, "_ShadowDiffuseColor") + " kw=[" + string.Join(",", m.shaderKeywords) + "]"; } static string F(UnityEngine.Material m, string n) { return m.HasProperty(n) ? m.GetFloat(n).ToString("F4") : "-"; } static string C(UnityEngine.Material m, string n) { if (!m.HasProperty(n)) return "-"; var c = m.GetColor(n); return "(" + c.r.ToString("F3") + "," + c.g.ToString("F3") + "," + c.b.ToString("F3") + ")"; } static string RatioStr(UnityEngine.Material m) { if (m == null || !m.HasProperty("_DiffuseColor") || !m.HasProperty("_ShadowDiffuseColor")) return "-"; var d = m.GetColor("_DiffuseColor"); var s = m.GetColor("_ShadowDiffuseColor"); return "(" + Safe(s.r, d.r) + "," + Safe(s.g, d.g) + "," + Safe(s.b, d.b) + ")"; } static string Safe(float a, float b) { return b < 0.001f ? "-" : (a / b).ToString("F3"); } static string Stat() { return "인스턴스=" + WL.Look.Farm.WLIslandGrass.Instances + " 삼각형=" + WL.Look.Farm.WLIslandGrass.Triangles + " 드로우콜+" + WL.Look.Farm.WLIslandGrass.DrawnConfigs + " 타일=" + WL.Look.Farm.WLIslandGrass.Tiles; } // ─────────────────────────────────────────── 카메라·캡처 static UnityEngine.Camera MakeTop(float cx, float cz, float size) { var go = UnityEngine.GameObject.Find("~816qTop"); if (go == null) go = new UnityEngine.GameObject("~816qTop"); var c = go.GetComponent(); if (c == null) c = go.AddComponent(); c.orthographic = true; c.orthographicSize = size; c.nearClipPlane = 0.1f; c.farClipPlane = 200f; c.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f); c.transform.position = new UnityEngine.Vector3(cx, 60f, cz); c.enabled = false; return c; } 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("~816qPd"); if (go == null) go = new UnityEngine.GameObject("~816qPd"); 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 string Shot(UnityEngine.Camera cam, int w, int h, string name) { var tex = Grab(cam, w, h); System.IO.File.WriteAllBytes(Dir + "/" + name + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex)); string s = Metric(tex.GetPixels32(), w, h); UnityEngine.Object.DestroyImmediate(tex); return s; } static void L(string s) { sb.AppendLine(s); Flush(); } static void Flush() { System.IO.File.WriteAllText("AgentScripts/WL816q_PLAY.txt", sb.ToString()); UnityEngine.Debug.Log("[816q play]\n" + sb.ToString()); } }