// WL-816j — ③ 가장자리 물보라가 「값만으로」 되는지 실측 (가장 싼 해법 먼저) // 깊이 텍스처가 살아 있는지 · 섬 벽이 수직이라 띠가 0 폭인지 · FoamDistance 를 키우면 되는지. // 🔴 자기 완결 파일(run_script 는 파일 1개만 컴파일한다). using System.Collections; using System.Text; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public static class WL816j_Foam { public const string D = "Screenshots_WL/WL816j/"; public const float PlaneW = 1000f, Cells = 6f; 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_DeepDistance = "Vector1_566e288f42864b8e9432d81fbdb83f38"; public const string C_Light = "Color_1ec6cccf8ead489ebb917674f7e8b3b1"; public static void Start() { var go = GameObject.Find("~WL816jFoam"); if (go != null) Object.DestroyImmediate(go); go = new GameObject("~WL816jFoam"); go.AddComponent(); Debug.Log("[816j] foam 러너 시작"); } public static Material Make(Material src, float cellM, float flowMs) { var m = new Material(src); float size = PlaneW / (cellM * Cells); m.SetFloat(R_Size, size); m.SetFloat(R_Strength, 0.3085f / size); m.SetFloat(R_Speed, flowMs * size / PlaneW); m.SetFloat(R_NoiseSpeed, flowMs * size / PlaneW); return m; } 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, int w, int h) { System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); var t = Grab(cam, w, h); System.IO.File.WriteAllBytes(path, t.EncodeToPNG()); var px = t.GetPixels32(); long r = 0, g = 0, b = 0; int n = 0, white = 0; for (int i = 0; i < px.Length; i += 7) { r += px[i].r; g += px[i].g; b += px[i].b; n++; if (px[i].r > 215 && px[i].g > 215 && px[i].b > 215) white++; } Object.DestroyImmediate(t); return "평균 #" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(b / n)).ToString("X2") + " 흰 " + (100f * white / n).ToString("F2") + "% → " + path; } 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++) { if (!System.IO.File.Exists(paths[i])) return; ts[i] = new Texture2D(2, 2, TextureFormat.RGB24, false); ts[i].LoadImage(System.IO.File.ReadAllBytes(paths[i])); 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.File.WriteAllBytes(outPath, o.EncodeToPNG()); Object.DestroyImmediate(o); for (int i = 0; i < ts.Length; i++) Object.DestroyImmediate(ts[i]); } } public class WL816j_FoamRunner : MonoBehaviour { static StringBuilder sb; static void L(string s) { sb.AppendLine(s); } const string D = WL816j_Foam.D; void Start() { StartCoroutine(Co()); } IEnumerator Co() { sb = new StringBuilder(); L("=== WL-816j ③ 가장자리 물보라 — 값만으로 되는가 ==="); Renderer water = null; foreach (var r in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) if (r != null && r.gameObject.scene.name == "Level01" && r.name == "Water") water = r; Camera live = null; foreach (var c in Object.FindObjectsByType(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; } if (water == null || live == null) { L("🔴 물/카메라 없음"); Done(); yield break; } var baseMat = water.sharedMaterial; var urp = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset; L("1) 파이프라인 = " + (urp ? urp.name : "없음") + " · depthTexture=" + (urp ? urp.supportsCameraDepthTexture.ToString() : "?") + " · opaqueTexture=" + (urp ? urp.supportsCameraOpaqueTexture.ToString() : "?")); var acd = live.GetComponent(); L(" 게임 카메라 " + live.name + " pos=" + live.transform.position + " 각=" + live.transform.eulerAngles + " fov=" + live.fieldOfView + " · requiresDepth=" + (acd ? acd.requiresDepthOption.ToString() : "컴포넌트없음") + " · requiresColor=" + (acd ? acd.requiresColorOption.ToString() : "-")); L(" 물 머티리얼 queue=" + baseMat.renderQueue + " shader=" + baseMat.shader.name); // ── 2) 섬 가장자리 단면 ───────────────────────────────────── float zc = -3.78f, shoreX = 0f; for (float x = 4f; x < 90f; x += 0.25f) { RaycastHit h; if (Physics.Raycast(new Vector3(x, 60f, zc), Vector3.down, out h, 200f) && h.point.y > -0.9f) shoreX = x; } var prof = new StringBuilder(); for (float d = -2f; d <= 3.01f; d += 0.5f) { RaycastHit h; bool hit = Physics.Raycast(new Vector3(shoreX + d, 60f, zc), Vector3.down, out h, 200f); prof.Append(" " + d.ToString("F1") + "→" + (hit ? h.point.y.ToString("F2") + "(" + h.collider.name + ")" : "없음")); } L(""); L("2) 해안선 x=" + shoreX.ToString("F2") + " (z=" + zc + ") · 물 y=-1 · 바깥으로 가며 바닥 높이 =" + prof); var cgo = GameObject.Find("~WL816jShore2"); if (cgo == null) { cgo = new GameObject("~WL816jShore2"); cgo.hideFlags = HideFlags.DontSave; } var cam = cgo.GetComponent(); if (cam == null) cam = cgo.AddComponent(); cam.orthographic = false; cam.fieldOfView = 32f; cam.nearClipPlane = 0.1f; cam.farClipPlane = 2000f; cam.clearFlags = CameraClearFlags.Skybox; cam.cullingMask = ~0; cam.depth = -100f; var ad = cgo.GetComponent(); if (ad == null) ad = cgo.AddComponent(); ad.renderPostProcessing = true; cgo.transform.position = new Vector3(shoreX + 5.5f, 2.2f, zc + 5.5f); cgo.transform.LookAt(new Vector3(shoreX - 0.5f, -1f, zc - 0.5f)); L(" 확대 카메라 pos=" + cgo.transform.position); // ── 3) 깊이 살아 있나 ─────────────────────────────────────── L(""); L("3) 깊이 텍스처 동작 — DeepDistance 만 바꿔 화면색이 변하는가"); foreach (float dd in new float[] { 0.5f, 7f, 400f }) { var m = new Material(baseMat); m.SetFloat(WL816j_Foam.R_DeepDistance, dd); water.sharedMaterial = m; yield return null; yield return null; L(" DeepDistance " + dd + " → " + WL816j_Foam.Shot(cam, D + "h_deep_" + dd + ".png", 720, 1280)); } // ── 4) 값만으로 되는가 ────────────────────────────────────── L(""); L("4) 🔴 값만으로 되는가 — FoamEdge 1 고정 · FoamDistance 훑기 (무늬 1.67 m · 거품알 0.75 m)"); var sweep = new float[] { 2.2f, 6f, 20f, 60f, 200f }; var shots = new string[sweep.Length]; for (int i = 0; i < sweep.Length; i++) { var m = WL816j_Foam.Make(baseMat, 1.67f, 0.8f); m.SetFloat(WL816j_Foam.R_FoamScale, 1000f / 0.75f); m.SetFloat(WL816j_Foam.R_FoamSpeed, 0.5f * (1000f / 0.75f) / 1000f); m.SetFloat(WL816j_Foam.R_FoamEdge, 1f); m.SetFloat(WL816j_Foam.R_FoamDistance, sweep[i]); water.sharedMaterial = m; yield return null; yield return null; shots[i] = D + "h_foamdist" + i + ".png"; L(" FoamDistance " + sweep[i] + " → " + WL816j_Foam.Shot(cam, shots[i], 720, 1280)); } WL816j_Foam.Strip(shots, D + "h_foamdist_sweep.png"); // ── 5) 무늬 대비 3단 ──────────────────────────────────────── L(""); L("5) 무늬 대비 3단 — 흰 줄 밝기(LightColor)만. WaterColor/DarkColor 는 816h 채택값 그대로."); var lc = new Color[] { new Color(1f, 1f, 1f, 1f), new Color(0.80f, 0.90f, 0.96f, 1f), new Color(0.62f, 0.78f, 0.88f, 1f) }; var lcShots = new string[3]; for (int i = 0; i < 3; i++) { var m = WL816j_Foam.Make(baseMat, 1.67f, 0.8f); m.SetColor(WL816j_Foam.C_Light, lc[i]); water.sharedMaterial = m; yield return null; yield return null; lcShots[i] = D + "i_contrast" + (i + 1) + ".png"; L(" LightColor " + lc[i] + " → " + WL816j_Foam.Shot(live, lcShots[i], 1080, 1920)); } WL816j_Foam.Strip(lcShots, D + "i_contrast_steps.png"); // ── 6) 무늬 크기 (게임 카메라) ────────────────────────────── L(""); L("6) 무늬 크기 (게임 카메라 · 대비는 중간값) — 지금 21.3 m / 4 m / 2.5 m / 1.67 m"); var cellShots = new string[4]; float[] cs = { 21.3f, 4f, 2.5f, 1.67f }; for (int i = 0; i < 4; i++) { Material m; if (i == 0) m = baseMat; else { m = WL816j_Foam.Make(baseMat, cs[i], 0.8f); m.SetColor(WL816j_Foam.C_Light, lc[1]); } water.sharedMaterial = m; yield return null; yield return null; cellShots[i] = D + "j_cell_game" + i + ".png"; L(" 1칸 " + cs[i] + " m → " + WL816j_Foam.Shot(live, cellShots[i], 1080, 1920)); } WL816j_Foam.Strip(cellShots, D + "j_cell_game_steps.png"); water.sharedMaterial = baseMat; yield return null; Done(); } void Done() { System.IO.File.WriteAllText("AgentScripts/WL816j_FOAM.txt", sb.ToString()); Debug.Log("[816j foam]\n" + sb); } }