From 75f1333583bb19e38f3f5027d32d364cf71bbc99 Mon Sep 17 00:00:00 2001 From: swrring Date: Tue, 15 Sep 2026 09:49:32 +0900 Subject: [PATCH] =?UTF-8?q?[WL-816r]=20=ED=92=80=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EC=A7=80=ED=98=95(=EA=B8=B8=C2=B7=EB=B0=AD=C2=B7=ED=9D=99?= =?UTF-8?q?=EB=9D=A0)=20=3D=20=EB=8D=B0=EB=AA=A8=20=ED=9D=99=20=ED=86=A4?= =?UTF-8?q?=20(#816)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 데모 실측: 흙 = 툰 단색(고유색 21 · 에지 1.11% · 질감/노이즈 0) · 화면색 #A4A88B vs 풀 #97C284 - 섬 윗면 텍스처 모래/흙 사분면 #CCCBB5/#807057 → #AEC4AC/#88A086 (초록 #A0E4A2 바이트 무변경) - 같은 프레임 띠/초록 비 = 1.0740,0.8479,1.0487 (데모 1.0861,0.8660,1.0530 · 채널당 ≤5) - 길 Farm_Road01_ToonTex _DiffuseColor #DDC89D → #ADC3AB · 그림자비 = 데모 흙 비(0.3596,0.2741,0.2051) - 밭 soilTargetEnabled/soilTargetColor 신설 — 칸 구분·마름/젖음 밝기비 보존한 채 목표 흙색으로 이동 - 되돌리기 = SO 참조 1개(islandTopMaterial/remap → Arena2) + soilTargetEnabled 0 Co-Authored-By: Claude Opus 5 (1M context) --- AgentScripts/WL816r_Demo.cs | 254 ++++++++++++ AgentScripts/WL816r_Demo2.cs | 178 +++++++++ AgentScripts/WL816r_Final.cs | 105 +++++ AgentScripts/WL816r_Play.cs | 372 ++++++++++++++++++ AgentScripts/WL816r_Play2.cs | 320 +++++++++++++++ AgentScripts/WL816r_Play3.cs | 189 +++++++++ .../Farm/Materials/Farm_IslandTop_Arena2.mat | 103 +++++ .../Materials/Farm_IslandTop_Arena2.mat.meta | 8 + .../Farm/Materials/Farm_Road01_ToonTex.mat | 4 +- .../Resources/WL/WLIslandLookSettings.asset | 6 +- .../Farm/Textures/WL_IslandTop_Arena2.png | Bin 0 -> 98 bytes .../Textures/WL_IslandTop_Arena2.png.meta | 181 +++++++++ Assets/WL/Look/Farm/WLIslandLook.cs | 27 +- Assets/WL/Look/Farm/WLIslandLookSettings.cs | 9 + 14 files changed, 1745 insertions(+), 11 deletions(-) create mode 100644 AgentScripts/WL816r_Demo.cs create mode 100644 AgentScripts/WL816r_Demo2.cs create mode 100644 AgentScripts/WL816r_Final.cs create mode 100644 AgentScripts/WL816r_Play.cs create mode 100644 AgentScripts/WL816r_Play2.cs create mode 100644 AgentScripts/WL816r_Play3.cs create mode 100644 Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena2.mat create mode 100644 Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena2.mat.meta create mode 100644 Assets/WL/Look/Farm/Textures/WL_IslandTop_Arena2.png create mode 100644 Assets/WL/Look/Farm/Textures/WL_IslandTop_Arena2.png.meta diff --git a/AgentScripts/WL816r_Demo.cs b/AgentScripts/WL816r_Demo.cs new file mode 100644 index 000000000..53dc734aa --- /dev/null +++ b/AgentScripts/WL816r_Demo.cs @@ -0,0 +1,254 @@ +// WL-816r — 데모 맵의 「흙(밭·길)」 화면색·질감 실측. 에디트 모드 전용(데모 Play 금지). +// 원본 무수정: 씬을 열기만 하고 저장하지 않는다. 프로브 오브젝트는 렌더 후 즉시 파괴. +public static class WL816r_Demo +{ + const int W = 1024, H = 1024; + static System.Text.StringBuilder sb; + + // 우리 텍스처의 현재 세 사분면 (sRGB) — 프로브 기준점 + static readonly string[] BaseHex = { "A0E4A2", "CCCBB5", "807057" }; + static readonly string[] BaseTag = { "우리초록(무변경)", "우리모래", "우리흙" }; + + public static void Run() + { + sb = new System.Text.StringBuilder(); + var sc = UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + L("=== Demo.unity roots=" + sc.rootCount + " ==="); + + var terrain = UnityEngine.Object.FindFirstObjectByType(); + if (terrain == null) { L("NO TERRAIN"); Dump(); return; } + var td = terrain.terrainData; + var layers = td.terrainLayers; + for (int i = 0; i < layers.Length; i++) L("layer[" + i + "] = " + (layers[i] ? layers[i].name : "-")); + + int grassIdx = 0, dirtIdx = 1; + for (int i = 0; i < layers.Length; i++) + { + if (layers[i] == null) continue; + if (layers[i].name.ToLower().Contains("dirt")) dirtIdx = i; + if (layers[i].name.ToLower().Contains("grass")) grassIdx = i; + } + L("grassIdx=" + grassIdx + " dirtIdx=" + dirtIdx + " · terrain size=" + td.size + " pos=" + terrain.transform.position); + + // ── 스플랫맵에서 흙 100 % / 풀 100 % 인 월드 좌표를 고른다 ───────────── + int ar = td.alphamapResolution; + var a = td.GetAlphamaps(0, 0, ar, ar); + var dirtPts = new System.Collections.Generic.List(); + var grassPts = new System.Collections.Generic.List(); + int nd = 0, ng = 0; + for (int y = 0; y < ar; y += 2) + for (int x = 0; x < ar; x += 2) + { + float wd = a[y, x, dirtIdx], wg = a[y, x, grassIdx]; + if (wd > 0.98f) nd++; + if (wg > 0.98f) ng++; + } + L("알파맵 res=" + ar + " · 흙 100% 표본=" + nd + " · 풀 100% 표본=" + ng); + + // 평탄도까지 본다(경사면은 램프 단이 달라 비교가 흐려진다) + System.Func hAt = (u, v) => terrain.transform.position.y + td.GetInterpolatedHeight(u, v); + System.Func slope = (u, v) => + { + var n = td.GetInterpolatedNormal(u, v); + return UnityEngine.Vector3.Angle(n, UnityEngine.Vector3.up); + }; + for (int y = 0; y < ar; y += 1) + for (int x = 0; x < ar; x += 1) + { + float u = (float)x / (ar - 1), v = (float)y / (ar - 1); + float wd = a[y, x, dirtIdx], wg = a[y, x, grassIdx]; + if (slope(u, v) > 6f) continue; + var p = new UnityEngine.Vector3(terrain.transform.position.x + u * td.size.x, hAt(u, v), + terrain.transform.position.z + v * td.size.z); + if (wd > 0.99f && dirtPts.Count < 4000) dirtPts.Add(p); + else if (wg > 0.99f && grassPts.Count < 4000) grassPts.Add(p); + } + L("평탄(≤6°) 흙점=" + dirtPts.Count + " · 풀점=" + grassPts.Count); + if (dirtPts.Count == 0) { L("평탄 흙점 0 — 중단"); Dump(); return; } + + // 흙점들의 무게중심 근처를 찍는다 + var c = UnityEngine.Vector3.zero; + foreach (var p in dirtPts) c += p; + c /= dirtPts.Count; + L("흙 무게중심=" + c); + + // ── 프로브 판 (우리 Toon 머티리얼 · 후보색) ──────────────────────────── + var topMat = UnityEditor.AssetDatabase.LoadAssetAtPath( + "Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena.mat"); + var shadowRatio = new UnityEngine.Color(0.3248f, 0.3416f, 0.3936f, 1f); + if (topMat != null) + { + var d0 = topMat.GetColor("_DiffuseColor"); var s0 = topMat.GetColor("_ShadowDiffuseColor"); + L("Farm_IslandTop_Arena: Dif=" + Cs(d0) + " Shd=" + Cs(s0) + " Shades=" + topMat.GetFloat("_Shades") + + " Bright=" + topMat.GetFloat("_Brightness") + " MinDark=" + topMat.GetFloat("_MinimumDarkness") + + " kw=[" + string.Join(",", topMat.shaderKeywords) + "]"); + shadowRatio = new UnityEngine.Color(s0.r, s0.g, s0.b, 1f); + } + var roadMat = UnityEditor.AssetDatabase.LoadAssetAtPath( + "Assets/WL/Look/Farm/Materials/Farm_Road01_ToonTex.mat"); + if (roadMat != null) + L("Farm_Road01_ToonTex: Dif=" + Cs(roadMat.GetColor("_DiffuseColor")) + " Shd=" + Cs(roadMat.GetColor("_ShadowDiffuseColor")) + + " kw=[" + string.Join(",", roadMat.shaderKeywords) + "]"); + + var probeRoot = new UnityEngine.GameObject("~816rProbe"); + var mats = new System.Collections.Generic.List(); + var probes = new System.Collections.Generic.List(); + var probeTag = new System.Collections.Generic.List(); + float step = 2.2f; + for (int i = 0; i < BaseHex.Length; i++) + { + var srgb = Hex(BaseHex[i]); + var lin = srgb.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 * shadowRatio.r, lin.g * shadowRatio.g, lin.b * shadowRatio.b, 1f)); + mats.Add(m); + var q = UnityEngine.GameObject.CreatePrimitive(UnityEngine.PrimitiveType.Quad); + q.transform.SetParent(probeRoot.transform); + q.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f); + q.transform.localScale = new UnityEngine.Vector3(2f, 2f, 1f); + var pos = c + new UnityEngine.Vector3((i - 1) * step, 0.35f, 4.5f); + q.transform.position = pos; + q.GetComponent().sharedMaterial = m; + UnityEngine.Object.DestroyImmediate(q.GetComponent()); + probes.Add(pos); probeTag.Add(BaseTag[i] + " #" + BaseHex[i]); + } + + // ── 카메라: 흙 무게중심 위 탑다운 직교 ───────────────────────────────── + var camGo = new UnityEngine.GameObject("~816rCam"); + var cam = camGo.AddComponent(); + cam.orthographic = true; cam.orthographicSize = 8f; + cam.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f); + cam.transform.position = new UnityEngine.Vector3(c.x, c.y + 40f, c.z + 2.2f); + cam.clearFlags = UnityEngine.CameraClearFlags.SolidColor; + cam.backgroundColor = new UnityEngine.Color(1f, 0f, 1f, 1f); + cam.nearClipPlane = 0.1f; cam.farClipPlane = 200f; + + var tex = Grab(cam, W, H); + System.IO.Directory.CreateDirectory("Screenshots_WL/WL816r"); + System.IO.File.WriteAllBytes("Screenshots_WL/WL816r/demo_dirt_probe.png", UnityEngine.ImageConversion.EncodeToPNG(tex)); + var px = tex.GetPixels32(); + + L("--- 데모 지형 화면색(탑다운 ortho8 · 같은 렌더) ---"); + L(" 흙 " + SampleSet(cam, tex, px, dirtPts, 600)); + L(" 풀 " + SampleSet(cam, tex, px, grassPts, 600)); + for (int i = 0; i < probes.Count; i++) + L(" 프로브 " + probeTag[i] + " " + SamplePatch(cam, tex, px, probes[i], 14)); + + // ── 질감: 흙 구역 안의 색 분포 ───────────────────────────────────────── + L("--- 흙 질감(같은 렌더 · 흙 표본 화면픽셀) ---"); + L(" 흙 고유색/에지 " + Texture(cam, tex, px, dirtPts)); + L(" 풀 고유색/에지 " + Texture(cam, tex, px, grassPts)); + + // ── 데모 안의 「길」 후보 오브젝트 ───────────────────────────────────── + L("--- 데모 렌더러 머티리얼 분포 ---"); + var cnt = new System.Collections.Generic.Dictionary(); + foreach (var r in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None)) + foreach (var m in r.sharedMaterials) + { + if (m == null) continue; + string k = m.name; + cnt[k] = cnt.ContainsKey(k) ? cnt[k] + 1 : 1; + } + foreach (var kv in cnt) L(" " + kv.Key + " × " + kv.Value); + + UnityEngine.Object.DestroyImmediate(tex); + UnityEngine.Object.DestroyImmediate(camGo); + UnityEngine.Object.DestroyImmediate(probeRoot); + foreach (var m in mats) UnityEngine.Object.DestroyImmediate(m); + Dump(); + } + + static UnityEngine.Color Hex(string h) + { + int r = System.Convert.ToInt32(h.Substring(0, 2), 16); + int g = System.Convert.ToInt32(h.Substring(2, 2), 16); + int b = System.Convert.ToInt32(h.Substring(4, 2), 16); + return new UnityEngine.Color(r / 255f, g / 255f, b / 255f, 1f); + } + static string Cs(UnityEngine.Color c) { return "(" + c.r.ToString("F4") + "," + c.g.ToString("F4") + "," + c.b.ToString("F4") + ")"; } + + static string SampleSet(UnityEngine.Camera cam, UnityEngine.Texture2D tex, UnityEngine.Color32[] px, + System.Collections.Generic.List pts, int maxN) + { + long sr = 0, sg = 0, sb2 = 0; int n = 0; + int stepN = UnityEngine.Mathf.Max(1, pts.Count / maxN); + for (int i = 0; i < pts.Count; i += stepN) + { + var s = cam.WorldToScreenPoint(pts[i]); + int x = (int)s.x, y = (int)s.y; + if (x < 2 || y < 2 || x >= W - 2 || y >= H - 2) continue; + var p = px[y * W + x]; + if (p.r > 200 && p.g < 60 && p.b > 200) continue; + sr += p.r; sg += p.g; sb2 += p.b; n++; + } + if (n == 0) return "표본 0"; + return string.Format("n={0} 평균=#{1:X2}{2:X2}{3:X2} ({4},{5},{6})", n, sr / n, sg / n, sb2 / n, sr / n, sg / n, sb2 / n); + } + + static string SamplePatch(UnityEngine.Camera cam, UnityEngine.Texture2D tex, UnityEngine.Color32[] px, UnityEngine.Vector3 w, int rad) + { + var s = cam.WorldToScreenPoint(w); + int cx = (int)s.x, cy = (int)s.y; + long sr = 0, sg = 0, sb2 = 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 (p.r > 200 && p.g < 60 && p.b > 200) continue; + sr += p.r; sg += p.g; sb2 += p.b; n++; + } + if (n == 0) return "표본 0"; + return string.Format("n={0} 평균=#{1:X2}{2:X2}{3:X2} ({4},{5},{6})", n, sr / n, sg / n, sb2 / n, sr / n, sg / n, sb2 / n); + } + + static string Texture(UnityEngine.Camera cam, UnityEngine.Texture2D tex, UnityEngine.Color32[] px, + System.Collections.Generic.List pts) + { + var uniq = new System.Collections.Generic.Dictionary(); + int edge = 0, cmp = 0; int n = 0; + int stepN = UnityEngine.Mathf.Max(1, pts.Count / 1500); + for (int i = 0; i < pts.Count; i += stepN) + { + var s = cam.WorldToScreenPoint(pts[i]); + int x = (int)s.x, y = (int)s.y; + if (x < 2 || y < 2 || x >= W - 2 || y >= H - 2) continue; + var p = px[y * W + x]; + if (p.r > 200 && p.g < 60 && p.b > 200) continue; + int key = (p.r << 16) | (p.g << 8) | p.b; + uniq[key] = uniq.ContainsKey(key) ? uniq[key] + 1 : 1; + var q = px[y * W + x + 1]; + cmp++; n++; + if (System.Math.Abs(p.r - q.r) + System.Math.Abs(p.g - q.g) + System.Math.Abs(p.b - q.b) > 6) edge++; + } + // 상위 5색 + 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(5, list.Count); i++) + top.Append(string.Format(" #{0:X6}×{1}", list[i].Key, list[i].Value)); + return string.Format("n={0} 고유색={1} 에지%={2:F2} 상위5:{3}", 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); } + static void Dump() + { + System.IO.File.WriteAllText("AgentScripts/WL816r_DEMO.txt", sb.ToString()); + UnityEngine.Debug.Log("[816r demo]\n" + sb.ToString()); + } +} diff --git a/AgentScripts/WL816r_Demo2.cs b/AgentScripts/WL816r_Demo2.cs new file mode 100644 index 000000000..6dffc668c --- /dev/null +++ b/AgentScripts/WL816r_Demo2.cs @@ -0,0 +1,178 @@ +// 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); } +} diff --git a/AgentScripts/WL816r_Final.cs b/AgentScripts/WL816r_Final.cs new file mode 100644 index 000000000..32b74832f --- /dev/null +++ b/AgentScripts/WL816r_Final.cs @@ -0,0 +1,105 @@ +// WL-816r 최종 — 적용 후 PD 구도 캡처 + 띠/초록 비 재측정 + 풀밭(초록) 무변경 확인. +public static class WL816r_Final +{ + public static void Start() + { + var go = UnityEngine.GameObject.Find("~WL816rFin"); + if (go != null) UnityEngine.Object.DestroyImmediate(go); + go = new UnityEngine.GameObject("~WL816rFin"); + go.AddComponent(); + } +} + +public class WL816r_Fin : UnityEngine.MonoBehaviour +{ + static System.Text.StringBuilder sb; + const int W = 1080, H = 1920; + static string Dir = "Screenshots_WL/WL816r"; + + 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 cfg = WL.Look.Farm.WLIslandLookSettings.Instance; + var topMat = cfg.islandTopMaterial; + L("적용 확인 — islandTopMaterial=" + (topMat ? topMat.name : "-") + + " tex=" + (topMat && topMat.GetTexture("_BaseMap") ? topMat.GetTexture("_BaseMap").name : "-") + + " · soilTargetEnabled=" + cfg.soilTargetEnabled + + " · 밭 재도색=" + WL.Look.Farm.WLIslandLook.SoilTilesRepainted + "타일/" + WL.Look.Farm.WLIslandLook.SoilFarmsToned + "Farm"); + UnityEngine.Material road = null; + foreach (var t in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None)) + if (t.name == "Road") + foreach (var r in t.GetComponentsInChildren(true)) + foreach (var m in r.sharedMaterials) + if (m != null && m.shader != null && m.shader.name.Contains("Toon") && road == null) road = m; + if (road != null) L("길 머티리얼 " + road.name + " Dif=" + Hx(road.GetColor("_DiffuseColor").gamma) + " Shd=" + Hx(road.GetColor("_ShadowDiffuseColor").gamma)); + + 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 tex = Grab(cam, W, H); + System.IO.File.WriteAllBytes(Dir + "/after_pd.png", UnityEngine.ImageConversion.EncodeToPNG(tex)); + var px = tex.GetPixels32(); + long gr = 0, gg = 0, gb = 0; int gn = 0; + long br = 0, bg = 0, bb = 0; int bn = 0; + for (int i = 0; i < px.Length; i++) + { + var p = px[i]; + if (p.g > p.r + 20 && p.g > p.b + 20 && p.g > 90) { gr += p.r; gg += p.g; gb += p.b; gn++; } + else if (p.r > 150 && p.g > 140 && p.b < p.g && p.b > 90) { br += p.r; bg += p.g; bb += p.b; bn++; } + } + if (gn > 0 && bn > 0) + { + float[] g = { gr / (float)gn, gg / (float)gn, gb / (float)gn }; + float[] b = { br / (float)bn, bg / (float)bn, bb / (float)bn }; + L(string.Format("[후] 초록={0} 띠={1} · 비={2:F4},{3:F4},{4:F4} (데모 목표 1.0861,0.8660,1.0530)", + Hx2(g), Hx2(b), b[0] / g[0], b[1] / g[1], b[2] / g[2])); + } + UnityEngine.Object.DestroyImmediate(tex); + Flush(); + } + + static string Hx(UnityEngine.Color c) + { return string.Format("#{0:X2}{1:X2}{2:X2}", (int)(UnityEngine.Mathf.Clamp01(c.r) * 255), (int)(UnityEngine.Mathf.Clamp01(c.g) * 255), (int)(UnityEngine.Mathf.Clamp01(c.b) * 255)); } + static string Hx2(float[] c) { return string.Format("#{0:X2}{1:X2}{2:X2}", (int)c[0], (int)c[1], (int)c[2]); } + + 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("~816rFPd"); if (go == null) go = new UnityEngine.GameObject("~816rFPd"); + 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_FINAL.txt", sb.ToString()); + UnityEngine.Debug.Log("[816r final]\n" + sb.ToString()); + } +} diff --git a/AgentScripts/WL816r_Play.cs b/AgentScripts/WL816r_Play.cs new file mode 100644 index 000000000..cd3822109 --- /dev/null +++ b/AgentScripts/WL816r_Play.cs @@ -0,0 +1,372 @@ +// WL-816r — 섬의 「풀 없는 지형」(길 Road · 밭 Soil · 흙띠/모래) 화면색을 데모 흙 톤에 맞춘다. +// 🔴 풀·바닥 초록(816q 확정)은 건드리지 않는다 — 읽기만 하고 대조에만 쓴다. +// 에셋 무변경(전부 런타임 복제본). Debug.LogError 금지. +public static class WL816r_Play +{ + public static void Start() + { + var go = UnityEngine.GameObject.Find("~WL816rPlay"); + if (go != null) UnityEngine.Object.DestroyImmediate(go); + go = new UnityEngine.GameObject("~WL816rPlay"); + go.AddComponent(); + } +} + +public class WL816r_Runner : UnityEngine.MonoBehaviour +{ + static System.Text.StringBuilder sb; + const int W = 1080, H = 1920; + const int TS = 768; + static string Dir = "Screenshots_WL/WL816r"; + + // 섬 윗면 텍스처 후보 (모래 사분면, 흙 사분면) — 데모 흙 화면색 #A4A88B 목표 + static readonly string[][] TopCand = { + new[]{"CCCBB5","807057"}, // 0 = 현행 (전) + new[]{"D4D6BE","C6C8B0"}, // 1 = 데모 흙 톤 + 한 단 어두운 흙 + new[]{"D9D8C2","CBCAB4"}, // 2 + new[]{"CFD1B9","C1C3AB"}, // 3 + }; + + // 밭(Soil) 마름쌍 목표색 후보 (Soil 은 Toon 이 아니라 Simple Lit — 응답이 다르다) + static readonly string[] SoilCand = { "-", "D4D6BE", "BFC1A9", "AAAC94" }; + + 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 b = g.CalculateInstancesBounds(); + + // ── 그룹 수집 ──────────────────────────────────────────────────────── + var roadR = new System.Collections.Generic.List(); + var soilR = new System.Collections.Generic.List(); + var topR = new System.Collections.Generic.List(); + var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; + var topMat = cfg.islandTopMaterial; + foreach (var r in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None)) + { + if (r == null || !r.enabled) continue; + if (r.GetComponent() != null) { soilR.Add(r); continue; } + bool isRoad = false; + for (var t = r.transform; t != null; t = t.parent) if (t.name == "Road") { isRoad = true; break; } + if (isRoad) { roadR.Add(r); continue; } + foreach (var m in r.sharedMaterials) if (m != null && m == topMat) { topR.Add(r); break; } + } + L("그룹 — Road 렌더러=" + roadR.Count + " · Soil=" + soilR.Count + " · 섬윗면(=" + (topMat ? topMat.name : "-") + ")=" + topR.Count); + var roadMats = new System.Collections.Generic.HashSet(); + foreach (var r in roadR) foreach (var m in r.sharedMaterials) if (m != null) roadMats.Add(m); + foreach (var m in roadMats) L(" Road mat=" + m.name + " sh=" + (m.shader ? m.shader.name : "-") + " Dif=" + Cs(m, "_DiffuseColor") + " Shd=" + Cs(m, "_ShadowDiffuseColor")); + var soilMats = new System.Collections.Generic.HashSet(); + foreach (var r in soilR) foreach (var m in r.sharedMaterials) if (m != null) soilMats.Add(m); + foreach (var m in soilMats) L(" Soil mat=" + m.name + " sh=" + (m.shader ? m.shader.name : "-") + " color=" + (m.HasProperty("_BaseColor") ? Hex32(m.GetColor("_BaseColor").gamma) : (m.HasProperty("_Color") ? Hex32(m.GetColor("_Color").gamma) : "-"))); + + var camTop = MakeTop(b.center.x, b.center.z, 6f); + var camPd = MakePd(); + + // ── ID 마스크 (그룹별 화면 픽셀 확정) ──────────────────────────────── + var maskTop = IdMask(camTop, TS, TS, roadR, soilR, topR); + var maskPd = IdMask(camPd, W, H, roadR, soilR, topR); + L("마스크(탑다운) road=" + Count(maskTop, 1) + " soil=" + Count(maskTop, 2) + " top=" + Count(maskTop, 3)); + L("마스크(PD) road=" + Count(maskPd, 1) + " soil=" + Count(maskPd, 2) + " top=" + Count(maskPd, 3)); + + // ── 전 ────────────────────────────────────────────────────────────── + Report("[전]", camTop, TS, TS, maskTop, "before_top"); + Report("[전PD]", camPd, W, H, maskPd, "before_pd"); + + // ── 섬 윗면 텍스처 후보 스윕 (초록 행은 원본 바이트 그대로) ────────── + var srcBytes = System.IO.File.ReadAllBytes("Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png"); + var origTop = topMat; + UnityEngine.Material bestTopMat = null; + for (int ci = 1; ci < TopCand.Length; ci++) + { + var tex = MakeTopTex(srcBytes, TopCand[ci][0], TopCand[ci][1]); + var m = new UnityEngine.Material(origTop); + m.name = origTop.name + "_r" + ci; + m.SetTexture("_BaseMap", tex); m.SetTexture("_ShadowBaseMap", tex); + SwapMat(topR, origTop, m); + yield return null; yield return null; + L("[윗면 후보" + ci + " 모래#" + TopCand[ci][0] + " 흙#" + TopCand[ci][1] + "] " + Sample(camTop, TS, TS, maskTop, 3)); + SwapMat(topR, m, origTop); + if (ci == 1) bestTopMat = m; else { UnityEngine.Object.DestroyImmediate(m); UnityEngine.Object.DestroyImmediate(tex); } + } + + // ── 길 후보 (데모 흙 하이라이트/그림자비 그대로) ───────────────────── + // 데모 Terrain.mat 흙 = Highlight(0.8396,0.8304,0.6456) · Shadow(0.3019,0.2276,0.1324) + var roadCand = new[] { "D4D6BE", "D9D8C2", "CFD1B9" }; + var roadShadowRatio = new UnityEngine.Color(0.3596f, 0.2741f, 0.2051f, 1f); + var roadOrig = new System.Collections.Generic.List(roadMats); + for (int ci = 0; ci < roadCand.Length; ci++) + { + var clones = new System.Collections.Generic.List(); + foreach (var om in roadOrig) + { + var m = new UnityEngine.Material(om); + var lin = Hex(roadCand[ci]).linear; + m.SetColor("_DiffuseColor", lin); + m.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(lin.r * roadShadowRatio.r, lin.g * roadShadowRatio.g, lin.b * roadShadowRatio.b, 1f)); + clones.Add(m); + SwapMat(roadR, om, m); + } + yield return null; yield return null; + L("[길 후보 #" + roadCand[ci] + "] " + Sample(camTop, TS, TS, maskTop, 1)); + for (int i = 0; i < roadOrig.Count; i++) { SwapMat(roadR, clones[i], roadOrig[i]); UnityEngine.Object.DestroyImmediate(clones[i]); } + } + + // ── 밭 후보 ───────────────────────────────────────────────────────── + for (int ci = 1; ci < SoilCand.Length; ci++) + { + RetoneSoil(SoilCand[ci]); + yield return null; yield return null; + L("[밭 후보 #" + SoilCand[ci] + "] " + Sample(camTop, TS, TS, maskTop, 2) + " · 색종류=" + SoilColorKinds(soilR)); + } + RetoneSoil("-"); // 원복 + + Flush(); + } + + // ───────────────────────────────── 밭 재도색 (WLIslandLook 과 같은 경로) + static UnityEngine.Color[] s_soilOrig; + static readonly string[] SF = { "soilDryColor1", "soilDryColor2", "soilWetColor1", "soilWetColor2" }; + + static void RetoneSoil(string hex) + { + var farms = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + var t = typeof(CryingSnow.FarmingIsland.Farm); + foreach (var farm in farms) + { + var fis = new System.Reflection.FieldInfo[4]; + var v = new UnityEngine.Color[4]; + bool ok = true; + for (int k = 0; k < 4; k++) + { + fis[k] = t.GetField(SF[k], System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + if (fis[k] == null) { ok = false; break; } + v[k] = (UnityEngine.Color)fis[k].GetValue(farm); + } + if (!ok) continue; + if (s_soilOrig == null) s_soilOrig = new[] { v[0], v[1], v[2], v[3] }; + if (hex == "-") { for (int k = 0; k < 4; k++) fis[k].SetValue(farm, s_soilOrig[k]); } + else + { + var tgt = Hex(hex).linear; + // 마름쌍 중점을 목표로 옮기고, 칸 차이·마름/젖음 차이는 밝기비로 보존 + var dm = (s_soilOrig[0] + s_soilOrig[1]) * 0.5f; + var wm = (s_soilOrig[2] + s_soilOrig[3]) * 0.5f; + float dl = UnityEngine.Mathf.Max(0.0001f, Lum(dm)), wl = Lum(wm); + var wt = tgt * (wl / dl); + for (int k = 0; k < 4; k++) + { + var mid = k < 2 ? dm : wm; + var nt = k < 2 ? tgt : wt; + var d = s_soilOrig[k] - mid; // 칸 차이 보존 + fis[k].SetValue(farm, new UnityEngine.Color(nt.r + d.r, nt.g + d.g, nt.b + d.b, 1f)); + } + } + foreach (var s in farm.GetComponentsInChildren(true)) + if (s != null) { try { s.Initialize(farm); } catch { } } + } + } + static float Lum(UnityEngine.Color c) { return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b; } + + static int SoilColorKinds(System.Collections.Generic.List soilR) + { + var s = new System.Collections.Generic.HashSet(); + foreach (var r in soilR) + { + var m = r.material; + if (m == null) continue; + var c = m.HasProperty("_BaseColor") ? m.GetColor("_BaseColor") : (m.HasProperty("_Color") ? m.GetColor("_Color") : UnityEngine.Color.black); + s.Add(Hex32(c.gamma)); + } + return s.Count; + } + + // ───────────────────────────────── 텍스처 (초록 행 원본 유지) + 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; + int w = tex.width, h = tex.height; + 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); + int ns = 0, nd = 0; + for (int i = 0; i < px.Length; i++) + { + if (Same(px[i], sandSrc)) { px[i] = sand; ns++; } + else if (Same(px[i], dirtSrc)) { px[i] = dirt; nd++; } + } + tex.SetPixels32(px); tex.Apply(false); + L(" 텍스처 " + w + "x" + h + " 모래칸=" + ns + " 흙칸=" + nd + " (초록 무변경)"); + return tex; + } + static bool Same(UnityEngine.Color32 a, UnityEngine.Color32 b) { return a.r == b.r && a.g == b.g && a.b == b.b; } + + 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; + } + } + + // ───────────────────────────────── ID 마스크 + static byte[] IdMask(UnityEngine.Camera cam, int w, int h, + System.Collections.Generic.List road, + System.Collections.Generic.List soil, + System.Collections.Generic.List top) + { + var unlit = UnityEngine.Shader.Find("Universal Render Pipeline/Unlit"); + var saved = new System.Collections.Generic.Dictionary(); + var mats = new System.Collections.Generic.List(); + System.Action, UnityEngine.Color> paint = (rs, c) => + { + var m = new UnityEngine.Material(unlit); m.SetColor("_BaseColor", c); mats.Add(m); + foreach (var r in rs) + { + if (!saved.ContainsKey(r)) saved[r] = r.sharedMaterials; + var arr = new UnityEngine.Material[r.sharedMaterials.Length]; + for (int i = 0; i < arr.Length; i++) arr[i] = m; + r.sharedMaterials = arr; + } + }; + // 그 밖의 렌더러는 검정 + var others = new System.Collections.Generic.List(); + foreach (var r in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsSortMode.None)) + if (r != null && r.enabled && !road.Contains(r) && !soil.Contains(r) && !top.Contains(r)) others.Add(r); + paint(others, UnityEngine.Color.black); + paint(road, new UnityEngine.Color(1f, 0f, 0f, 1f)); + paint(soil, new UnityEngine.Color(0f, 1f, 0f, 1f)); + paint(top, new UnityEngine.Color(0f, 0f, 1f, 1f)); + + var tex = Grab(cam, w, h); + var px = tex.GetPixels32(); + var mask = new byte[w * h]; + for (int i = 0; i < px.Length; i++) + { + var p = px[i]; + if (p.r > 150 && p.g < 90 && p.b < 90) mask[i] = 1; + else if (p.g > 150 && p.r < 90 && p.b < 90) mask[i] = 2; + else if (p.b > 150 && p.r < 90 && p.g < 90) mask[i] = 3; + } + UnityEngine.Object.DestroyImmediate(tex); + foreach (var kv in saved) kv.Key.sharedMaterials = kv.Value; + foreach (var m in mats) UnityEngine.Object.DestroyImmediate(m); + return mask; + } + static int Count(byte[] m, byte id) { int n = 0; for (int i = 0; i < m.Length; i++) if (m[i] == id) n++; return n; } + + // ───────────────────────────────── 측정 + static void Report(string tag, UnityEngine.Camera cam, int w, int h, byte[] mask, string name) + { + var tex = Grab(cam, w, h); + System.IO.File.WriteAllBytes(Dir + "/" + name + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex)); + var px = tex.GetPixels32(); + L(tag + " 길 " + Stat(px, mask, 1) + " | 밭 " + Stat(px, mask, 2) + " | 섬윗면 " + Stat(px, mask, 3)); + UnityEngine.Object.DestroyImmediate(tex); + } + static string Sample(UnityEngine.Camera cam, int w, int h, byte[] mask, byte id) + { + var tex = Grab(cam, w, h); + var px = tex.GetPixels32(); + string s = Stat(px, mask, id); + UnityEngine.Object.DestroyImmediate(tex); + return s; + } + static string Stat(UnityEngine.Color32[] px, byte[] mask, byte id) + { + long r = 0, g = 0, b2 = 0; int n = 0; + var uniq = new System.Collections.Generic.Dictionary(); + for (int i = 0; i < mask.Length && i < px.Length; i++) + { + if (mask[i] != id) continue; + var p = px[i]; r += p.r; g += p.g; b2 += p.b; n++; + int k = (p.r << 16) | (p.g << 8) | p.b; + uniq[k] = uniq.ContainsKey(k) ? uniq[k] + 1 : 1; + } + if (n == 0) return "px0"; + 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(3, 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}", n, r / n, g / n, b2 / n, uniq.Count, top); + } + + // ───────────────────────────────── 유틸 + 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 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 string Hex32(UnityEngine.Color c) + { + return string.Format("#{0:X2}{1:X2}{2:X2}", (int)(UnityEngine.Mathf.Clamp01(c.r) * 255), (int)(UnityEngine.Mathf.Clamp01(c.g) * 255), (int)(UnityEngine.Mathf.Clamp01(c.b) * 255)); + } + static string Cs(UnityEngine.Material m, string n) + { + if (m == null || !m.HasProperty(n)) return "-"; + var c = m.GetColor(n); + return "(" + c.r.ToString("F4") + "," + c.g.ToString("F4") + "," + c.b.ToString("F4") + ")=" + Hex32(c.gamma); + } + + static UnityEngine.Camera MakeTop(float cx, float cz, float size) + { + var go = UnityEngine.GameObject.Find("~816rTop"); if (go == null) go = new UnityEngine.GameObject("~816rTop"); + 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("~816rPd"); if (go == null) go = new UnityEngine.GameObject("~816rPd"); + 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_PLAY.txt", sb.ToString()); + UnityEngine.Debug.Log("[816r play]\n" + sb.ToString()); + } +} diff --git a/AgentScripts/WL816r_Play2.cs b/AgentScripts/WL816r_Play2.cs new file mode 100644 index 000000000..ef8e02d5e --- /dev/null +++ b/AgentScripts/WL816r_Play2.cs @@ -0,0 +1,320 @@ +// WL-816r 2차 — PD 구도에서 「흙띠·모래띠」 후보 스윕 + 길/밭 머티리얼 프로브 역산. +public static class WL816r_Play2 +{ + public static void Start() + { + var go = UnityEngine.GameObject.Find("~WL816rPlay2"); + if (go != null) UnityEngine.Object.DestroyImmediate(go); + go = new UnityEngine.GameObject("~WL816rPlay2"); + go.AddComponent(); + } +} + +public class WL816r_Runner2 : UnityEngine.MonoBehaviour +{ + static System.Text.StringBuilder sb; + const int W = 1080, H = 1920; + static string Dir = "Screenshots_WL/WL816r"; + + // (모래 사분면, 흙 사분면) 후보 — 데모 흙 화면색 #A4A88B 목표 + static readonly string[][] TopCand = { + new[]{"CCCBB5","807057"}, // 0 현행 + new[]{"B5B69E","8F8F79"}, + new[]{"A8A992","82836E"}, + new[]{"C0C1A8","9A9B84"}, + new[]{"AEAF98","6E6F5C"}, + }; + + void Start() { StartCoroutine(Co()); } + + System.Collections.IEnumerator Co() + { + sb = new System.Text.StringBuilder(); + System.IO.Directory.CreateDirectory(Dir); + var cfg = WL.Look.Farm.WLIslandLookSettings.Instance; + var topMat = cfg.islandTopMaterial; + + // ── 실태 보고 (비활성 포함) ───────────────────────────────────────── + var farms = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + int soilAll = 0, soilActive = 0; + UnityEngine.Material soilMat = null; + foreach (var f in farms) + foreach (var s in f.GetComponentsInChildren(true)) + { + soilAll++; + var r = s.GetComponent(); + if (r != null && r.gameObject.activeInHierarchy && r.enabled) soilActive++; + if (soilMat == null && r != null) soilMat = r.sharedMaterial; + } + var roadTfs = new System.Collections.Generic.List(); + foreach (var t in UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None)) + if (t.name == "Road") roadTfs.Add(t); + int roadRendAll = 0, roadRendActive = 0; + UnityEngine.Material roadMat = null; + foreach (var t in roadTfs) + foreach (var r in t.GetComponentsInChildren(true)) + { + roadRendAll++; + if (r.gameObject.activeInHierarchy && r.enabled) roadRendActive++; + foreach (var m in r.sharedMaterials) + if (m != null && m.shader != null && m.shader.name.Contains("Toon") && roadMat == null) roadMat = m; + } + L("실태 — Farm=" + farms.Length + " Soil(전체/활성)=" + soilAll + "/" + soilActive + + " · Road 트랜스폼=" + roadTfs.Count + " 렌더러(전체/활성)=" + roadRendAll + "/" + roadRendActive); + L(" Soil mat=" + (soilMat ? soilMat.name + " sh=" + soilMat.shader.name + " color=" + Hx(MatColor(soilMat).gamma) : "없음")); + L(" Road mat=" + (roadMat ? roadMat.name + " sh=" + roadMat.shader.name + " Dif=" + Hx(roadMat.GetColor("_DiffuseColor").gamma) + " Shd=" + Hx(roadMat.GetColor("_ShadowDiffuseColor").gamma) : "없음")); + + // ── 섬 윗면 렌더러 ────────────────────────────────────────────────── + 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 camPd = MakePd(); + + // 띠 픽셀 = 섬윗면 그룹 중 「초록이 아닌」 픽셀 (전 기준으로 고정한다) + var mask = IdMask(camPd, W, H, topR); + var texB = Grab(camPd, W, H); + System.IO.File.WriteAllBytes(Dir + "/r2_before_pd.png", UnityEngine.ImageConversion.EncodeToPNG(texB)); + var pxB = texB.GetPixels32(); + var band = new byte[W * H]; + int nb = 0; + for (int i = 0; i < mask.Length; i++) + { + if (mask[i] != 1) continue; + var p = pxB[i]; + if (p.g > p.r + 10 && p.g > p.b + 10) continue; // 초록(풀밭 바닥) 제외 + band[i] = 1; nb++; + } + L("섬윗면 그룹 px=" + Count(mask, 1) + " · 그중 띠(비초록) px=" + nb); + L("[전] 띠 " + Stat(pxB, band, 1)); + UnityEngine.Object.DestroyImmediate(texB); + + // ── 텍스처 후보 스윕 (초록 행 원본 바이트 유지) ───────────────────── + var srcBytes = System.IO.File.ReadAllBytes("Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png"); + for (int ci = 1; ci < TopCand.Length; ci++) + { + var tex = MakeTopTex(srcBytes, TopCand[ci][0], TopCand[ci][1]); + var m = new UnityEngine.Material(topMat); m.name = topMat.name + "_r" + ci; + m.SetTexture("_BaseMap", tex); m.SetTexture("_ShadowBaseMap", tex); + SwapMat(topR, topMat, m); + yield return null; yield return null; + var t2 = Grab(camPd, W, H); + var p2 = t2.GetPixels32(); + L("[후보" + ci + " 모래#" + TopCand[ci][0] + " 흙#" + TopCand[ci][1] + "] 띠 " + Stat(p2, band, 1)); + if (ci == 1) System.IO.File.WriteAllBytes(Dir + "/r2_cand1_pd.png", UnityEngine.ImageConversion.EncodeToPNG(t2)); + UnityEngine.Object.DestroyImmediate(t2); + SwapMat(topR, m, topMat); + UnityEngine.Object.DestroyImmediate(m); UnityEngine.Object.DestroyImmediate(tex); + } + + // ── 길·밭 머티리얼 프로브 (평평한 판 · 섬 위) ─────────────────────── + var pc = UnityEngine.GameObject.FindGameObjectWithTag("Player"); + var org = pc != null ? pc.transform.position : UnityEngine.Vector3.zero; + var cands = new[] { "D4D6BE", "C6C8B0", "B5B69E", "A8A992", "9A9B84", "CCCBB5" }; + var camTopP = MakeTopProbe(org.x, org.z + 3f, 5f); + yield return ProbeRow(camTopP, roadMat, cands, org + new UnityEngine.Vector3(0f, 1.2f, 3f), "길(Toon)"); + yield return ProbeRow(camTopP, soilMat, cands, org + new UnityEngine.Vector3(0f, 1.2f, 4.6f), "밭(Soil셰이더)"); + + Flush(); + } + + System.Collections.IEnumerator ProbeRow(UnityEngine.Camera cam, UnityEngine.Material baseMat, string[] cands, UnityEngine.Vector3 origin, string tag) + { + if (baseMat == null) { L(tag + " — 머티리얼 없음, 프로브 생략"); yield break; } + var root = new UnityEngine.GameObject("~816rP2"); + var mats = new System.Collections.Generic.List(); + var rends = new System.Collections.Generic.List(); + var pos = new UnityEngine.Vector3[cands.Length]; + bool toon = baseMat.HasProperty("_DiffuseColor"); + var sr = toon && baseMat.HasProperty("_ShadowDiffuseColor") + ? new UnityEngine.Color(0.3596f, 0.2741f, 0.2051f, 1f) : UnityEngine.Color.white; // 데모 흙 그림자비 + for (int i = 0; i < cands.Length; i++) + { + var lin = Hex(cands[i]).linear; + var m = new UnityEngine.Material(baseMat); m.name = baseMat.name + "_p" + i; + if (toon) + { + 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)); + } + else SetMatColor(m, lin); + 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.1f, 1.1f, 1f); + pos[i] = origin + new UnityEngine.Vector3((i - (cands.Length - 1) * 0.5f) * 1.3f, 0f, 0f); + q.transform.position = pos[i]; + var rr = q.GetComponent(); + rr.sharedMaterial = m; rends.Add(rr); + UnityEngine.Object.DestroyImmediate(q.GetComponent()); + } + yield return null; yield return null; + var tex = Grab(cam, 1024, 1024); + var px = tex.GetPixels32(); + for (int i = 0; i < cands.Length; i++) L(" " + tag + " #" + cands[i] + " -> " + Patch(cam, px, pos[i], 1024, 8)); + System.IO.File.WriteAllBytes(Dir + "/r2_probe_" + (toon ? "road" : "soil") + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex)); + UnityEngine.Object.DestroyImmediate(tex); + UnityEngine.Object.DestroyImmediate(root); + foreach (var m in mats) UnityEngine.Object.DestroyImmediate(m); + } + + static UnityEngine.Color MatColor(UnityEngine.Material m) + { + if (m == null) return UnityEngine.Color.black; + if (m.HasProperty("_BaseColor")) return m.GetColor("_BaseColor"); + if (m.HasProperty("_Color")) return m.GetColor("_Color"); + return UnityEngine.Color.black; + } + static void SetMatColor(UnityEngine.Material m, UnityEngine.Color c) + { + if (m.HasProperty("_BaseColor")) m.SetColor("_BaseColor", c); + if (m.HasProperty("_Color")) m.SetColor("_Color", c); + } + + 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 (Same(px[i], sandSrc)) px[i] = sand; + else if (Same(px[i], dirtSrc)) px[i] = dirt; + } + tex.SetPixels32(px); tex.Apply(false); + return tex; + } + static bool Same(UnityEngine.Color32 a, UnityEngine.Color32 b) { return a.r == b.r && a.g == b.g && a.b == b.b; } + + 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 mats = new System.Collections.Generic.List(); + var black = new UnityEngine.Material(unlit); black.SetColor("_BaseColor", UnityEngine.Color.black); mats.Add(black); + var blue = new UnityEngine.Material(unlit); blue.SetColor("_BaseColor", new UnityEngine.Color(0f, 0f, 1f, 1f)); mats.Add(blue); + 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; + foreach (var m in mats) UnityEngine.Object.DestroyImmediate(m); + return mask; + } + static int Count(byte[] m, byte id) { int n = 0; for (int i = 0; i < m.Length; i++) if (m[i] == id) n++; return n; } + + static string Stat(UnityEngine.Color32[] px, byte[] mask, byte id) + { + long r = 0, g = 0, b2 = 0; int n = 0; + var uniq = new System.Collections.Generic.Dictionary(); + for (int i = 0; i < mask.Length && i < px.Length; i++) + { + if (mask[i] != id) continue; + var p = px[i]; r += p.r; g += p.g; b2 += p.b; n++; + int k = (p.r << 16) | (p.g << 8) | p.b; + uniq[k] = uniq.ContainsKey(k) ? uniq[k] + 1 : 1; + } + if (n == 0) return "px0"; + 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(4, list.Count); i++) top.Append(string.Format(" #{0:X6}x{1}", list[i].Key, list[i].Value)); + return string.Format("n={0} avg=#{1:X2}{2:X2}{3:X2} uniq={4} top:{5}", n, r / n, g / n, b2 / n, uniq.Count, top); + } + + static string Patch(UnityEngine.Camera cam, UnityEngine.Color32[] px, UnityEngine.Vector3 w, int size, int rad) + { + var v = cam.WorldToViewportPoint(w); + int cx = (int)(v.x * size), cy = (int)(v.y * size); + 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 >= size || y >= size) continue; + var p = px[y * size + x]; r += p.r; g += p.g; b += p.b; n++; + } + return n == 0 ? "px0" : string.Format("#{0:X2}{1:X2}{2:X2} ({3},{4},{5})", r / n, g / n, b / n, r / n, g / n, b / n); + } + + 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 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 string Hx(UnityEngine.Color c) + { + return string.Format("#{0:X2}{1:X2}{2:X2}", (int)(UnityEngine.Mathf.Clamp01(c.r) * 255), (int)(UnityEngine.Mathf.Clamp01(c.g) * 255), (int)(UnityEngine.Mathf.Clamp01(c.b) * 255)); + } + + 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("~816r2Pd"); if (go == null) go = new UnityEngine.GameObject("~816r2Pd"); + 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.aspect = (float)W / H; + c.enabled = false; return c; + } + static UnityEngine.Camera MakeTopProbe(float x, float z, float size) + { + var go = UnityEngine.GameObject.Find("~816r2Tp"); if (go == null) go = new UnityEngine.GameObject("~816r2Tp"); + 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(x, 40f, z); + c.aspect = 1f; 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_PLAY2.txt", sb.ToString()); + UnityEngine.Debug.Log("[816r play2]\n" + sb.ToString()); + } +} diff --git a/AgentScripts/WL816r_Play3.cs b/AgentScripts/WL816r_Play3.cs new file mode 100644 index 000000000..938d4101c --- /dev/null +++ b/AgentScripts/WL816r_Play3.cs @@ -0,0 +1,189 @@ +// 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()); + } +} diff --git a/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena2.mat b/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena2.mat new file mode 100644 index 000000000..4815589c6 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena2.mat @@ -0,0 +1,103 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Farm_IslandTop_Arena2 + m_Shader: {fileID: -6465566751694194690, guid: 378227f8a69213e419dfb0d889d8dd2f, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _OUTLINESENABLED + - _SHADOWS_SOFT + m_InvalidKeywords: + - _CLOUDSENABLED + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: + RenderType: Opaque + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 2800000, guid: 6fd28da5d8014083aed0633dfd21c3a5, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ShadowBaseMap: + m_Texture: {fileID: 2800000, guid: 6fd28da5d8014083aed0633dfd21c3a5, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _Brightness: 0.25 + - _CastShadows: 1 + - _Cloud_Change: 0.005 + - _Cloud_Cover: 0.5 + - _Cloud_Density: 0.01 + - _Cloud_Strength: 1 + - _Cull: 2 + - _DepthEdgeStrength: 0.5 + - _DepthThreshold: 0.01 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _MinimumDarkness: 0.2 + - _NormalEdgeStrength: 0.3 + - _NormalThreshold: 1 + - _OUTLINESENABLED: 1 + - _QueueControl: 0 + - _QueueOffset: 0 + - _Shades: 7 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _XRMotionVectorsPass: 1 + - _ZTest: 4 + - _ZWrite: 1 + - _ZWriteControl: 0 + m_Colors: + - _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0} + - _Cloud_Step: {r: 13, g: 17, b: 0, a: 0} + - _DiffuseColor: {r: 1, g: 1, b: 1, a: 1} + - _NormalBias: {r: 1, g: 1, b: 1, a: 0} + - _Outline: {r: 0, g: 0, b: 0, a: 0} + - _ShadowDiffuseColor: {r: 0.3248, g: 0.3416, b: 0.3936, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 +--- !u!114 &4831086338097714236 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 diff --git a/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena2.mat.meta b/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena2.mat.meta new file mode 100644 index 000000000..e33eab49f --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena2.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eba171966111423bbabd5fc93575b339 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Materials/Farm_Road01_ToonTex.mat b/Assets/WL/Look/Farm/Materials/Farm_Road01_ToonTex.mat index 7ddf7ca2f..26e55a1ad 100644 --- a/Assets/WL/Look/Farm/Materials/Farm_Road01_ToonTex.mat +++ b/Assets/WL/Look/Farm/Materials/Farm_Road01_ToonTex.mat @@ -95,9 +95,9 @@ Material: m_Colors: - _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0} - _Cloud_Step: {r: 13, g: 17, b: 0, a: 0} - - _DiffuseColor: {r: 0.7264151, g: 0.5812177, b: 0.3392217, a: 0} + - _DiffuseColor: {r: 0.4232677, g: 0.5520114, b: 0.4125426, a: 0} - _NormalBias: {r: 1, g: 1, b: 1, a: 0} - _Outline: {r: 0, g: 0, b: 0, a: 0} - - _ShadowDiffuseColor: {r: 0.15719622, g: 0.12159074, b: 0.12595302, a: 0} + - _ShadowDiffuseColor: {r: 0.1522071, g: 0.1513063, b: 0.0846125, a: 0} m_BuildTextureStacks: [] m_AllowLocking: 1 diff --git a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset index 953497735..f3e7bc8cf 100644 --- a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset @@ -23,7 +23,7 @@ MonoBehaviour: to: {fileID: 2100000, guid: 68107aa66ca3855458e2ac55c239d57c, type: 2} - enabled_: 1 from: {fileID: 2100000, guid: 8b9c3fe585ec43742a0e29e3b3c82f6b, type: 2} - to: {fileID: 2100000, guid: 197cbcbf4165c954887272f7aea2ac42, type: 2} + to: {fileID: 2100000, guid: eba171966111423bbabd5fc93575b339, type: 2} - enabled_: 1 from: {fileID: 2100000, guid: ee0c382820516a74ba37852af34ecd11, type: 2} to: {fileID: 2100000, guid: ece3895a184497040812f320d69cb967, type: 2} @@ -36,11 +36,13 @@ MonoBehaviour: - enabled_: 1 from: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2} to: {fileID: 2100000, guid: 1c9ce9b7865f6d643ad9601f6fa81ba3, type: 2} - islandTopMaterial: {fileID: 2100000, guid: 197cbcbf4165c954887272f7aea2ac42, type: 2} + islandTopMaterial: {fileID: 2100000, guid: eba171966111423bbabd5fc93575b339, type: 2} skipSoilRenderers: 1 soilToneEnabled: 1 soilTint: {r: 0.84, g: 0.86, b: 0.9, a: 1} soilCheckerContrast: 0.6 + soilTargetEnabled: 1 + soilTargetColor: {r: 0.3049, g: 0.4033, b: 0.2961, a: 1} ambientSky: {r: 0.212, g: 0.227, b: 0.259, a: 1} ambientEquator: {r: 0.114, g: 0.125, b: 0.133, a: 1} ambientGround: {r: 0.047, g: 0.043, b: 0.035, a: 1} diff --git a/Assets/WL/Look/Farm/Textures/WL_IslandTop_Arena2.png b/Assets/WL/Look/Farm/Textures/WL_IslandTop_Arena2.png new file mode 100644 index 0000000000000000000000000000000000000000..7dc40f322261cbca646817d197873a276d2d7a82 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqnw~C>Ar*6yW6D#`*0ZS;ygsL! vqQ@-E#m~JUhVis|YrY1P5SP)HGG>N5Ivmmmf){WDH8FU)`njxgN@xNA9Tyl( literal 0 HcmV?d00001 diff --git a/Assets/WL/Look/Farm/Textures/WL_IslandTop_Arena2.png.meta b/Assets/WL/Look/Farm/Textures/WL_IslandTop_Arena2.png.meta new file mode 100644 index 000000000..6019e9c58 --- /dev/null +++ b/Assets/WL/Look/Farm/Textures/WL_IslandTop_Arena2.png.meta @@ -0,0 +1,181 @@ +fileFormatVersion: 2 +guid: 6fd28da5d8014083aed0633dfd21c3a5 +TextureImporter: + internalIDToNameTable: + - first: + 213: -7907069911445722437 + second: WL_IslandTop_Demo_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 1 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: iOS + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: WL_IslandTop_Demo_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 8 + height: 8 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: bba291a1fc1744290800000000000000 + internalID: -7907069911445722437 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/WLIslandLook.cs b/Assets/WL/Look/Farm/WLIslandLook.cs index 9e9793be6..90cd2d758 100644 --- a/Assets/WL/Look/Farm/WLIslandLook.cs +++ b/Assets/WL/Look/Farm/WLIslandLook.cs @@ -427,8 +427,10 @@ namespace WL.Look.Farm } if (!ok) continue; - Apply(cfg, fis, farm, vals, 0, 1); // 마름 한 쌍 - Apply(cfg, fis, farm, vals, 2, 3); // 젖음 한 쌍 + // 「마름」 중점 밝기 = 기준. 젖음은 이 비를 그대로 유지한다(물주기 피드백 보존). + float dryLum = SoilLum((vals[0] + vals[1]) * 0.5f); + Apply(cfg, fis, farm, vals, 0, 1, dryLum); // 마름 한 쌍 + Apply(cfg, fis, farm, vals, 2, 3, dryLum); // 젖음 한 쌍 s_tonedFarms.Add(farm); SoilFarmsToned++; @@ -442,17 +444,28 @@ namespace WL.Look.Farm } } - static void Apply(WLIslandLookSettings cfg, System.Reflection.FieldInfo[] fis, FIFarm farm, Color[] v, int a, int b) + static void Apply(WLIslandLookSettings cfg, System.Reflection.FieldInfo[] fis, FIFarm farm, Color[] v, int a, int b, float dryLum) { var mid = (v[a] + v[b]) * 0.5f; float c = cfg.soilCheckerContrast; - var ca = Color.Lerp(mid, v[a], c); - var cb = Color.Lerp(mid, v[b], c); - fis[a].SetValue(farm, Mul(ca, cfg.soilTint)); - fis[b].SetValue(farm, Mul(cb, cfg.soilTint)); + var ca = Mul(Color.Lerp(mid, v[a], c), cfg.soilTint); + var cb = Mul(Color.Lerp(mid, v[b], c), cfg.soilTint); + + // 816r — 목표 흙색으로 통째로 옮긴다. 칸 차이(ca−mid, cb−mid)와 마름↔젖음 밝기비는 보존. + if (cfg.soilTargetEnabled != 0 && dryLum > 0.0001f) + { + var baseMid = Mul(mid, cfg.soilTint); + float k = SoilLum(mid) / dryLum; // 마름 = 1 · 젖음 = 젖음/마름 + var t = cfg.soilTargetColor * k; + ca = new Color(t.r + (ca.r - baseMid.r), t.g + (ca.g - baseMid.g), t.b + (ca.b - baseMid.b), ca.a); + cb = new Color(t.r + (cb.r - baseMid.r), t.g + (cb.g - baseMid.g), t.b + (cb.b - baseMid.b), cb.a); + } + fis[a].SetValue(farm, ca); + fis[b].SetValue(farm, cb); } static Color Mul(Color a, Color b) { return new Color(a.r * b.r, a.g * b.g, a.b * b.b, a.a); } + static float SoilLum(Color c) { return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b; } // ───────────────────────────────────────────────────────────────── // ③ 아레나와 같은 외곽선·대비·무드 (씬 비의존 — 816a 실측) diff --git a/Assets/WL/Look/Farm/WLIslandLookSettings.cs b/Assets/WL/Look/Farm/WLIslandLookSettings.cs index c5459a8d5..012854290 100644 --- a/Assets/WL/Look/Farm/WLIslandLookSettings.cs +++ b/Assets/WL/Look/Farm/WLIslandLookSettings.cs @@ -149,6 +149,15 @@ namespace WL.Look.Farm "🔴 0 으로 두지 말 것 — 어디를 갈았는지 안 보인다.")] [Range(0f, 1f)] public float soilCheckerContrast = 0.6f; + // ── 816r: 밭 흙색을 데모 흙 톤으로 「옮긴다」 (곱셈 틴트로는 못 가는 색) + [Tooltip("0 = 816f 상태(곱셈 틴트만) · 1 이면 아래 목표색으로 밭 바닥색을 옮긴다. " + + "칸 차이(어디를 갈았나)와 마름↔젖음 밝기비는 그대로 보존한다.")] + public int soilTargetEnabled = 1; + + [Tooltip("밭 「마름」 바닥의 목표색(리니어). 816r 실측 — 데모 흙 화면색에 맞춘 값. " + + "젖은 흙은 이 색에 원본의 젖음/마름 밝기비를 곱해 자동으로 어두워진다.")] + public Color soilTargetColor = new Color(0.3049f, 0.4033f, 0.2961f, 1f); + [Header("§0 — 조명 값 (816a 실측 = 데모/아레나와 동일)")] public Color ambientSky = new Color(0.212f, 0.227f, 0.259f, 1f); public Color ambientEquator = new Color(0.114f, 0.125f, 0.133f, 1f);