Project_WL/AgentScripts/WL816j_Verify.cs

377 lines
22 KiB
C#
Raw Permalink Normal View History

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