// WL-816n — ① 기준 캡처(c_demo_vs_island_ground.png) 이미지 분석: 데모 풀 대비 · 경계 집중도 // ② 섬 바닥/풀 머티리얼 *_Arena 복사본 생성(구름 속도 감속) + SO 값 적용(밀도 = 데모 2.5) // ③ 섬 전/후 렌더(PD 구도 12 m · 45° · fov 60) public static class WL816n_Apply { const string TOP_D = "Assets/WL/Look/Farm/Materials/Farm_IslandTop_Demo.mat"; const string TOP_A = "Assets/WL/Look/Farm/Materials/Farm_IslandTop_Arena.mat"; const string GR_D = "Assets/WL/Look/Farm/Materials/Farm_Grass_Demo.mat"; const string GR_A = "Assets/WL/Look/Farm/Materials/Farm_Grass_Arena.mat"; const string SO_P = "Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset"; const int W = 540, H = 960; static System.Text.StringBuilder sb = new System.Text.StringBuilder(); // ─────────────────────────────────────────── 1) 기준 이미지 분석 public static void Analyze() { sb.Length = 0; var path = "Screenshots_WL/WL816f/c_demo_vs_island_ground.png"; var bytes = System.IO.File.ReadAllBytes(path); var tex = new UnityEngine.Texture2D(2, 2, UnityEngine.TextureFormat.RGB24, false); UnityEngine.ImageConversion.LoadImage(tex, bytes); int w = tex.width, h = tex.height; var px = tex.GetPixels32(); sb.AppendLine("기준 캡처 " + w + "x" + h + " (좌 = 데모 · 우 = 우리 섬)"); Half(px, w, h, 10, w / 2 - 12, 40, h - 60, "데모(좌)"); Half(px, w, h, w / 2 + 12, w - 10, 40, h - 60, "우리섬(우)"); Flush("AgentScripts/WL816n_IMG.txt"); UnityEngine.Object.DestroyImmediate(tex); } // 로컬 중앙값(=띠 색) 대비 어두운 이탈 픽셀 = 풀 실루엣. 띠 경계 근처 / 평면 내부로 갈라 센다. static void Half(UnityEngine.Color32[] px, int w, int h, int x0, int x1, int y0, int y1, string tag) { int R = 9; // 로컬 창 반경(풀 포기보다 크게) long tuft = 0, tot = 0, tuftNearEdge = 0, nearEdge = 0; double dsum = 0; long lr = 0, lg = 0, lb = 0; // 바닥(띠) 평균 long gr = 0, gg = 0, gb = 0; long gn = 0; for (int y = y0 + R; y < y1 - R; y += 2) for (int x = x0 + R; x < x1 - R; x += 2) { // 로컬 밝기 중앙값 근사 = 창 안 최대값 쪽 25% 평균(풀이 어두우므로 바닥이 밝은 쪽) int mx = 0; long s = 0; int c = 0; for (int dy = -R; dy <= R; dy += 3) for (int dx = -R; dx <= R; dx += 3) { var p = px[(y + dy) * w + (x + dx)]; int L = p.r + p.g + p.b; if (L > mx) mx = L; s += L; c++; } int mean = (int)(s / c); var q = px[y * w + x]; int lum = q.r + q.g + q.b; tot++; lr += q.r; lg += q.g; lb += q.b; // 창 안 밝기 폭 = 띠 경계 지표(경계면 창 안 명암 차가 크다) bool edgeZone = (mx - mean) > 14; if (edgeZone) nearEdge++; if (lum < mx - 18) // 바닥보다 뚜렷이 어두움 = 풀 실루엣 { tuft++; dsum += (double)lum / mx; gr += q.r; gg += q.g; gb += q.b; gn++; if (edgeZone) tuftNearEdge++; } } double cov = 100.0 * tuft / System.Math.Max(1, tot); double ne = 100.0 * nearEdge / System.Math.Max(1, tot); double tne = 100.0 * tuftNearEdge / System.Math.Max(1, tuft); sb.AppendLine(string.Format("{0}: 표본={1} 풀실루엣커버%={2:F2} 풀/바닥 밝기비={3:F3} 바닥평균=#{4:X2}{5:X2}{6:X2} 풀평균=#{7:X2}{8:X2}{9:X2}", tag, tot, cov, gn == 0 ? 0 : dsum / gn, lr / System.Math.Max(1, tot), lg / System.Math.Max(1, tot), lb / System.Math.Max(1, tot), gn == 0 ? 0 : gr / gn, gn == 0 ? 0 : gg / gn, gn == 0 ? 0 : gb / gn)); sb.AppendLine(string.Format(" 경계대 면적%={0:F2} · 풀 중 경계대 비율={1:F1}% (균일 배치면 면적%와 같아야 한다 → 초과분 = 경계 집중)", ne, tne)); } // ─────────────────────────────────────────── 2) 값 적용 public static void Apply() { MakeMats(); SetSO(); } public static void MakeMats() { sb.Length = 0; // 머티리얼 복사본 if (!System.IO.File.Exists(TOP_A)) UnityEditor.AssetDatabase.CopyAsset(TOP_D, TOP_A); if (!System.IO.File.Exists(GR_A)) UnityEditor.AssetDatabase.CopyAsset(GR_D, GR_A); UnityEditor.AssetDatabase.Refresh(); var top = UnityEditor.AssetDatabase.LoadAssetAtPath(TOP_A); var gra = UnityEditor.AssetDatabase.LoadAssetAtPath(GR_A); var topD = UnityEditor.AssetDatabase.LoadAssetAtPath(TOP_D); var graD = UnityEditor.AssetDatabase.LoadAssetAtPath(GR_D); // 구름 그림자 = 데모 띠의 원천(실측). 움직임만 5배 느리게 — 얼룩 모양·세기는 데모 그대로. foreach (var m in new UnityEngine.Material[] { top, gra }) { if (m == null) continue; m.EnableKeyword("_CLOUDSENABLED"); m.SetFloat("_CLOUDSENABLED", 1f); m.SetFloat("_Cloud_Change", 0.001f); // 0.005 → 0.001 (5배 감속) m.SetVector("_Cloud_Movement", new UnityEngine.Vector4(0.2f, 0.2f, 0, 0)); // (1,1) → (0.2,0.2) m.SetFloat("_Cloud_Strength", 1f); m.SetFloat("_Cloud_Cover", 0.5f); m.SetFloat("_Cloud_Density", 0.01f); m.SetVector("_Cloud_Step", new UnityEngine.Vector4(13f, 17f, 0, 0)); } // 풀은 바닥보다 살짝 진하게 (기준 이미지 실측 비율 적용 — Analyze 로그 참조) if (gra != null && graD != null) { var d = graD.GetColor("_DiffuseColor"); var s = graD.GetColor("_ShadowDiffuseColor"); gra.SetColor("_DiffuseColor", Mul(d, GRASS_MUL)); gra.SetColor("_ShadowDiffuseColor", Mul(s, GRASS_MUL)); sb.AppendLine("풀색 " + Hex(d) + "/" + Hex(s) + " → " + Hex(Mul(d, GRASS_MUL)) + "/" + Hex(Mul(s, GRASS_MUL)) + " (x" + GRASS_MUL + ")"); } UnityEditor.EditorUtility.SetDirty(top); UnityEditor.EditorUtility.SetDirty(gra); UnityEditor.AssetDatabase.SaveAssets(); Flush("AgentScripts/WL816n_MATS.txt"); } public static void SetSO() { sb.Length = 0; var top = UnityEditor.AssetDatabase.LoadAssetAtPath(TOP_A); var gra = UnityEditor.AssetDatabase.LoadAssetAtPath(GR_A); var topD = UnityEditor.AssetDatabase.LoadAssetAtPath(TOP_D); var graD = UnityEditor.AssetDatabase.LoadAssetAtPath(GR_D); // SO 값 var so = UnityEditor.AssetDatabase.LoadAssetAtPath(SO_P); var sobj = new UnityEditor.SerializedObject(so); var mr = sobj.FindProperty("materialRemap"); for (int i = 0; i < mr.arraySize; i++) { var to = mr.GetArrayElementAtIndex(i).FindPropertyRelative("to"); if (to.objectReferenceValue == topD) { to.objectReferenceValue = top; sb.AppendLine("materialRemap[" + i + "].to → Farm_IslandTop_Arena"); } } sobj.FindProperty("islandTopMaterial").objectReferenceValue = top; var sc = sobj.FindProperty("scatter"); for (int i = 0; i < sc.arraySize; i++) { var e = sc.GetArrayElementAtIndex(i); var mp = e.FindPropertyRelative("material"); if (mp.objectReferenceValue == graD) mp.objectReferenceValue = gra; var dp = e.FindPropertyRelative("density"); var lb = e.FindPropertyRelative("label").stringValue; if (dp.floatValue > 0.5f) { sb.AppendLine("scatter[" + i + "](" + lb + ") density " + dp.floatValue + " → 2.5 (데모 FirstLayer 값)"); dp.floatValue = 2.5f; } } sobj.ApplyModifiedPropertiesWithoutUndo(); UnityEditor.EditorUtility.SetDirty(so); UnityEditor.AssetDatabase.SaveAssets(); sb.AppendLine("SO 저장 완료"); Flush("AgentScripts/WL816n_APPLY.txt"); } public static float GRASS_MUL = 0.93f; static UnityEngine.Color Mul(UnityEngine.Color c, float k) { return new UnityEngine.Color(c.r * k, c.g * k, c.b * k, c.a); } static string Hex(UnityEngine.Color c) { return "#" + UnityEngine.ColorUtility.ToHtmlStringRGB(c); } static void Flush(string p) { System.IO.File.WriteAllText(p, sb.ToString()); UnityEngine.Debug.Log("[816n]\n" + sb.ToString()); } }