Project_WL/AgentScripts/WL816o_Play.cs

281 lines
14 KiB
C#
Raw Normal View History

// 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());
}
}