// WL-816n — 데모 풀밭의 「색 경계」가 무엇으로 생기는지 격리 렌더로 가른다. 에디트 모드 전용(데모 Play 금지). // 원본 에셋 무수정: terrain.materialTemplate 에 복사본을 잠시 물렸다가 원복하고, 씬은 저장하지 않는다. public static class WL816n_Demo { const int W = 540, H = 960; public static void Run() { var sb = new System.Text.StringBuilder(); var sc = UnityEditor.SceneManagement.EditorSceneManager.OpenScene( "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); sb.AppendLine("=== Demo.unity roots=" + sc.rootCount + " ==="); var terrain = UnityEngine.Object.FindFirstObjectByType(); if (terrain == null) { sb.AppendLine("NO TERRAIN"); Dump(sb); return; } var td = terrain.terrainData; sb.AppendLine("terrain size=" + td.size + " pos=" + terrain.transform.position + " mat=" + (terrain.materialTemplate ? terrain.materialTemplate.name : "null") + " alphamapTex=" + td.alphamapTextureCount + " alphamapRes=" + td.alphamapResolution + " heightmapRes=" + td.heightmapResolution + " detailProtos=" + td.detailPrototypes.Length); // 높이 분포(= 툰 램프 띠의 원천 후보) float hmin = 1e9f, hmax = -1e9f; for (int i = 0; i <= 32; i++) for (int j = 0; j <= 32; j++) { var h = td.GetInterpolatedHeight(i / 32f, j / 32f); if (h < hmin) hmin = h; if (h > hmax) hmax = h; } sb.AppendLine("terrain height min=" + hmin.ToString("F2") + " max=" + hmax.ToString("F2") + " (월드 y 범위)"); // 데모 풀 배치 규칙 (ⓓ) var tib = terrain.GetComponent(); if (tib == null) tib = UnityEngine.Object.FindFirstObjectByType(); if (tib != null) { sb.AppendLine("--- TerrainInstancesBehaviour (배치 규칙) ---"); sb.AppendLine(" PositionVariance=" + tib.PositionVariance + " ScaleVariance=" + tib.ScaleVariance); DumpLayer(sb, "FirstLayer", tib.FirstLayer); DumpLayer(sb, "SecondLayer", tib.SecondLayer); DumpLayer(sb, "ThirdLayer", tib.ThirdLayer); DumpLayer(sb, "FourthLayer", tib.FourthLayer); } else sb.AppendLine("TerrainInstancesBehaviour 없음"); // 카메라 (816f 판정 카메라와 같은 각 · 직교 6) var camGo = new UnityEngine.GameObject("~816nCam"); var cam = camGo.AddComponent(); cam.orthographic = true; cam.orthographicSize = 6f; cam.transform.rotation = UnityEngine.Quaternion.Euler(30f, 60f, 0f); var c = terrain.transform.position + td.size * 0.5f; c.y = terrain.transform.position.y + hmax; cam.transform.position = c - cam.transform.forward * 40f; cam.clearFlags = UnityEngine.CameraClearFlags.SolidColor; cam.backgroundColor = new UnityEngine.Color(1f, 0f, 1f, 1f); cam.nearClipPlane = 0.1f; cam.farClipPlane = 200f; var orig = terrain.materialTemplate; var copy = new UnityEngine.Material(orig); terrain.materialTemplate = copy; sb.AppendLine("--- 격리 렌더 (데모 바닥 · 풀은 에디트 모드에서 안 그려짐) ---"); Shot(sb, cam, copy, "A_baseline", orig, false, -1); Shot(sb, cam, copy, "B_clouds_off", orig, true, -1); Shot(sb, cam, copy, "C_shades1", orig, false, 1); Shot(sb, cam, copy, "D_clouds_off_shades1", orig, true, 1); terrain.materialTemplate = orig; UnityEngine.Object.DestroyImmediate(copy); UnityEngine.Object.DestroyImmediate(camGo); Dump(sb); } static void DumpLayer(System.Text.StringBuilder sb, string name, Environment.Instancing.TerrainInstancesBehaviour.TerrainInstancingInput v) { if (v == null) { sb.AppendLine(" " + name + " null"); return; } sb.AppendLine(" " + name + " Density=" + v.Density + " settings=" + (v.Settings == null ? 0 : v.Settings.Length)); if (v.Settings == null) return; foreach (var s in v.Settings) sb.AppendLine(" mesh=" + (s.Mesh ? s.Mesh.name : "-") + " mat=" + (s.Material ? s.Material.name : "-") + " scale=" + s.Scale + " prob=" + s.Probability + " normalOffset=" + s.NormalOffset); } static void Shot(System.Text.StringBuilder sb, UnityEngine.Camera cam, UnityEngine.Material copy, string tag, UnityEngine.Material orig, bool cloudsOff, int shades) { copy.CopyPropertiesFromMaterial(orig); if (cloudsOff) { copy.SetFloat("_Cloud_Strength", 0f); copy.DisableKeyword("_CLOUDSENABLED"); } if (shades > 0) copy.SetFloat("_Shades", shades); var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); 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; var px = tex.GetPixels32(); // 배경(마젠타) 제외한 지형 픽셀만 var idx = new System.Collections.Generic.List(); for (int i = 0; i < px.Length; i++) if (!(px[i].r > 200 && px[i].g < 60 && px[i].b > 200)) idx.Add(i); // 고유색 수 + 에지%(가로 인접 차 > 6) var uniq = new System.Collections.Generic.HashSet(); long sr = 0, sg = 0, sb2 = 0; int edge = 0, cmp = 0; foreach (var i in idx) { uniq.Add((px[i].r << 16) | (px[i].g << 8) | px[i].b); sr += px[i].r; sg += px[i].g; sb2 += px[i].b; int x = i % W; if (x + 1 < W) { int j = i + 1; if (!(px[j].r > 200 && px[j].g < 60 && px[j].b > 200)) { cmp++; int d = System.Math.Abs(px[i].r - px[j].r) + System.Math.Abs(px[i].g - px[j].g) + System.Math.Abs(px[i].b - px[j].b); if (d > 6) edge++; } } } int n = idx.Count == 0 ? 1 : idx.Count; sb.AppendLine(string.Format(" {0,-22} 지형px={1} 평균=#{2:X2}{3:X2}{4:X2} 고유색={5} 색경계%={6:F2}", tag, idx.Count, sr / n, sg / n, sb2 / n, uniq.Count, cmp == 0 ? 0f : 100f * edge / cmp)); System.IO.Directory.CreateDirectory("Screenshots_WL/WL816n"); System.IO.File.WriteAllBytes("Screenshots_WL/WL816n/demo_" + tag + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex)); UnityEngine.Object.DestroyImmediate(tex); rt.Release(); UnityEngine.Object.DestroyImmediate(rt); } static void Dump(System.Text.StringBuilder sb) { System.IO.File.WriteAllText("AgentScripts/WL816n_DEMO.txt", sb.ToString()); UnityEngine.Debug.Log("[816n] demo probe done\n" + sb.ToString()); } }