255 lines
14 KiB
C#
255 lines
14 KiB
C#
// 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());
|
||
}
|
||
}
|