Merge branch 'wl/gameplay/WL-816r-dirt-look'

This commit is contained in:
깃 관리자 2026-09-15 09:51:56 +09:00
commit fad1270c48
14 changed files with 1745 additions and 11 deletions

254
AgentScripts/WL816r_Demo.cs Normal file
View File

@ -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<UnityEngine.Terrain>();
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<UnityEngine.Vector3>();
var grassPts = new System.Collections.Generic.List<UnityEngine.Vector3>();
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<float, float, float> hAt = (u, v) => terrain.transform.position.y + td.GetInterpolatedHeight(u, v);
System.Func<float, float, float> 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<UnityEngine.Material>(
"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<UnityEngine.Material>(
"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<UnityEngine.Material>();
var probes = new System.Collections.Generic.List<UnityEngine.Vector3>();
var probeTag = new System.Collections.Generic.List<string>();
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<UnityEngine.MeshRenderer>().sharedMaterial = m;
UnityEngine.Object.DestroyImmediate(q.GetComponent<UnityEngine.Collider>());
probes.Add(pos); probeTag.Add(BaseTag[i] + " #" + BaseHex[i]);
}
// ── 카메라: 흙 무게중심 위 탑다운 직교 ─────────────────────────────────
var camGo = new UnityEngine.GameObject("~816rCam");
var cam = camGo.AddComponent<UnityEngine.Camera>();
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<string, int>();
foreach (var r in UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(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<UnityEngine.Vector3> 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<UnityEngine.Vector3> pts)
{
var uniq = new System.Collections.Generic.Dictionary<int, int>();
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<System.Collections.Generic.KeyValuePair<int, int>>(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());
}
}

View File

@ -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<UnityEngine.Terrain>();
var td = terrain.terrainData;
int ar = td.alphamapResolution;
var a = td.GetAlphamaps(0, 0, ar, ar);
// 흙 100 % + 평탄 점 중, 가장 촘촘히 모인 곳(= 넓은 흙 마당)을 고른다
var dirt = new System.Collections.Generic.List<UnityEngine.Vector3>();
var grass = new System.Collections.Generic.List<UnityEngine.Vector3>();
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<UnityEngine.Material>(
"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<UnityEngine.Material>();
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<UnityEngine.MeshRenderer>().sharedMaterial = m;
UnityEngine.Object.DestroyImmediate(q.GetComponent<UnityEngine.Collider>());
}
var camGo = new UnityEngine.GameObject("~816rC");
var cam = camGo.AddComponent<UnityEngine.Camera>();
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<UnityEngine.Vector3> pts)
{
var uniq = new System.Collections.Generic.Dictionary<int, int>();
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<System.Collections.Generic.KeyValuePair<int, int>>(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); }
}

View File

@ -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<WL816r_Fin>();
}
}
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.Transform>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None))
if (t.name == "Road")
foreach (var r in t.GetComponentsInChildren<UnityEngine.Renderer>(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<UnityEngine.Renderer>();
foreach (var r in UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(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<UnityEngine.Camera>(); if (c == null) c = go.AddComponent<UnityEngine.Camera>();
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());
}
}

372
AgentScripts/WL816r_Play.cs Normal file
View File

@ -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<WL816r_Runner>();
}
}
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<WL.Look.Farm.WLIslandGrass>(UnityEngine.FindObjectsInactive.Include);
if (g == null) { L("WLIslandGrass 없음 — 중단"); Flush(); yield break; }
var b = g.CalculateInstancesBounds();
// ── 그룹 수집 ────────────────────────────────────────────────────────
var roadR = new System.Collections.Generic.List<UnityEngine.Renderer>();
var soilR = new System.Collections.Generic.List<UnityEngine.Renderer>();
var topR = new System.Collections.Generic.List<UnityEngine.Renderer>();
var cfg = WL.Look.Farm.WLIslandLookSettings.Instance;
var topMat = cfg.islandTopMaterial;
foreach (var r in UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(UnityEngine.FindObjectsSortMode.None))
{
if (r == null || !r.enabled) continue;
if (r.GetComponent<CryingSnow.FarmingIsland.Soil>() != 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<UnityEngine.Material>();
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<UnityEngine.Material>();
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<UnityEngine.Material>(roadMats);
for (int ci = 0; ci < roadCand.Length; ci++)
{
var clones = new System.Collections.Generic.List<UnityEngine.Material>();
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<CryingSnow.FarmingIsland.Farm>(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<CryingSnow.FarmingIsland.Soil>(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<UnityEngine.Renderer> soilR)
{
var s = new System.Collections.Generic.HashSet<string>();
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<UnityEngine.Renderer> 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<UnityEngine.Renderer> road,
System.Collections.Generic.List<UnityEngine.Renderer> soil,
System.Collections.Generic.List<UnityEngine.Renderer> top)
{
var unlit = UnityEngine.Shader.Find("Universal Render Pipeline/Unlit");
var saved = new System.Collections.Generic.Dictionary<UnityEngine.Renderer, UnityEngine.Material[]>();
var mats = new System.Collections.Generic.List<UnityEngine.Material>();
System.Action<System.Collections.Generic.List<UnityEngine.Renderer>, 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<UnityEngine.Renderer>();
foreach (var r in UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(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<int, int>();
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<System.Collections.Generic.KeyValuePair<int, int>>(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<UnityEngine.Camera>(); if (c == null) c = go.AddComponent<UnityEngine.Camera>();
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<UnityEngine.Camera>(); if (c == null) c = go.AddComponent<UnityEngine.Camera>();
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());
}
}

View File

@ -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<WL816r_Runner2>();
}
}
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<CryingSnow.FarmingIsland.Farm>(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<CryingSnow.FarmingIsland.Soil>(true))
{
soilAll++;
var r = s.GetComponent<UnityEngine.Renderer>();
if (r != null && r.gameObject.activeInHierarchy && r.enabled) soilActive++;
if (soilMat == null && r != null) soilMat = r.sharedMaterial;
}
var roadTfs = new System.Collections.Generic.List<UnityEngine.Transform>();
foreach (var t in UnityEngine.Object.FindObjectsByType<UnityEngine.Transform>(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<UnityEngine.Renderer>(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<UnityEngine.Renderer>();
foreach (var r in UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(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<UnityEngine.Material>();
var rends = new System.Collections.Generic.List<UnityEngine.Renderer>();
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<UnityEngine.MeshRenderer>();
rr.sharedMaterial = m; rends.Add(rr);
UnityEngine.Object.DestroyImmediate(q.GetComponent<UnityEngine.Collider>());
}
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<UnityEngine.Renderer> 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<UnityEngine.Renderer> top)
{
var unlit = UnityEngine.Shader.Find("Universal Render Pipeline/Unlit");
var saved = new System.Collections.Generic.Dictionary<UnityEngine.Renderer, UnityEngine.Material[]>();
var mats = new System.Collections.Generic.List<UnityEngine.Material>();
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.Renderer>(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<int, int>();
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<System.Collections.Generic.KeyValuePair<int, int>>(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<UnityEngine.Camera>(); if (c == null) c = go.AddComponent<UnityEngine.Camera>();
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<UnityEngine.Camera>(); if (c == null) c = go.AddComponent<UnityEngine.Camera>();
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());
}
}

View File

@ -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<WL816r_Runner3>();
}
}
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<UnityEngine.Renderer>();
foreach (var r in UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(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<UnityEngine.Renderer> 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<UnityEngine.Renderer> top)
{
var unlit = UnityEngine.Shader.Find("Universal Render Pipeline/Unlit");
var saved = new System.Collections.Generic.Dictionary<UnityEngine.Renderer, UnityEngine.Material[]>();
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.Renderer>(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<UnityEngine.Camera>(); if (c == null) c = go.AddComponent<UnityEngine.Camera>();
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());
}
}

View File

@ -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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eba171966111423bbabd5fc93575b339
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -95,9 +95,9 @@ Material:
m_Colors: m_Colors:
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0} - _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
- _Cloud_Step: {r: 13, g: 17, 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} - _NormalBias: {r: 1, g: 1, b: 1, a: 0}
- _Outline: {r: 0, g: 0, b: 0, 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_BuildTextureStacks: []
m_AllowLocking: 1 m_AllowLocking: 1

View File

@ -23,7 +23,7 @@ MonoBehaviour:
to: {fileID: 2100000, guid: 68107aa66ca3855458e2ac55c239d57c, type: 2} to: {fileID: 2100000, guid: 68107aa66ca3855458e2ac55c239d57c, type: 2}
- enabled_: 1 - enabled_: 1
from: {fileID: 2100000, guid: 8b9c3fe585ec43742a0e29e3b3c82f6b, type: 2} from: {fileID: 2100000, guid: 8b9c3fe585ec43742a0e29e3b3c82f6b, type: 2}
to: {fileID: 2100000, guid: 197cbcbf4165c954887272f7aea2ac42, type: 2} to: {fileID: 2100000, guid: eba171966111423bbabd5fc93575b339, type: 2}
- enabled_: 1 - enabled_: 1
from: {fileID: 2100000, guid: ee0c382820516a74ba37852af34ecd11, type: 2} from: {fileID: 2100000, guid: ee0c382820516a74ba37852af34ecd11, type: 2}
to: {fileID: 2100000, guid: ece3895a184497040812f320d69cb967, type: 2} to: {fileID: 2100000, guid: ece3895a184497040812f320d69cb967, type: 2}
@ -36,11 +36,13 @@ MonoBehaviour:
- enabled_: 1 - enabled_: 1
from: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2} from: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2}
to: {fileID: 2100000, guid: 1c9ce9b7865f6d643ad9601f6fa81ba3, 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 skipSoilRenderers: 1
soilToneEnabled: 1 soilToneEnabled: 1
soilTint: {r: 0.84, g: 0.86, b: 0.9, a: 1} soilTint: {r: 0.84, g: 0.86, b: 0.9, a: 1}
soilCheckerContrast: 0.6 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} ambientSky: {r: 0.212, g: 0.227, b: 0.259, a: 1}
ambientEquator: {r: 0.114, g: 0.125, b: 0.133, 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} ambientGround: {r: 0.047, g: 0.043, b: 0.035, a: 1}

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

View File

@ -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:

View File

@ -427,8 +427,10 @@ namespace WL.Look.Farm
} }
if (!ok) continue; 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); s_tonedFarms.Add(farm);
SoilFarmsToned++; 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; var mid = (v[a] + v[b]) * 0.5f;
float c = cfg.soilCheckerContrast; float c = cfg.soilCheckerContrast;
var ca = Color.Lerp(mid, v[a], c); var ca = Mul(Color.Lerp(mid, v[a], c), cfg.soilTint);
var cb = Color.Lerp(mid, v[b], c); var cb = Mul(Color.Lerp(mid, v[b], c), cfg.soilTint);
fis[a].SetValue(farm, Mul(ca, cfg.soilTint));
fis[b].SetValue(farm, Mul(cb, cfg.soilTint)); // 816r — 목표 흙색으로 통째로 옮긴다. 칸 차이(camid, cbmid)와 마름↔젖음 밝기비는 보존.
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 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 실측) // ③ 아레나와 같은 외곽선·대비·무드 (씬 비의존 — 816a 실측)

View File

@ -149,6 +149,15 @@ namespace WL.Look.Farm
"🔴 0 으로 두지 말 것 — 어디를 갈았는지 안 보인다.")] "🔴 0 으로 두지 말 것 — 어디를 갈았는지 안 보인다.")]
[Range(0f, 1f)] public float soilCheckerContrast = 0.6f; [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 실측 = 데모/아레나와 동일)")] [Header("§0 — 조명 값 (816a 실측 = 데모/아레나와 동일)")]
public Color ambientSky = new Color(0.212f, 0.227f, 0.259f, 1f); public Color ambientSky = new Color(0.212f, 0.227f, 0.259f, 1f);
public Color ambientEquator = new Color(0.114f, 0.125f, 0.133f, 1f); public Color ambientEquator = new Color(0.114f, 0.125f, 0.133f, 1f);