// WL-816r 3차 — 같은 프레임 안에서 「띠 / 초록바닥」 비를 재서 데모의 「흙 / 풀」 비에 맞춘다. // 구름이 흘러 밝기가 변해도 같은 프레임의 비는 흔들리지 않는다(816q 교훈). public static class WL816r_Play3 { public static void Start() { var go = UnityEngine.GameObject.Find("~WL816rPlay3"); if (go != null) UnityEngine.Object.DestroyImmediate(go); go = new UnityEngine.GameObject("~WL816rPlay3"); go.AddComponent(); } } public class WL816r_Runner3 : UnityEngine.MonoBehaviour { static System.Text.StringBuilder sb; const int W = 1080, H = 1920; static string Dir = "Screenshots_WL/WL816r"; // 데모 실측: 흙 #A4A88B(164,168,139) / 풀 #97C284(151,194,132) → 비 static readonly float[] TargetRatio = { 164f / 151f, 168f / 194f, 139f / 132f }; static readonly string[][] Cand = { new[]{"CCCBB5","807057"}, // 0 현행 new[]{"96B39C","708D76"}, new[]{"A2BCA4","7C967E"}, new[]{"AEC4AC","88A086"}, new[]{"BACCB4","94A88E"}, new[]{"8AA894","648276"}, }; void Start() { StartCoroutine(Co()); } System.Collections.IEnumerator Co() { sb = new System.Text.StringBuilder(); System.IO.Directory.CreateDirectory(Dir); L("데모 목표비 흙/풀 = " + TargetRatio[0].ToString("F4") + "," + TargetRatio[1].ToString("F4") + "," + TargetRatio[2].ToString("F4")); var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; var topMat = cfg.islandTopMaterial; 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 == topMat) { topR.Add(r); break; } var cam = MakePd(); var mask = IdMask(cam, W, H, topR); // 초록/띠 분류는 「현행」 프레임 한 번으로 고정한다 var t0 = Grab(cam, W, H); var p0 = t0.GetPixels32(); var kind = new byte[W * H]; int ng = 0, nb = 0; for (int i = 0; i < mask.Length; i++) { if (mask[i] != 1) continue; var p = p0[i]; if (p.g > p.r + 20 && p.g > p.b + 20) { kind[i] = 1; ng++; } // 초록 바닥 else if (p.r > 150) { kind[i] = 2; nb++; } // 🔴 평평한 윗면 띠만(수직 절벽 제외) } L("초록px=" + ng + " 윗면띠px=" + nb); UnityEngine.Object.DestroyImmediate(t0); var srcBytes = System.IO.File.ReadAllBytes("Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png"); for (int ci = 0; ci < Cand.Length; ci++) { UnityEngine.Material m = null; UnityEngine.Texture2D tex = null; if (ci > 0) { tex = MakeTopTex(srcBytes, Cand[ci][0], Cand[ci][1]); m = new UnityEngine.Material(topMat); m.name = topMat.name + "_c" + ci; m.SetTexture("_BaseMap", tex); m.SetTexture("_ShadowBaseMap", tex); SwapMat(topR, topMat, m); yield return null; yield return null; } var t = Grab(cam, W, H); var px = t.GetPixels32(); var g = Avg(px, kind, 1); var b = Avg(px, kind, 2); float[] ratio = { b[0] / UnityEngine.Mathf.Max(1f, g[0]), b[1] / UnityEngine.Mathf.Max(1f, g[1]), b[2] / UnityEngine.Mathf.Max(1f, g[2]) }; float err = UnityEngine.Mathf.Abs(ratio[0] - TargetRatio[0]) + UnityEngine.Mathf.Abs(ratio[1] - TargetRatio[1]) + UnityEngine.Mathf.Abs(ratio[2] - TargetRatio[2]); L(string.Format("[{0}] 모래#{1} 흙#{2} | 초록={3} 띠={4} | 비={5:F4},{6:F4},{7:F4} | 오차={8:F4}", ci, Cand[ci][0], Cand[ci][1], Hx(g), Hx(b), ratio[0], ratio[1], ratio[2], err)); if (ci == 0) System.IO.File.WriteAllBytes(Dir + "/r3_before.png", UnityEngine.ImageConversion.EncodeToPNG(t)); System.IO.File.WriteAllBytes(Dir + "/r3_c" + ci + ".png", UnityEngine.ImageConversion.EncodeToPNG(t)); UnityEngine.Object.DestroyImmediate(t); if (ci > 0) { SwapMat(topR, m, topMat); UnityEngine.Object.DestroyImmediate(m); UnityEngine.Object.DestroyImmediate(tex); } } Flush(); } static float[] Avg(UnityEngine.Color32[] px, byte[] kind, byte id) { double r = 0, g = 0, b = 0; int n = 0; for (int i = 0; i < kind.Length && i < px.Length; i++) { if (kind[i] != id) continue; r += px[i].r; g += px[i].g; b += px[i].b; n++; } if (n == 0) return new float[] { 1, 1, 1 }; return new float[] { (float)(r / n), (float)(g / n), (float)(b / n) }; } static string Hx(float[] c) { return string.Format("#{0:X2}{1:X2}{2:X2}", (int)c[0], (int)c[1], (int)c[2]); } static UnityEngine.Texture2D MakeTopTex(byte[] srcBytes, string sandHex, string dirtHex) { var tex = new UnityEngine.Texture2D(2, 2, UnityEngine.TextureFormat.RGBA32, false); UnityEngine.ImageConversion.LoadImage(tex, srcBytes); tex.filterMode = UnityEngine.FilterMode.Point; tex.wrapMode = UnityEngine.TextureWrapMode.Clamp; var px = tex.GetPixels32(); var sand = Hex32c(sandHex); var dirt = Hex32c(dirtHex); var sandSrc = new UnityEngine.Color32(0xCC, 0xCB, 0xB5, 255); var dirtSrc = new UnityEngine.Color32(0x80, 0x70, 0x57, 255); for (int i = 0; i < px.Length; i++) { if (px[i].r == sandSrc.r && px[i].g == sandSrc.g && px[i].b == sandSrc.b) px[i] = sand; else if (px[i].r == dirtSrc.r && px[i].g == dirtSrc.g && px[i].b == dirtSrc.b) px[i] = dirt; } tex.SetPixels32(px); tex.Apply(false); return tex; } static UnityEngine.Color32 Hex32c(string h) { return new UnityEngine.Color32((byte)System.Convert.ToInt32(h.Substring(0, 2), 16), (byte)System.Convert.ToInt32(h.Substring(2, 2), 16), (byte)System.Convert.ToInt32(h.Substring(4, 2), 16), 255); } 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 byte[] IdMask(UnityEngine.Camera cam, int w, int h, System.Collections.Generic.List top) { var unlit = UnityEngine.Shader.Find("Universal Render Pipeline/Unlit"); var saved = new System.Collections.Generic.Dictionary(); var black = new UnityEngine.Material(unlit); black.SetColor("_BaseColor", UnityEngine.Color.black); var blue = new UnityEngine.Material(unlit); blue.SetColor("_BaseColor", new UnityEngine.Color(0f, 0f, 1f, 1f)); foreach (var r in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None)) { if (r == null || !r.enabled) continue; var use = top.Contains(r) ? blue : black; saved[r] = r.sharedMaterials; var arr = new UnityEngine.Material[r.sharedMaterials.Length]; for (int i = 0; i < arr.Length; i++) arr[i] = use; r.sharedMaterials = arr; } var tex = Grab(cam, w, h); var px = tex.GetPixels32(); var mask = new byte[w * h]; for (int i = 0; i < px.Length; i++) if (px[i].b > 150 && px[i].r < 90 && px[i].g < 90) mask[i] = 1; UnityEngine.Object.DestroyImmediate(tex); foreach (var kv in saved) if (kv.Key != null) kv.Key.sharedMaterials = kv.Value; UnityEngine.Object.DestroyImmediate(black); UnityEngine.Object.DestroyImmediate(blue); return mask; } 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("~816r3Pd"); if (go == null) go = new UnityEngine.GameObject("~816r3Pd"); 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_PLAY3.txt", sb.ToString()); UnityEngine.Debug.Log("[816r play3]\n" + sb.ToString()); } }