// WL-816q 스캔 — 풀이 바닥과 같은 톤 단계에 들어가는 「고원」의 폭과 중심을 찾는다. // 구름 위상 3곳에서 재확인해 특정 순간에만 맞는 값을 배제한다. 에셋 무변경. public static class WL816q_Scan { public static void Start() { var go = UnityEngine.GameObject.Find("~WL816qScan"); if (go != null) UnityEngine.Object.DestroyImmediate(go); go = new UnityEngine.GameObject("~WL816qScan"); go.AddComponent(); } } public class WL816q_ScanRunner : UnityEngine.MonoBehaviour { static System.Text.StringBuilder sb; const int TS = 512; static string Dir = "Screenshots_WL/WL816q"; static readonly float[] K0 = { 1.0737f, 1.1189f, 1.1365f }; 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 < 5; i++) yield return new UnityEngine.WaitForSeconds(1f); var g = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); if (g == null) { L("grass 없음"); Flush(); yield break; } var rt = UnityEngine.Object.Instantiate(WL.Look.Farm.WLIslandLookSettings.Instance); 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 baseMat = arr[0].material; var dif0 = baseMat.GetColor("_DiffuseColor"); var shd0 = baseMat.GetColor("_ShadowDiffuseColor"); var clone = new UnityEngine.Material(baseMat); clone.name = baseMat.name + "_scan"; for (int i = 0; i < arr.Length; i++) if (arr[i].material == baseMat) arr[i].material = clone; var b = g.CalculateInstancesBounds(); var cam = MakeTop(b.center.x, b.center.z, 6f); float[] phases = { 0f, 3.5f, 7f }; // 구름이 흘러간 서로 다른 순간 3곳 float[] ss = { 0.97f, 0.98f, 0.99f, 1.00f, 1.01f, 1.02f, 1.03f, 1.04f, 1.05f, 1.06f, 1.07f, 1.08f }; var cov = new float[phases.Length, ss.Length]; for (int p = 0; p < phases.Length; p++) { if (p > 0) { UnityEngine.Time.timeScale = 1f; float t0 = UnityEngine.Time.realtimeSinceStartup; while (UnityEngine.Time.realtimeSinceStartup - t0 < 3.5f) yield return null; } UnityEngine.Time.timeScale = 0f; yield return null; rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null; var A = Grab(cam, TS, TS); var apx = A.GetPixels32(); rt.grassEnabled = 1; for (int i = 0; i < ss.Length; i++) { float s = ss[i]; clone.SetColor("_DiffuseColor", new UnityEngine.Color(dif0.r * K0[0] * s, dif0.g * K0[1] * s, dif0.b * K0[2] * s, dif0.a)); clone.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(shd0.r * K0[0] * s, shd0.g * K0[1] * s, shd0.b * K0[2] * s, shd0.a)); g.Rebuild(); yield return null; yield return null; var Bt = Grab(cam, TS, TS); cov[p, i] = Cover(apx, Bt.GetPixels32()); UnityEngine.Object.DestroyImmediate(Bt); } UnityEngine.Object.DestroyImmediate(A); var line = "구름 위상 " + p + " (t=" + UnityEngine.Time.timeSinceLevelLoad.ToString("F1") + "s) 보이는 풀 % : "; for (int i = 0; i < ss.Length; i++) line += ss[i].ToString("F2") + "=" + cov[p, i].ToString("F2") + " "; L(line); } // 세 위상 모두에서 최소인 구간의 중심 int best = 0; float bestv = 999f; for (int i = 0; i < ss.Length; i++) { float m = 0f; for (int p = 0; p < phases.Length; p++) m = UnityEngine.Mathf.Max(m, cov[p, i]); if (m < bestv) { bestv = m; best = i; } } float sBest = ss[best]; L("최적 s=" + sBest.ToString("F2") + " (세 위상 최대 보이는 풀 " + bestv.ToString("F2") + "%)"); L(string.Format("채택 _DiffuseColor = {0:F4} {1:F4} {2:F4}", dif0.r * K0[0] * sBest, dif0.g * K0[1] * sBest, dif0.b * K0[2] * sBest)); L(string.Format("채택 _ShadowDiffuseColor = {0:F4} {1:F4} {2:F4}", shd0.r * K0[0] * sBest, shd0.g * K0[1] * sBest, shd0.b * K0[2] * sBest)); UnityEngine.Time.timeScale = 1f; Flush(); } static float Cover(UnityEngine.Color32[] a, UnityEngine.Color32[] b) { int n = 0, tot = 0; for (int i = 0; i < a.Length; i++) { var p = a[i]; if (!(p.g > p.b + 8 && p.g > 45)) continue; tot++; var q = b[i]; if (q.g <= q.b + 4) continue; // 꽃·자갈 제외 if (System.Math.Abs(p.r - q.r) + System.Math.Abs(p.g - q.g) + System.Math.Abs(p.b - q.b) > 6) n++; } return tot == 0 ? 0f : 100f * n / tot; } 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.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/WL816q_SCAN.txt", sb.ToString()); UnityEngine.Debug.Log("[816q scan]\n" + sb.ToString()); } }