// WL-816r — 데모 흙 화면색 정밀 실측 + 우리 Toon 머티리얼 응답곡선(프로브) 역산. // 에디트 모드 전용 · 데모 씬은 열기만 하고 저장하지 않는다(원본 0줄). public static class WL816r_Demo2 { const int W = 1024, H = 1024; static System.Text.StringBuilder sb; // 프로브 후보 (sRGB hex) — 우리 텍스처가 가질 수 있는 값 static readonly string[] Cand = { "A0E4A2", // 현행 초록(기준점 · 무변경) "CCCBB5", // 현행 모래 "807057", // 현행 흙 "FFFFFF", // 응답곡선 상단 "808080", // 응답곡선 중간 "404040", // 응답곡선 하단 "D6D2AE", "C8C39C", "BAB48C", "E0DCBC", "C2B896", "AC9E78", "D9D8C2", "E6E5CF", "D2D1BA", "DEDCC0", "C9CBB2", "D4D6BE" }; public static void Run() { sb = new System.Text.StringBuilder(); UnityEditor.SceneManagement.EditorSceneManager.OpenScene( "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); var terrain = UnityEngine.Object.FindFirstObjectByType(); var td = terrain.terrainData; int ar = td.alphamapResolution; var a = td.GetAlphamaps(0, 0, ar, ar); // 흙 100 % + 평탄 점 중, 가장 촘촘히 모인 곳(= 넓은 흙 마당)을 고른다 var dirt = new System.Collections.Generic.List(); var grass = new System.Collections.Generic.List(); for (int y = 0; y < ar; y++) for (int x = 0; x < ar; x++) { float u = (float)x / (ar - 1), v = (float)y / (ar - 1); var n = td.GetInterpolatedNormal(u, v); if (UnityEngine.Vector3.Angle(n, UnityEngine.Vector3.up) > 5f) continue; var p = new UnityEngine.Vector3(terrain.transform.position.x + u * td.size.x, terrain.transform.position.y + td.GetInterpolatedHeight(u, v), terrain.transform.position.z + v * td.size.z); if (a[y, x, 1] > 0.995f) dirt.Add(p); else if (a[y, x, 0] > 0.995f) grass.Add(p); } // 흙 점 밀집 중심 = 5 m 반경 이웃이 가장 많은 점 UnityEngine.Vector3 best = dirt.Count > 0 ? dirt[0] : UnityEngine.Vector3.zero; int bestN = -1; for (int i = 0; i < dirt.Count; i += System.Math.Max(1, dirt.Count / 400)) { int cnt = 0; for (int j = 0; j < dirt.Count; j += System.Math.Max(1, dirt.Count / 800)) if ((dirt[j] - dirt[i]).sqrMagnitude < 25f) cnt++; if (cnt > bestN) { bestN = cnt; best = dirt[i]; } } L("흙점=" + dirt.Count + " 풀점=" + grass.Count + " · 밀집중심=" + best + " (이웃표본 " + bestN + ")"); // ── 프로브 판: 흙 마당 위 4 m 상공에 12장 격자 ────────────────────── var topMat = UnityEditor.AssetDatabase.LoadAssetAtPath( "Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena.mat"); var sr = topMat.GetColor("_ShadowDiffuseColor"); // (0.3248,0.3416,0.3936) = 데모 풀 그림자비 var root = new UnityEngine.GameObject("~816rP"); var mats = new System.Collections.Generic.List(); var pos = new UnityEngine.Vector3[Cand.Length]; for (int i = 0; i < Cand.Length; i++) { var lin = Hex(Cand[i]).linear; var m = new UnityEngine.Material(topMat); m.SetTexture("_BaseMap", null); m.SetTexture("_ShadowBaseMap", null); m.SetColor("_DiffuseColor", lin); m.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(lin.r * sr.r, lin.g * sr.g, lin.b * sr.b, 1f)); mats.Add(m); var q = UnityEngine.GameObject.CreatePrimitive(UnityEngine.PrimitiveType.Quad); q.transform.SetParent(root.transform); q.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f); q.transform.localScale = new UnityEngine.Vector3(1.4f, 1.4f, 1f); pos[i] = best + new UnityEngine.Vector3((i % 6 - 2.5f) * 1.6f, 6f, (i / 6 - 0.5f) * 1.6f + 6.5f); q.transform.position = pos[i]; q.GetComponent().sharedMaterial = m; UnityEngine.Object.DestroyImmediate(q.GetComponent()); } var camGo = new UnityEngine.GameObject("~816rC"); var cam = camGo.AddComponent(); cam.orthographic = true; cam.orthographicSize = 9f; cam.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f); cam.transform.position = new UnityEngine.Vector3(best.x, best.y + 40f, best.z + 3.2f); cam.clearFlags = UnityEngine.CameraClearFlags.SolidColor; cam.backgroundColor = new UnityEngine.Color(1f, 0f, 1f, 1f); cam.nearClipPlane = 0.1f; cam.farClipPlane = 200f; cam.aspect = (float)W / H; // 🔴 targetTexture 해제 후에도 WorldToViewportPoint 가 맞도록 고정 var tex = Grab(cam, W, H); System.IO.Directory.CreateDirectory("Screenshots_WL/WL816r"); System.IO.File.WriteAllBytes("Screenshots_WL/WL816r/demo_probe2.png", UnityEngine.ImageConversion.EncodeToPNG(tex)); var px = tex.GetPixels32(); L("--- 프로브 화면색 (탑다운 ortho9 · 데모 조명) ---"); for (int i = 0; i < Cand.Length; i++) L(string.Format(" #{0} -> {1}", Cand[i], Patch(cam, px, pos[i], 10))); L("--- 데모 지형 화면색 ---"); L(" 흙(100%) " + Multi(cam, px, dirt)); L(" 풀(100%) " + Multi(cam, px, grass)); UnityEngine.Object.DestroyImmediate(tex); UnityEngine.Object.DestroyImmediate(camGo); UnityEngine.Object.DestroyImmediate(root); foreach (var m in mats) UnityEngine.Object.DestroyImmediate(m); System.IO.File.WriteAllText("AgentScripts/WL816r_DEMO2.txt", sb.ToString()); UnityEngine.Debug.Log("[816r demo2]\n" + sb.ToString()); } static UnityEngine.Color Hex(string h) { return new UnityEngine.Color(System.Convert.ToInt32(h.Substring(0, 2), 16) / 255f, System.Convert.ToInt32(h.Substring(2, 2), 16) / 255f, System.Convert.ToInt32(h.Substring(4, 2), 16) / 255f, 1f); } static bool Bg(UnityEngine.Color32 p) { return p.r > 200 && p.g < 60 && p.b > 200; } static void Px(UnityEngine.Camera cam, UnityEngine.Vector3 w, out int x, out int y) { var v = cam.WorldToViewportPoint(w); // 픽셀 크기와 무관 x = (int)(v.x * W); y = (int)(v.y * H); } static string Patch(UnityEngine.Camera cam, UnityEngine.Color32[] px, UnityEngine.Vector3 w, int rad) { int cx, cy; Px(cam, w, out cx, out cy); long r = 0, g = 0, b = 0; int n = 0; for (int y = cy - rad; y <= cy + rad; y++) for (int x = cx - rad; x <= cx + rad; x++) { if (x < 0 || y < 0 || x >= W || y >= H) continue; var p = px[y * W + x]; if (Bg(p)) continue; r += p.r; g += p.g; b += p.b; n++; } return n == 0 ? "표본0" : string.Format("#{0:X2}{1:X2}{2:X2} ({3},{4},{5}) n={6}", r / n, g / n, b / n, r / n, g / n, b / n, n); } // 화면 안 표본의 상위 색 5개 + 평균 + 에지% static string Multi(UnityEngine.Camera cam, UnityEngine.Color32[] px, System.Collections.Generic.List pts) { var uniq = new System.Collections.Generic.Dictionary(); long r = 0, g = 0, b = 0; int n = 0, edge = 0, cmp = 0; int st = System.Math.Max(1, pts.Count / 6000); for (int i = 0; i < pts.Count; i += st) { int x, y; Px(cam, pts[i], out x, out y); if (x < 2 || y < 2 || x >= W - 2 || y >= H - 2) continue; var p = px[y * W + x]; if (Bg(p)) continue; r += p.r; g += p.g; b += p.b; n++; int k = (p.r << 16) | (p.g << 8) | p.b; uniq[k] = uniq.ContainsKey(k) ? uniq[k] + 1 : 1; var q = px[y * W + x + 1]; if (Bg(q)) continue; cmp++; if (System.Math.Abs(p.r - q.r) + System.Math.Abs(p.g - q.g) + System.Math.Abs(p.b - q.b) > 6) edge++; } if (n == 0) return "표본0"; var list = new System.Collections.Generic.List>(uniq); list.Sort((A, B) => B.Value.CompareTo(A.Value)); var top = new System.Text.StringBuilder(); for (int i = 0; i < System.Math.Min(6, list.Count); i++) top.Append(string.Format(" #{0:X6}×{1}", list[i].Key, list[i].Value)); return string.Format("n={0} 평균=#{1:X2}{2:X2}{3:X2} 고유색={4} 에지%={5:F2} 상위:{6}", n, r / n, g / n, b / n, uniq.Count, cmp == 0 ? 0f : 100f * edge / cmp, top); } 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); } }