Project_WL/AgentScripts/WL816o_Play.cs

281 lines
14 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// WL-816o — 구름 그림자 경계 배치: 전(816n) / 후 실측 + CPU 포팅 검증.
// 에셋은 건드리지 않는다 (cfg 는 Instantiate 복제본 · 816f/816n 방식).
public static class WL816o_Play
{
public static void Start()
{
var go = UnityEngine.GameObject.Find("~WL816oPlay");
if (go != null) UnityEngine.Object.DestroyImmediate(go);
go = new UnityEngine.GameObject("~WL816oPlay");
go.AddComponent<WL816o_PlayRunner>();
}
}
public class WL816o_PlayRunner : UnityEngine.MonoBehaviour
{
static System.Text.StringBuilder sb;
const int W = 1080, H = 1920;
const int TW = 512;
void Start() { StartCoroutine(Co()); }
System.Collections.IEnumerator Co()
{
sb = new System.Text.StringBuilder();
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); // 에셋 무변경
g.cfg = rt;
var mat = rt.cloudEdgeSourceMaterial != null ? rt.cloudEdgeSourceMaterial : rt.islandTopMaterial;
L("바닥 머티리얼 = " + (mat ? mat.name : "-") + " · 셰이더 " + (mat && mat.shader ? mat.shader.name : "-"));
L("SO 값 : cell=" + rt.cloudEdgeCell + " maxCells=" + rt.cloudEdgeMaxCells + " dilate=" + rt.cloudEdgeDilate
+ " driftCover=" + rt.cloudEdgeDriftCover + " refresh=" + rt.cloudEdgeRefreshSeconds + "s densityScale=" + rt.cloudEdgeDensityScale);
var cam = MakeCam();
L("카메라 " + cam.transform.position + " euler=" + cam.transform.eulerAngles + " fov=" + cam.fieldOfView + " (타깃까지 12 m · 45°)");
// ── BEFORE (816n 상태 = 균일 격자) ─────────────────────────────
rt.cloudEdgeEnabled = 0;
g.Rebuild(); yield return null; yield return null;
L(Shot(cam, "a_before", "BEFORE 816n(균일 격자)"));
L(" " + Stat() + " · " + Bench(cam, 60).ToString("F3") + " ms/frame");
int bInst = WL.Look.Farm.WLIslandGrass.Instances;
long bTri = WL.Look.Farm.WLIslandGrass.Triangles;
int bDraw = WL.Look.Farm.WLIslandGrass.DrawnConfigs;
// ── AFTER (구름 경계 배치) ─────────────────────────────────────
rt.cloudEdgeEnabled = 1;
g.Rebuild(); yield return null; yield return null;
L(Shot(cam, "b_after", "AFTER 816o(구름 경계 배치)"));
L(" " + Stat() + " · " + Bench(cam, 60).ToString("F3") + " ms/frame");
int aInst = WL.Look.Farm.WLIslandGrass.Instances;
long aTri = WL.Look.Farm.WLIslandGrass.Triangles;
int aDraw = WL.Look.Farm.WLIslandGrass.DrawnConfigs;
L(" 로그: " + WL.Look.Farm.WLIslandGrass.LastLog);
// 교대 2회 (측정 노이즈 확인)
rt.cloudEdgeEnabled = 0; g.Rebuild(); yield return null;
float b2 = Bench(cam, 60);
rt.cloudEdgeEnabled = 1; g.Rebuild(); yield return null;
float a2 = Bench(cam, 60);
L("[교대 2회] before=" + b2.ToString("F3") + " ms · after=" + a2.ToString("F3") + " ms");
L("[예산] 인스턴스 " + bInst + " → " + aInst + " (" + Pct(aInst, bInst) + ")"
+ " · 삼각형 " + bTri + " → " + aTri + " (" + Pct((int)aTri, (int)bTri) + ")"
+ " · 드로우콜 +" + bDraw + " → +" + aDraw
+ " · 후보 " + WL.Look.Farm.WLIslandGrass.Candidates);
// 주기 갱신 비용
var sw = System.Diagnostics.Stopwatch.StartNew();
g.RefreshCloudEdges();
sw.Stop();
L("[주기 갱신] RefreshCloudEdges 1회 = " + sw.Elapsed.TotalMilliseconds.ToString("F2") + " ms"
+ " (굽기 " + WL.Look.Farm.WLIslandGrass.BakeMs.ToString("F2") + " · 고르기 " + WL.Look.Farm.WLIslandGrass.FilterMs.ToString("F2")
+ ") → 인스턴스 " + WL.Look.Farm.WLIslandGrass.Instances);
// ── CPU 포팅 검증 (셰이더가 그린 띠 경계 ↔ CPU 가 계산한 띠 경계) ──
L(Verify(g, rt, mat));
// 최종 상태 = 구름 경계 ON
rt.cloudEdgeEnabled = 1; g.Rebuild(); yield return null;
Shot(cam, "b_after", "final");
TopShot(g, "t_after");
rt.cloudEdgeEnabled = 0; g.Rebuild(); yield return null;
TopShot(g, "t_before");
rt.cloudEdgeEnabled = 1; g.Rebuild(); yield return null;
Flush();
}
// ─────────────────────────────────────────── 검증
static string Verify(WL.Look.Farm.WLIslandGrass g, WL.Look.Farm.WLIslandLookSettings rt, UnityEngine.Material mat)
{
var f = new WL.Look.Farm.WLCloudShadowField();
if (!f.Configure(mat)) return "[검증] 실패 — " + f.Why;
var b = g.CalculateInstancesBounds();
float size = UnityEngine.Mathf.Max(b.size.x, b.size.z) * 0.5f;
float cx = b.center.x, cz = b.center.z, y = b.center.y;
bool was = g.enabled;
g.enabled = false; // 풀을 빼고 바닥만 본다
var cam = MakeTop(cx, cz, size);
var tex = GrabCam(cam, TW, TW);
g.enabled = was;
var px = tex.GetPixels32();
// 픽셀 ↔ 월드 (ortho · Euler(90,0,0) → screen +x = world +x, screen +y = world +z)
var band = new int[TW * TW];
var ok = new bool[TW * TW];
for (int iy = 0; iy < TW; iy++)
{
float wz = cz + (((iy + 0.5f) / TW) - 0.5f) * 2f * size;
for (int ix = 0; ix < TW; ix++)
{
float wx = cx + (((ix + 0.5f) / TW) - 0.5f) * 2f * size;
int i = iy * TW + ix;
band[i] = f.Band(wx, y, wz);
var p = px[i];
ok[i] = (p.g > p.b + 12 && p.g > p.r + 8 && p.g > 60); // 풀밭 초록만
}
}
// CPU 가 「여기가 경계」라고 한 곳에 화면에도 색 계단이 있는가 (이 방향이 포팅 검증이다)
int cpuEdges = 0, cpuHit = 0, flatTot = 0, flatHit = 0;
for (int iy = 1; iy < TW - 1; iy++)
{
for (int ix = 3; ix < TW - 4; ix++)
{
int i = iy * TW + ix;
if (!ok[i - 3] || !ok[i + 4]) continue;
if (band[i] != band[i + 1])
{
cpuEdges++;
if (Stepy(px, i - 3, 7)) cpuHit++;
}
else if (band[i - 3] == band[i] && band[i] == band[i + 4])
{
flatTot++; // CPU 가 평평하다고 한 곳
if (Stepy(px, i - 3, 7)) flatHit++; // 그런데 화면에 계단이 있으면 오차
}
}
}
UnityEngine.Object.DestroyImmediate(tex);
string s = "[검증] CPU 가 띠 경계라 한 " + cpuEdges + "곳 중 화면에도 색 계단이 있는 것 = "
+ (cpuEdges > 0 ? (100f * cpuHit / cpuEdges).ToString("F1") : "0") + "%"
+ " · CPU 가 평평하다 한 " + flatTot + "곳 중 화면에 계단이 있는 것 = "
+ (flatTot > 0 ? (100f * flatHit / flatTot).ToString("F1") : "0") + "% (낮을수록 좋다 · 타일·소품 경계 포함)";
s += "\n (_Time.y=" + UnityEngine.Shader.GetGlobalVector("_Time").y.ToString("F3")
+ " · timeSinceLevelLoad=" + UnityEngine.Time.timeSinceLevelLoad.ToString("F3")
+ " · 띠비율=" + (WL.Look.Farm.WLIslandGrass.EdgeFraction * 100f).ToString("F1") + "%"
+ " · 칸=" + WL.Look.Farm.WLIslandGrass.EdgeCellSize.ToString("F2") + "m×" + WL.Look.Farm.WLIslandGrass.EdgeCells
+ " · 노이즈=" + WL.Look.Farm.WLIslandGrass.EdgeSampleCell.ToString("F2") + "m×" + WL.Look.Farm.WLIslandGrass.EdgeSamples
+ " · 넓힘=" + WL.Look.Farm.WLIslandGrass.EdgeDilate + ")";
return s;
}
static int Lum(UnityEngine.Color32 p) { return (p.r * 77 + p.g * 150 + p.b * 29) >> 8; }
/// <summary>i 부터 n 픽셀 안에 6~45 크기의 밝기 계단이 있는가.</summary>
static bool Stepy(UnityEngine.Color32[] px, int i, int n)
{
for (int k = 0; k < n - 1; k++)
{
int d = Lum(px[i + k + 1]) - Lum(px[i + k]); if (d < 0) d = -d;
if (d >= 6 && d <= 45) return true;
}
return false;
}
static string Pct(int a, int b) { return b <= 0 ? "-" : ((100f * a / b).ToString("F1") + "%"); }
static string Stat()
{
return "인스턴스=" + WL.Look.Farm.WLIslandGrass.Instances
+ " 삼각형=" + WL.Look.Farm.WLIslandGrass.Triangles
+ " 드로우콜+" + WL.Look.Farm.WLIslandGrass.DrawnConfigs
+ " 타일=" + WL.Look.Farm.WLIslandGrass.Tiles;
}
// ─────────────────────────────────────────── helpers
static UnityEngine.Camera MakeCam()
{
UnityEngine.Vector3 tgt = UnityEngine.Vector3.zero;
var pc = UnityEngine.GameObject.FindGameObjectWithTag("Player");
if (pc != null) tgt = pc.transform.position;
var go = UnityEngine.GameObject.Find("~816oCam");
if (go == null) go = new UnityEngine.GameObject("~816oCam");
var c = go.GetComponent<UnityEngine.Camera>();
if (c == null) c = go.AddComponent<UnityEngine.Camera>();
c.fieldOfView = 60f; c.orthographic = false;
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.Camera MakeTop(float cx, float cz, float size)
{
var go = UnityEngine.GameObject.Find("~816oTop");
if (go == null) go = new UnityEngine.GameObject("~816oTop");
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, 40f, cz);
c.enabled = false;
return c;
}
static UnityEngine.Texture2D GrabCam(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 TopShot(WL.Look.Farm.WLIslandGrass g, string name)
{
var b = g.CalculateInstancesBounds();
float size = UnityEngine.Mathf.Max(b.size.x, b.size.z) * 0.5f;
var cam = MakeTop(b.center.x, b.center.z, size);
var tex = GrabCam(cam, 768, 768);
System.IO.Directory.CreateDirectory("Screenshots_WL/WL816o");
System.IO.File.WriteAllBytes("Screenshots_WL/WL816o/" + name + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex));
UnityEngine.Object.DestroyImmediate(tex);
}
static string Shot(UnityEngine.Camera cam, string name, string tag)
{
var tex = GrabCam(cam, W, H);
System.IO.Directory.CreateDirectory("Screenshots_WL/WL816o");
System.IO.File.WriteAllBytes("Screenshots_WL/WL816o/" + name + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex));
var px = tex.GetPixels32();
long r = 0, gg = 0, bb = 0; int n = 0;
for (int i = 0; i < px.Length; i++)
{
var p = px[i];
if (p.g > p.b + 12 && p.g > p.r + 8 && p.g > 60) { r += p.r; gg += p.g; bb += p.b; n++; }
}
UnityEngine.Object.DestroyImmediate(tex);
if (n == 0) return tag + " : 초록 픽셀 0";
return string.Format("{0} : 풀밭 초록 픽셀 {1} ({2:F1}%) 평균 #{3:X2}{4:X2}{5:X2}", tag, n, 100f * n / px.Length, r / n, gg / n, bb / n);
}
static float Bench(UnityEngine.Camera cam, int frames)
{
var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB);
rt.Create(); cam.targetTexture = rt;
for (int i = 0; i < 10; i++) cam.Render();
var sw = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < frames; i++) cam.Render();
sw.Stop(); cam.targetTexture = null;
rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
return (float)sw.Elapsed.TotalMilliseconds / frames;
}
static void L(string s) { sb.AppendLine(s); }
static void Flush()
{
System.IO.File.WriteAllText("AgentScripts/WL816o_PLAY.txt", sb.ToString());
UnityEngine.Debug.Log("[816o play]\n" + sb.ToString());
}
}