Project_WL/AgentScripts/WL816q_Play.cs

269 lines
15 KiB
C#
Raw Permalink Normal View History

// WL-816q — 「그라데이션(구름 띠)이 왜 안 보이나」 요소별 ON/OFF 실측.
// 에셋 무변경(SO·머티리얼은 전부 런타임 복제본). Debug.LogError 금지.
public static class WL816q_Play
{
public static void Start()
{
var go = UnityEngine.GameObject.Find("~WL816qPlay");
if (go != null) UnityEngine.Object.DestroyImmediate(go);
go = new UnityEngine.GameObject("~WL816qPlay");
go.AddComponent<WL816q_PlayRunner>();
}
}
public class WL816q_PlayRunner : UnityEngine.MonoBehaviour
{
static System.Text.StringBuilder sb;
const int W = 1080, H = 1920;
const int TS = 768;
static string Dir = "Screenshots_WL/WL816q";
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 src = WL.Look.Farm.WLIslandLookSettings.Instance;
var rt = UnityEngine.Object.Instantiate(src);
// scatter 정의를 새 인스턴스로 깊은 복사 — 에셋 쪽 객체를 절대 만지지 않는다
if (rt.scatter != null)
{
var arr = new WL.Look.Farm.WLScatterDef[rt.scatter.Length];
for (int i = 0; i < arr.Length; i++)
{
var s = rt.scatter[i];
arr[i] = new WL.Look.Farm.WLScatterDef
{
enabled_ = s.enabled_, label = s.label, mesh = s.mesh, material = s.material,
probability = s.probability, scale = s.scale, normalOffset = s.normalOffset, density = s.density
};
}
rt.scatter = arr;
}
g.cfg = rt;
// ── 요소 진단 ────────────────────────────────────────────────
L("=== 요소 진단 === t=" + UnityEngine.Time.timeSinceLevelLoad);
L("ⓑ WLReferenceLook.IsApplied=" + WL.Look.Arena.WLReferenceLook.IsApplied
+ " · Outlined=" + WL.Look.Arena.WLReferenceLook.OutlinedRenderers
+ " · Contrast=" + WL.Look.Arena.WLReferenceLook.ContrastMaterials);
int hull = 0, toonR = 0;
var rends = UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(UnityEngine.FindObjectsSortMode.None);
for (int i = 0; i < rends.Length; i++)
{
var ms = rends[i].sharedMaterials;
for (int m = 0; m < ms.Length; m++)
{
if (ms[m] == null || ms[m].shader == null) continue;
if (ms[m].shader.name == "WL/HullOutline") hull++;
if (ms[m].shader.name.Contains("Toon")) toonR++;
}
}
L("ⓑ 씬 안 HullOutline 머티리얼 슬롯=" + hull + " · Toon 슬롯=" + toonR);
var vols = UnityEngine.Object.FindObjectsByType<UnityEngine.Rendering.Volume>(UnityEngine.FindObjectsSortMode.None);
L("ⓓ Volume 개수=" + vols.Length + (vols.Length > 0 ? " 첫 프로필=" + (vols[0].sharedProfile ? vols[0].sharedProfile.name : "-") : ""));
var sun = UnityEngine.RenderSettings.sun;
var lights = UnityEngine.Object.FindObjectsByType<UnityEngine.Light>(UnityEngine.FindObjectsSortMode.None);
for (int i = 0; i < lights.Length; i++)
if (lights[i].type == UnityEngine.LightType.Directional)
L("ⓐ Directional " + lights[i].name + " shadows=" + lights[i].shadows + " int=" + lights[i].intensity + " rot=" + lights[i].transform.eulerAngles);
var top = rt.islandTopMaterial;
var grassMat = (rt.scatter != null && rt.scatter.Length > 0) ? rt.scatter[0].material : null;
L("ⓒⓔ 바닥=" + (top ? top.name : "-") + " sh=" + (top && top.shader ? top.shader.name : "-") + " " + MatDump(top));
L("ⓒⓔ 풀 =" + (grassMat ? grassMat.name : "-") + " sh=" + (grassMat && grassMat.shader ? grassMat.shader.name : "-") + " " + MatDump(grassMat));
L("ⓔ 그림자비(Shadow/Diffuse) 바닥=" + RatioStr(top) + " · 풀=" + RatioStr(grassMat));
var b = g.CalculateInstancesBounds();
L("섬 바운즈 center=" + b.center + " size=" + b.size);
var camTop = MakeTop(b.center.x, b.center.z, 6f);
var camPd = MakePd();
L("탑다운 = ortho6 @(" + b.center.x.ToString("F2") + ",60," + b.center.z.ToString("F2") + ") · PD = 12m/45°/fov60");
// ── A: 풀 끔 (바닥만) ───────────────────────────────────────
rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null;
L("[A 바닥만] " + Shot(camTop, TS, TS, "A_top_groundonly") + " | " + Stat());
Shot(camPd, W, H, "A_pd_groundonly");
// ── B: 현재 풀 (816n 상태) ──────────────────────────────────
rt.grassEnabled = 1; g.Rebuild(); yield return null; yield return null;
L("[B 현재풀] " + Shot(camTop, TS, TS, "B_top_before") + " | " + Stat());
Shot(camPd, W, H, "B_pd_before");
// ── C: 풀 그림자색만 데모 비율로 (다른 값 전부 동일) ────────
if (grassMat != null)
{
var clone = new UnityEngine.Material(grassMat);
clone.name = grassMat.name + "_816qTest";
var dif = clone.GetColor("_DiffuseColor");
clone.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(dif.r * 0.3232f, dif.g * 0.3414f, dif.b * 0.3948f, dif.a));
for (int i = 0; i < rt.scatter.Length; i++)
if (rt.scatter[i].material == grassMat) rt.scatter[i].material = clone;
g.Rebuild(); yield return null; yield return null;
L("[C 풀그림자=데모비율] " + Shot(camTop, TS, TS, "C_top_shadowfix") + " | " + Stat());
Shot(camPd, W, H, "C_pd_shadowfix");
}
// ── D: 참고 — 데모 기준 캡처에 같은 지표 ────────────────────
Ref("Screenshots_WL/WL816f/x_demo_ground.png", "D 데모참고(스케일 미상)");
Ref("Screenshots_WL/WL816f/x_ours_ground.png", "D 우리참고(스케일 미상)");
Flush();
}
// ─────────────────────────────────────────── 지표
// LF = 8×8 블록 평균 밝기의 표준편차(= 띠 세기) · HF = 블록 내 편차 평균(= 포기 잡음)
// levels = 블록 밝기를 8단계로 양자화했을 때 면적 0.5% 이상인 단계 수(= 띠 단계)
static string Metric(UnityEngine.Color32[] px, int w, int h)
{
int bs = 8, bw = w / bs, bh = h / bs;
var bm = new float[bw * bh];
var bok = new bool[bw * bh];
double hf = 0; int hfn = 0;
for (int by = 0; by < bh; by++)
for (int bx = 0; bx < bw; bx++)
{
double s = 0; int n = 0;
for (int y = 0; y < bs; y++)
for (int x = 0; x < bs; x++)
{
var p = px[(by * bs + y) * w + (bx * bs + x)];
if (!Ground(p)) continue;
s += Lum(p); n++;
}
if (n < bs * bs / 2) continue;
float mean = (float)(s / n);
bm[by * bw + bx] = mean; bok[by * bw + bx] = true;
for (int y = 0; y < bs; y++)
for (int x = 0; x < bs; x++)
{
var p = px[(by * bs + y) * w + (bx * bs + x)];
if (!Ground(p)) continue;
hf += System.Math.Abs(Lum(p) - mean); hfn++;
}
}
double bs1 = 0, bs2 = 0; int bn = 0;
var lv = new int[40];
for (int i = 0; i < bm.Length; i++) { if (!bok[i]) continue; bs1 += bm[i]; bs2 += (double)bm[i] * bm[i]; bn++; int q = (int)(bm[i] / 8f); if (q >= 0 && q < 40) lv[q]++; }
if (bn == 0 || hfn == 0) return "지면 픽셀 0";
double mu = bs1 / bn;
double lf = System.Math.Sqrt(System.Math.Max(0, bs2 / bn - mu * mu));
int levels = 0; for (int i = 0; i < 40; i++) if (lv[i] >= bn * 0.005) levels++;
// 에지% — 인접 픽셀 밝기차 8 이상
int ed = 0, edn = 0;
for (int y = 0; y < h - 1; y++)
for (int x = 0; x < w - 1; x++)
{
var p = px[y * w + x]; if (!Ground(p)) continue;
var r = px[y * w + x + 1]; var d = px[(y + 1) * w + x];
edn++;
if ((Ground(r) && System.Math.Abs(Lum(p) - Lum(r)) > 8) || (Ground(d) && System.Math.Abs(Lum(p) - Lum(d)) > 8)) ed++;
}
return string.Format("지면 {0:F1}% · 띠세기LF={1:F2} · 포기잡음HF={2:F2} · LF/HF={3:F2} · 띠단계={4} · 에지%={5:F2} · 평균밝기={6:F1}",
100.0 * edn / (w * h), lf, hf / hfn, lf / System.Math.Max(0.01, hf / hfn), levels, 100.0 * ed / System.Math.Max(1, edn), mu);
}
static bool Ground(UnityEngine.Color32 p) { return p.g > p.b + 8 && p.g > 45; } // 초록 지면(물·하늘 제외)
static float Lum(UnityEngine.Color32 p) { return 0.299f * p.r + 0.587f * p.g + 0.114f * p.b; }
static void Ref(string path, string tag)
{
if (!System.IO.File.Exists(path)) { L(tag + " : 파일 없음 " + path); return; }
var tex = new UnityEngine.Texture2D(2, 2, UnityEngine.TextureFormat.RGB24, false);
if (!UnityEngine.ImageConversion.LoadImage(tex, System.IO.File.ReadAllBytes(path))) { L(tag + " : 읽기 실패"); return; }
L(tag + " " + tex.width + "x" + tex.height + " : " + Metric(tex.GetPixels32(), tex.width, tex.height));
UnityEngine.Object.DestroyImmediate(tex);
}
static string MatDump(UnityEngine.Material m)
{
if (m == null) return "";
return "Shades=" + F(m, "_Shades") + " Bright=" + F(m, "_Brightness") + " MinDark=" + F(m, "_MinimumDarkness")
+ " CloudStr=" + F(m, "_Cloud_Strength") + " CloudDens=" + F(m, "_Cloud_Density") + " Cover=" + F(m, "_Cloud_Cover")
+ " Change=" + F(m, "_Cloud_Change") + " Move=" + (m.HasProperty("_Cloud_Movement") ? m.GetVector("_Cloud_Movement").ToString() : "-")
+ " Dif=" + C(m, "_DiffuseColor") + " Shd=" + C(m, "_ShadowDiffuseColor")
+ " kw=[" + string.Join(",", m.shaderKeywords) + "]";
}
static string F(UnityEngine.Material m, string n) { return m.HasProperty(n) ? m.GetFloat(n).ToString("F4") : "-"; }
static string C(UnityEngine.Material m, string n) { if (!m.HasProperty(n)) return "-"; var c = m.GetColor(n); return "(" + c.r.ToString("F3") + "," + c.g.ToString("F3") + "," + c.b.ToString("F3") + ")"; }
static string RatioStr(UnityEngine.Material m)
{
if (m == null || !m.HasProperty("_DiffuseColor") || !m.HasProperty("_ShadowDiffuseColor")) return "-";
var d = m.GetColor("_DiffuseColor"); var s = m.GetColor("_ShadowDiffuseColor");
return "(" + Safe(s.r, d.r) + "," + Safe(s.g, d.g) + "," + Safe(s.b, d.b) + ")";
}
static string Safe(float a, float b) { return b < 0.001f ? "-" : (a / b).ToString("F3"); }
static string Stat()
{
return "인스턴스=" + WL.Look.Farm.WLIslandGrass.Instances + " 삼각형=" + WL.Look.Farm.WLIslandGrass.Triangles
+ " 드로우콜+" + WL.Look.Farm.WLIslandGrass.DrawnConfigs + " 타일=" + WL.Look.Farm.WLIslandGrass.Tiles;
}
// ─────────────────────────────────────────── 카메라·캡처
static UnityEngine.Camera MakeTop(float cx, float cz, float size)
{
var go = UnityEngine.GameObject.Find("~816qTop");
if (go == null) go = new UnityEngine.GameObject("~816qTop");
var c = go.GetComponent<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("~816qPd");
if (go == null) go = new UnityEngine.GameObject("~816qPd");
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 string Shot(UnityEngine.Camera cam, int w, int h, string name)
{
var tex = Grab(cam, w, h);
System.IO.File.WriteAllBytes(Dir + "/" + name + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex));
string s = Metric(tex.GetPixels32(), w, h);
UnityEngine.Object.DestroyImmediate(tex);
return s;
}
static void L(string s) { sb.AppendLine(s); Flush(); }
static void Flush()
{
System.IO.File.WriteAllText("AgentScripts/WL816q_PLAY.txt", sb.ToString());
UnityEngine.Debug.Log("[816q play]\n" + sb.ToString());
}
}