Project_WL/AgentScripts/WL816j_Verify.cs

377 lines
22 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-816j — ② PD 경로(구조 재현)에서 무늬 3단 · 움직임 3단 · 가장자리 물보라 · 성능 전/후
// 🔴 Play 중 에셋(.mat/.asset)을 고치지 않는다 — 런타임 Material 인스턴스만 만들어 렌더러에 건다(§9).
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Rendering.Universal;
using UnityEngine.SceneManagement;
public static class WL816j_Verify
{
public const string Dir = "Screenshots_WL/WL816j/";
public const int W = 1080, H = 1920;
public const float PlaneW = 1000f; // 실측
public const float Cells = 6f; // 보로노이 1타일에 든 칸 수(실측)
public static void Open()
{
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(
"Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single);
Debug.Log("[816j] InGame 열림");
}
public static void Start()
{
var go = GameObject.Find("~WL816jVerify");
if (go != null) Object.DestroyImmediate(go);
go = new GameObject("~WL816jVerify");
go.AddComponent<WL816j_VerifyRunner>();
Debug.Log("[816j] verify 러너 시작");
}
// ── 카메라 ─────────────────────────────────────────────────────
public static Camera Cam(string name, Vector3 pos, Vector3 euler, float fov)
{
var go = GameObject.Find(name);
if (go == null) { go = new GameObject(name); go.hideFlags = HideFlags.DontSave; }
var c = go.GetComponent<Camera>(); if (c == null) c = go.AddComponent<Camera>();
c.orthographic = false; c.fieldOfView = fov; c.nearClipPlane = 0.3f; c.farClipPlane = 2000f;
c.clearFlags = CameraClearFlags.Skybox; c.cullingMask = ~0; c.depth = -100f;
go.transform.position = pos; go.transform.rotation = Quaternion.Euler(euler);
var ac = go.GetComponent<UniversalAdditionalCameraData>();
if (ac == null) ac = go.AddComponent<UniversalAdditionalCameraData>();
ac.renderPostProcessing = true;
return c;
}
/// <summary>🔴 자로 재는 카메라 — 정사영 · 바다만 · 1 m 가 정확히 몇 픽셀인지 안다.</summary>
public static Camera RulerCam(float orthoSize)
{
var go = GameObject.Find("~WL816jRuler");
if (go == null) { go = new GameObject("~WL816jRuler"); go.hideFlags = HideFlags.DontSave; }
var c = go.GetComponent<Camera>(); if (c == null) c = go.AddComponent<Camera>();
c.orthographic = true; c.orthographicSize = orthoSize;
c.nearClipPlane = 0.3f; c.farClipPlane = 400f; c.clearFlags = CameraClearFlags.SolidColor;
c.backgroundColor = Color.black; c.cullingMask = ~0; c.depth = -100f;
go.transform.position = new Vector3(-160f, 60f, -160f); // 섬 밖 열린 바다
go.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
var ac = go.GetComponent<UniversalAdditionalCameraData>();
if (ac == null) ac = go.AddComponent<UniversalAdditionalCameraData>();
ac.renderPostProcessing = false; // 자로 잴 때는 후처리 없이
return c;
}
public static Camera WaterCam() { return Cam("~WL816jWaterCam", new Vector3(-16f, 5.5f, -16f), new Vector3(10f, 45f, 0f), 45f); }
public static Camera HorizonCam(){ return Cam("~WL816jHorizonCam", new Vector3(-14f, 8f, -14f), new Vector3(4f, 45f, 0f), 55f); }
// ── 캡처 ───────────────────────────────────────────────────────
public static Texture2D Grab(Camera cam, int w, int h)
{
var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
rt.antiAliasing = 1; rt.Create();
var pT = cam.targetTexture; var pA = RenderTexture.active;
cam.targetTexture = rt; cam.Render(); RenderTexture.active = rt;
var tex = new Texture2D(w, h, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(0, 0, w, h), 0, 0); tex.Apply();
RenderTexture.active = pA; cam.targetTexture = pT;
rt.Release(); Object.DestroyImmediate(rt);
return tex;
}
public static string Shot(Camera cam, string path) { return Shot(cam, path, W, H); }
public static string Shot(Camera cam, string path, int w, int h)
{
if (cam == null) return "카메라 없음";
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path));
var tex = Grab(cam, w, h);
System.IO.File.WriteAllBytes(path, tex.EncodeToPNG());
string s = "평균=" + Band(tex, 0f, 1f);
Object.DestroyImmediate(tex);
return s + " → " + path;
}
public static string Band(Texture2D t, float y0, float y1)
{
int a = Mathf.RoundToInt(y0 * (t.height - 1)), b = Mathf.RoundToInt(y1 * (t.height - 1));
var px = t.GetPixels32(); long r = 0, g = 0, bl = 0; int n = 0;
for (int y = a; y < b; y += 3) for (int x = 0; x < t.width; x += 3)
{ var c = px[y * t.width + x]; r += c.r; g += c.g; bl += c.b; n++; }
if (n == 0) return "-";
return "#" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(bl / n)).ToString("X2");
}
/// <summary>흰 픽셀(거품) 비율 % — 물보라가 실제로 생겼는지 숫자로.</summary>
public static float FoamPct(Camera cam, int w, int h)
{
var t = Grab(cam, w, h); var px = t.GetPixels32(); int n = 0;
for (int i = 0; i < px.Length; i++) if (px[i].r > 215 && px[i].g > 215 && px[i].b > 215) n++;
Object.DestroyImmediate(t);
return 100f * n / px.Length;
}
public static float Ms(Camera cam, int frames)
{
var rt = new RenderTexture(W, H, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
rt.antiAliasing = 1; rt.Create();
var pT = cam.targetTexture; 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 = pT; rt.Release(); Object.DestroyImmediate(rt);
return (float)sw.Elapsed.TotalMilliseconds / frames;
}
public static void Strip(string[] paths, string outPath)
{
var ts = new Texture2D[paths.Length]; int w = 0, h = 0;
for (int i = 0; i < paths.Length; i++)
{ ts[i] = Load(paths[i]); if (ts[i] == null) return; w += ts[i].width; h = Mathf.Max(h, ts[i].height); }
var o = new Texture2D(w, h, TextureFormat.RGB24, false);
var fill = new Color32[w * h];
for (int i = 0; i < fill.Length; i++) fill[i] = new Color32(24, 24, 28, 255);
o.SetPixels32(fill);
int x = 0;
for (int i = 0; i < ts.Length; i++) { o.SetPixels32(x, 0, ts[i].width, ts[i].height, ts[i].GetPixels32()); x += ts[i].width; }
o.Apply();
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outPath));
System.IO.File.WriteAllBytes(outPath, o.EncodeToPNG());
Object.DestroyImmediate(o);
for (int i = 0; i < ts.Length; i++) Object.DestroyImmediate(ts[i]);
}
public static void Pair(string a, string b, string o) { Strip(new[] { a, b }, o); }
public static Texture2D Load(string p)
{
if (!System.IO.File.Exists(p)) return null;
var t = new Texture2D(2, 2, TextureFormat.RGB24, false);
t.LoadImage(System.IO.File.ReadAllBytes(p));
return t;
}
// ── 값 만들기 ──────────────────────────────────────────────────
public const string R_Size = "Vector1_07238257334b4a149e675e2451801030";
public const string R_Speed = "Vector1_8e22a98f75c94b218c49e6c4805e799d";
public const string R_Strength = "Vector1_99cc56b5c6fd474082f07f00a6df94f9";
public const string R_NoiseSpeed = "Vector1_ad7e0fcd37f44d68b68de6f06bb43b96";
public const string R_FoamScale = "Vector1_11671d7a4cdb4d059a6c69e39f3f7b3f";
public const string R_FoamSpeed = "Vector1_3089b6a325a44c1686907af4ee3b0776";
public const string R_FoamDistance = "Vector1_101dc4546b684d40bc2ff2fc59ab43d5";
public const string R_FoamEdge = "Vector1_34f757bec6b8422b9aef3b9d97117a6e";
public const string R_WaweNormalSize = "Vector1_68f0c21b684f4b7c9ebef8789113b107";
public const string R_WaweNormalSpeed = "Vector1_85ea9246e72a44ed9e0482861767c826";
public const string R_WaweNormalStrength = "Vector1_2b4963cef1de4859983ffcbae94c6f60";
/// <summary>무늬 1칸을 cellM 미터로, 무늬가 흐르는 속도를 flowMs m/s 로 (색·거품은 손대지 않는다).</summary>
public static Material Make(Material src, string name, float cellM, float flowMs)
{
var m = new Material(src); m.name = name;
float size = PlaneW / (cellM * Cells);
m.SetFloat(R_Size, size);
m.SetFloat(R_Strength, 0.3085f / size); // 텍스처 기준 뒤틀림량을 816h 와 똑같이 유지
m.SetFloat(R_Speed, flowMs * size / PlaneW);
m.SetFloat(R_NoiseSpeed, flowMs * size / PlaneW);
return m;
}
/// <summary>가장자리 거품 — 거품알 크기 foamM(m) · 띠 폭 bandM(깊이차 m) · 흐름 churnMs(m/s).</summary>
public static void Foam(Material m, float foamM, float bandM, float churnMs)
{
float fs = PlaneW / foamM;
m.SetFloat(R_FoamScale, fs);
m.SetFloat(R_FoamEdge, 4f);
m.SetFloat(R_FoamDistance, bandM * 4f); // edge=saturate(d/FoamDistance)*FoamEdge → 띠 = FoamDistance/FoamEdge
m.SetFloat(R_FoamSpeed, churnMs * fs / PlaneW);
}
}
public class WL816j_VerifyRunner : MonoBehaviour
{
static StringBuilder sb;
static void L(string s) { sb.AppendLine(s); }
const string D = "Screenshots_WL/WL816j/";
const string MatDir = "Assets/WL/Look/Farm/Materials/";
Renderer water; Camera live;
void Start() { StartCoroutine(Co()); }
IEnumerator Co()
{
sb = new StringBuilder();
L("=== WL-816j 확인 (PD 경로 구조 재현 = InGame 활성 + Level01 Additive) ===");
bool viaGame = false;
try { if (InGameInfo.Ins != null) { InGameInfo.Ins.Load_Map(900); viaGame = true; } }
catch (System.Exception) { }
L("Load_Map(900) = " + (viaGame ? "성공" : "🔴 로그인 없이는 못 탄다 → LoadSceneAsync(Level01, Additive) 로 대체(816f·816h 와 같음)"));
if (!viaGame)
{
var op = SceneManager.LoadSceneAsync("Level01", LoadSceneMode.Additive);
while (op != null && !op.isDone) yield return null;
}
for (int i = 0; i < 7; i++) yield return new WaitForSeconds(1f);
foreach (var r in Object.FindObjectsByType<Renderer>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
if (r != null && r.gameObject.scene.name == "Level01" && r.name == "Water") water = r;
foreach (var c in Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
if (!c.gameObject.activeInHierarchy || !c.enabled || c.targetTexture != null) continue;
if (c.name.StartsWith("~WL816j")) continue;
if (live == null || c.depth > live.depth) live = c;
}
L("물 렌더러 = " + (water == null ? "🔴 없음" : water.name + " mat=" + water.sharedMaterial.name + " shader=" + water.sharedMaterial.shader.name)
+ " · 물 교체 " + WL.Look.Farm.WLIslandLook.WaterSwapped + " · 보이는 카메라 = " + (live ? live.name : "없음"));
var baseMat = water != null ? water.sharedMaterial : null; // = 816h 채택 Farm_Water_WL_C
if (baseMat == null || live == null) { Done(); yield break; }
// ── 0) 해안선 찾기 (물보라 확대용) ──────────────────────────
float shoreX = 0f, shoreZ = -3.78f;
for (float x = 4f; x < 90f; x += 0.5f)
{
RaycastHit hit;
bool land = Physics.Raycast(new Vector3(x, 60f, shoreZ), Vector3.down, out hit, 200f) && hit.point.y > -0.9f;
if (land) shoreX = x;
}
L("해안선(z=" + shoreZ + " 에서 +X 방향 마지막 육지) x = " + shoreX.ToString("F1"));
var shoreCam = WL816j_Verify.Cam("~WL816jShore", new Vector3(shoreX + 11f, 1.6f, shoreZ + 8f), new Vector3(9f, 215f, 0f), 26f);
// ── 1) 지금(816h 채택 C) ────────────────────────────────────
L("");
L("1) 지금 = " + baseMat.name + " (Size " + baseMat.GetFloat(WL816j_Verify.R_Size).ToString("G4")
+ " → 무늬 1칸 " + (1000f / (baseMat.GetFloat(WL816j_Verify.R_Size) * 6f)).ToString("F1") + " m"
+ " · FoamScale " + baseMat.GetFloat(WL816j_Verify.R_FoamScale).ToString("G4") + ")");
L(" 게임화면 " + WL816j_Verify.Shot(live, D + "a_now_game.png"));
L(" 바다근접 " + WL816j_Verify.Shot(WL816j_Verify.WaterCam(), D + "a_now_water.png"));
L(" 자(정사영 40 m 폭 · 1 m = 27.0 px) " + WL816j_Verify.Shot(WL816j_Verify.RulerCam(20f), D + "a_now_ruler.png", 1080, 1080));
L(" 가장자리 확대 " + WL816j_Verify.Shot(shoreCam, D + "a_now_shore.png"));
L(" 가장자리 흰픽셀 = " + WL816j_Verify.FoamPct(shoreCam, 540, 960).ToString("F2") + " %");
// ── 2) 무늬 3단 (움직임은 지금과 같은 1.28 m/s 로 고정) ─────
L("");
L("2) 무늬 3단 — 1칸 3 m / 1.67 m / 1 m (움직임은 전부 1.28 m/s 로 고정 · 색·거품 그대로)");
float[] cells = { 3f, 1.67f, 1f };
var sizeShots = new string[4]; var rulerShots = new string[4];
sizeShots[0] = D + "a_now_water.png"; rulerShots[0] = D + "a_now_ruler.png";
var mats = new Material[3];
for (int i = 0; i < 3; i++)
{
mats[i] = WL816j_Verify.Make(baseMat, "J_cell" + cells[i], cells[i], 1.28f);
water.sharedMaterial = mats[i];
yield return null; yield return null; yield return new WaitForSeconds(0.3f);
sizeShots[i + 1] = D + "b_cell" + (i + 1) + "_water.png";
rulerShots[i + 1] = D + "b_cell" + (i + 1) + "_ruler.png";
L(" [1칸 " + cells[i] + " m · Size " + mats[i].GetFloat(WL816j_Verify.R_Size).ToString("F0")
+ " · Strength " + mats[i].GetFloat(WL816j_Verify.R_Strength).ToString("G3") + "]");
L(" 바다근접 " + WL816j_Verify.Shot(WL816j_Verify.WaterCam(), sizeShots[i + 1]));
L(" 자 " + WL816j_Verify.Shot(WL816j_Verify.RulerCam(20f), rulerShots[i + 1], 1080, 1080));
}
WL816j_Verify.Strip(sizeShots, D + "b_pattern_steps.png");
WL816j_Verify.Strip(rulerShots, D + "b_pattern_steps_ruler.png");
L(" 3단 한 장 = b_pattern_steps.png(바다근접) · b_pattern_steps_ruler.png(자 · 좌부터 지금/3 m/1.67 m/1 m)");
// ── 3) 움직임 3단 (무늬는 1.67 m 고정) ──────────────────────
L("");
L("3) 움직임 3단 — 0.4 / 0.8 / 1.5 m/s (무늬 1칸 1.67 m 고정) · 1.0 초 간격 두 장씩(흐른 거리 확인용)");
float[] flows = { 0.4f, 0.8f, 1.5f };
var flowShots = new string[3];
for (int i = 0; i < 3; i++)
{
var m = WL816j_Verify.Make(baseMat, "J_flow" + flows[i], 1.67f, flows[i]);
water.sharedMaterial = m;
yield return null; yield return null; yield return new WaitForSeconds(0.3f);
string p0 = D + "c_flow" + (i + 1) + "_t0.png";
string p1 = D + "c_flow" + (i + 1) + "_t1.png";
float t0 = Time.time;
WL816j_Verify.Shot(WL816j_Verify.RulerCam(10f), p0, 1080, 1080);
yield return new WaitForSeconds(1f);
float dt = Time.time - t0;
WL816j_Verify.Shot(WL816j_Verify.RulerCam(10f), p1, 1080, 1080);
flowShots[i] = p1;
L(" [" + flows[i] + " m/s] dt=" + dt.ToString("F2") + " s · 두 장 = " + p0 + " , " + p1
+ " (정사영 20 m 폭 · 1 m = 54.0 px)");
}
WL816j_Verify.Strip(flowShots, D + "c_flow_steps.png");
// ── 4) 가장자리 물보라 — 거품알 3단 + 띠 폭 3단 ─────────────
L("");
L("4) 🔴 섬 가장자리 물보라 = 셰이더에 **이미 있는 Foam**(깊이 기반)의 값만 조정 — 새 시스템 0");
float[] foamCell = { 1.5f, 0.75f, 0.375f };
var foamShots = new string[4]; foamShots[0] = D + "a_now_shore.png";
for (int i = 0; i < 3; i++)
{
var m = WL816j_Verify.Make(baseMat, "J_foam" + i, 1.67f, 0.8f);
WL816j_Verify.Foam(m, foamCell[i], 0.55f, 0.5f);
water.sharedMaterial = m;
yield return null; yield return null; yield return new WaitForSeconds(0.3f);
foamShots[i + 1] = D + "d_foamcell" + (i + 1) + ".png";
L(" [거품알 " + foamCell[i] + " m · FoamScale " + m.GetFloat(WL816j_Verify.R_FoamScale).ToString("F0") + "] "
+ WL816j_Verify.Shot(shoreCam, foamShots[i + 1])
+ " 흰픽셀 " + WL816j_Verify.FoamPct(shoreCam, 540, 960).ToString("F2") + " %");
}
WL816j_Verify.Strip(foamShots, D + "d_foam_cell_steps.png");
float[] band = { 0.55f, 1.1f, 1.8f };
var bandShots = new string[3];
for (int i = 0; i < 3; i++)
{
var m = WL816j_Verify.Make(baseMat, "J_band" + i, 1.67f, 0.8f);
WL816j_Verify.Foam(m, 0.75f, band[i], 0.5f);
water.sharedMaterial = m;
yield return null; yield return null; yield return new WaitForSeconds(0.3f);
bandShots[i] = D + "d_band" + (i + 1) + ".png";
L(" [띠 폭(깊이차) " + band[i] + " m · FoamDistance " + m.GetFloat(WL816j_Verify.R_FoamDistance).ToString("G3") + "] "
+ WL816j_Verify.Shot(shoreCam, bandShots[i])
+ " 흰픽셀 " + WL816j_Verify.FoamPct(shoreCam, 540, 960).ToString("F2") + " %");
}
WL816j_Verify.Strip(bandShots, D + "d_foam_band_steps.png");
// ── 5) 잔물결 노멀(공짜 — 텍스처는 이미 뽑고 있고 Strength 0 이라 꺼져 있었다) ──
L("");
L("5) 잔물결 노멀맵 on/off — 셰이더가 이미 샘플링하는 텍스처(WaweNormalStrength 0 이라 꺼져 있던 것)");
var mOff = WL816j_Verify.Make(baseMat, "J_normOff", 1.67f, 0.8f); WL816j_Verify.Foam(mOff, 0.75f, 1.1f, 0.5f);
var mOn = new Material(mOff); mOn.name = "J_normOn";
mOn.SetFloat(WL816j_Verify.R_WaweNormalSize, 250f); // 잔물결 1타일 4 m
mOn.SetFloat(WL816j_Verify.R_WaweNormalSpeed, 0.075f); // 4 m × 0.075 = 0.3 m/s
mOn.SetFloat(WL816j_Verify.R_WaweNormalStrength, 0.3f);
string[] nShots = new string[2];
var pairM = new[] { mOff, mOn };
for (int i = 0; i < 2; i++)
{
water.sharedMaterial = pairM[i];
yield return null; yield return null; yield return new WaitForSeconds(0.3f);
nShots[i] = D + "e_normal_" + (i == 0 ? "off" : "on") + ".png";
L(" [" + (i == 0 ? "off(지금)" : "on 4 m·0.3") + "] " + WL816j_Verify.Shot(WL816j_Verify.WaterCam(), nShots[i]));
}
WL816j_Verify.Strip(nShots, D + "e_normal_pair.png");
// ── 6) 성능 — 전(816h C) / 후(후보) 교대 2회 ────────────────
L("");
L("6) 성능 (같은 카메라 1080×1920 · 60프레임 오프스크린 · 순서 교대 2회)");
var cand = mOff; // 후보 = 무늬 1.67 m · 흐름 0.8 · 거품알 0.75 m · 띠 1.1 m
for (int round = 0; round < 2; round++)
{
foreach (var pr in new[] { new object[] { "전(816h C)", baseMat }, new object[] { "후(816j 후보)", cand } })
{
water.sharedMaterial = pr[1] as Material;
yield return null; yield return null;
L(" " + round + "회차 " + (string)pr[0] + " = " + WL816j_Verify.Ms(live, 60).ToString("F3") + " ms/frame");
}
}
L(" 드로우콜/렌더러 변화 = 같은 평면 1장 · 머티리얼 1개 · 셰이더 동일 → 0 (값만 다름)");
// ── 7) 후보로 마무리 캡처 ───────────────────────────────────
water.sharedMaterial = cand; yield return null; yield return new WaitForSeconds(0.3f);
L("");
L("7) 후보 적용 화면");
L(" 게임화면 " + WL816j_Verify.Shot(live, D + "f_after_game.png"));
L(" 바다근접 " + WL816j_Verify.Shot(WL816j_Verify.WaterCam(), D + "f_after_water.png"));
L(" 수평선 " + WL816j_Verify.Shot(WL816j_Verify.HorizonCam(), D + "f_after_horizon.png"));
L(" 가장자리 " + WL816j_Verify.Shot(shoreCam, D + "f_after_shore.png")
+ " 흰픽셀 " + WL816j_Verify.FoamPct(shoreCam, 540, 960).ToString("F2") + " %");
L(" 자 " + WL816j_Verify.Shot(WL816j_Verify.RulerCam(20f), D + "f_after_ruler.png", 1080, 1080));
WL816j_Verify.Pair(D + "a_now_game.png", D + "f_after_game.png", D + "g_side_by_side.png");
WL816j_Verify.Pair(D + "a_now_shore.png", D + "f_after_shore.png", D + "g_shore_before_after.png");
L(" 좌우 = g_side_by_side.png(좌 지금/우 후보) · g_shore_before_after.png(좌 지금/우 후보 · 가장자리 확대)");
water.sharedMaterial = baseMat; yield return null;
Done();
}
void Done()
{
System.IO.File.WriteAllText("AgentScripts/WL816j_VERIFY.txt", sb.ToString());
Debug.Log("[816j verify 완료]\n" + sb);
}
}