diff --git a/AgentScripts/WL816j2_BUILD.txt b/AgentScripts/WL816j2_BUILD.txt new file mode 100644 index 000000000..9e4c29794 --- /dev/null +++ b/AgentScripts/WL816j2_BUILD.txt @@ -0,0 +1,4 @@ +띠 텍스처 = Assets/WL/Look/Farm/Textures/WL_ShoreFoam.png (512×128 · U 반복 / V 고정 · mip on) +띠 머티리얼 = Assets/WL/Look/Farm/Materials/Farm_ShoreFoam.mat · shader Universal Render Pipeline/Unlit · queue 3050 +물 E = Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat · Size 166.67(무늬 1칸 1 m) · LightColor RGBA(0.420, 0.720, 0.845, 1.000) · FoamShadowColor 흰색 · FoamColor = 물색(깊이 거품 off) · FoamDistance 2.2 · FoamEdge 4 +SO waterTo = Farm_Water_WL_E · shoreFoamMaterial = Farm_ShoreFoam · 폭 1.6 m · 격자 0.5 m diff --git a/AgentScripts/WL816j2_Build.cs b/AgentScripts/WL816j2_Build.cs new file mode 100644 index 000000000..1e796f638 --- /dev/null +++ b/AgentScripts/WL816j2_Build.cs @@ -0,0 +1,157 @@ +// WL-816j2 — ① 띠 텍스처·머티리얼·물 머티리얼 E 를 만든다 (에디트 모드) +// 🔴 원본 셰이더/FI/씬 0줄. 전부 새 에셋(`Assets/WL/Look/Farm/**`)이다. +using System.Text; +using UnityEngine; +using UnityEditor; + +public static class WL816j2_Build +{ + const string MatDir = "Assets/WL/Look/Farm/Materials/"; + const string TexDir = "Assets/WL/Look/Farm/Textures/"; + const string TexPath = TexDir + "WL_ShoreFoam.png"; + const string FoamMat = MatDir + "Farm_ShoreFoam.mat"; + + const string R_FoamScale = "Vector1_11671d7a4cdb4d059a6c69e39f3f7b3f"; + const string R_FoamSpeed = "Vector1_3089b6a325a44c1686907af4ee3b0776"; + const string R_FoamDistance = "Vector1_101dc4546b684d40bc2ff2fc59ab43d5"; + const string R_FoamEdge = "Vector1_34f757bec6b8422b9aef3b9d97117a6e"; + const string C_Light = "Color_1ec6cccf8ead489ebb917674f7e8b3b1"; + const string C_Foam = "Color_e1f155248d144786a62fc1585ca21e9a"; + const string C_FoamShadow = "Color_2323d69962e04c499c5d5f0925432b55"; + + public static void Run() + { + var sb = new StringBuilder(); + + // ── ① 거품 띠 텍스처 (가로 = 해안을 따라 · 세로 v0 = 물가, v1 = 바깥) ── + System.IO.Directory.CreateDirectory(TexDir); + const int W = 512, H = 128; + var tex = new Texture2D(W, H, TextureFormat.RGBA32, false); + var px = new Color32[W * H]; + for (int y = 0; y < H; y++) + { + float v = y / (float)(H - 1); + float t = 1f - v; // 물가에서 1, 바깥에서 0 + for (int x = 0; x < W; x++) + { + float n = Fbm(x, y, W); // 0~1 · 가로로 이어진다 + // 🔴 Mathf.SmoothStep(a,b,t) 는 「a~b 사이 보간」이지 GLSL smoothstep 이 아니다(816j2 에서 이걸로 한 번 틀렸다). + float k = Mathf.Clamp01((t - 0.18f) / 0.30f + (n - 0.5f) * 1.0f); + float a = k * k * (3f - 2f * k); // 물가 = 1 · 바깥으로 가며 거품이 부서진다 + px[y * W + x] = new Color32(255, 255, 255, (byte)Mathf.RoundToInt(Mathf.Clamp01(a) * 255f)); + } + } + tex.SetPixels32(px); tex.Apply(); + System.IO.File.WriteAllBytes(TexPath, tex.EncodeToPNG()); + Object.DestroyImmediate(tex); + AssetDatabase.ImportAsset(TexPath, ImportAssetOptions.ForceUpdate); + var imp = AssetImporter.GetAtPath(TexPath) as TextureImporter; + if (imp != null) + { + imp.textureType = TextureImporterType.Default; + imp.alphaIsTransparency = true; + imp.alphaSource = TextureImporterAlphaSource.FromInput; + imp.wrapModeU = TextureWrapMode.Repeat; + imp.wrapModeV = TextureWrapMode.Clamp; + imp.filterMode = FilterMode.Bilinear; + imp.mipmapEnabled = true; + imp.maxTextureSize = 512; + imp.SaveAndReimport(); + } + sb.AppendLine("띠 텍스처 = " + TexPath + " (" + W + "×" + H + " · U 반복 / V 고정 · mip on)"); + + // ── ② 띠 머티리얼 (URP Unlit · 반투명 · ZWrite off · 물보다 뒤에) ── + var sh = Shader.Find("Universal Render Pipeline/Unlit"); + if (sh == null) { Debug.LogError("[816j2] URP Unlit 셰이더 없음"); return; } + var fm = AssetDatabase.LoadAssetAtPath(FoamMat); + if (fm == null) { fm = new Material(sh); AssetDatabase.CreateAsset(fm, FoamMat); } + fm.shader = sh; + fm.SetTexture("_BaseMap", AssetDatabase.LoadAssetAtPath(TexPath)); + fm.SetColor("_BaseColor", new Color(1f, 1f, 1f, 0.92f)); + fm.SetFloat("_Surface", 1f); // Transparent + fm.SetFloat("_Blend", 0f); // Alpha + fm.SetFloat("_AlphaClip", 0f); + fm.SetFloat("_SrcBlend", (float)UnityEngine.Rendering.BlendMode.SrcAlpha); + fm.SetFloat("_DstBlend", (float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + fm.SetFloat("_ZWrite", 0f); + fm.SetFloat("_Cull", (float)UnityEngine.Rendering.CullMode.Off); + fm.SetFloat("_QueueOffset", 50f); // 물(3000)보다 뒤 + fm.SetFloat("_QueueControl", 0f); // 0 = 사용자 지정(URP 가 자동으로 되돌리지 못하게) + fm.EnableKeyword("_SURFACE_TYPE_TRANSPARENT"); + fm.DisableKeyword("_ALPHATEST_ON"); + fm.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + fm.renderQueue = 3050; // 물(3000)보다 뒤에 그린다 + EditorUtility.SetDirty(fm); + sb.AppendLine("띠 머티리얼 = " + FoamMat + " · shader " + fm.shader.name + " · queue " + fm.renderQueue); + + // ── ③ 물 머티리얼 E = D 복사본 + 무늬 선 약화 + 깊이 거품 낮춤 ── + string dst = MatDir + "Farm_Water_WL_E.mat"; + if (AssetDatabase.LoadAssetAtPath(dst) == null) + { AssetDatabase.CopyAsset(MatDir + "Farm_Water_WL_D.mat", dst); AssetDatabase.ImportAsset(dst); } + var e = AssetDatabase.LoadAssetAtPath(dst); + // ⓐ 무늬를 1칸 2.5 m → **1.0 m** 로 더 잘게(「금 간 유리」 → 잔물결) · 흐름 0.8 m/s 유지 + const float cellM = 1.0f; + float size = 1000f / (cellM * 6f); + e.SetFloat("Vector1_07238257334b4a149e675e2451801030", size); // Size + e.SetFloat("Vector1_99cc56b5c6fd474082f07f00a6df94f9", 0.3085f / size); // Strength + e.SetFloat("Vector1_8e22a98f75c94b218c49e6c4805e799d", 0.8f * size / 1000f); // Speed + e.SetFloat("Vector1_ad7e0fcd37f44d68b68de6f06bb43b96", 0.8f * size / 1000f); // NoiseSpeed + // ⓑ 선 세기 — 밝은 쪽(LightColor)은 물색 가까이, 어두운 쪽(FoamShadowColor)은 흰색으로 없앤다 + e.SetColor(C_Light, new Color(0.42f, 0.72f, 0.845f, 1f)); + e.SetColor(C_FoamShadow, Color.white); + // ⓒ 깊이 기반 거품은 **끈다** — 둘레 띠가 대신한다(둘 다 켜면 분홍 얼룩이 된다) + e.SetColor(C_Foam, e.GetColor("Color_9af4ad59934f40d1ae6565e6ab22c45e")); // = WaterColor + e.SetFloat(R_FoamDistance, 2.2f); + e.SetFloat(R_FoamEdge, 4f); + EditorUtility.SetDirty(e); + sb.AppendLine("물 E = " + dst + " · Size " + size.ToString("F2") + "(무늬 1칸 " + cellM + " m)" + + " · LightColor " + e.GetColor(C_Light) + " · FoamShadowColor 흰색" + + " · FoamColor = 물색(깊이 거품 off) · FoamDistance " + e.GetFloat(R_FoamDistance) + " · FoamEdge " + e.GetFloat(R_FoamEdge)); + + // ── ④ SO ── + var cfg = AssetDatabase.LoadAssetAtPath( + "Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset"); + var so = new SerializedObject(cfg); + so.FindProperty("waterTo").objectReferenceValue = e; + so.FindProperty("shoreFoamMaterial").objectReferenceValue = fm; + so.FindProperty("shoreFoamEnabled").intValue = 1; + so.ApplyModifiedPropertiesWithoutUndo(); + EditorUtility.SetDirty(cfg); + AssetDatabase.SaveAssets(); + sb.AppendLine("SO waterTo = " + cfg.waterTo.name + " · shoreFoamMaterial = " + cfg.shoreFoamMaterial.name + + " · 폭 " + cfg.shoreFoamWidth + " m · 격자 " + cfg.shoreFoamCell + " m"); + + System.IO.File.WriteAllText("AgentScripts/WL816j2_BUILD.txt", sb.ToString()); + Debug.Log("[816j2 build]\n" + sb); + } + + // 가로로 이어지는(타일링) 값 노이즈 fbm + static float Fbm(int x, int y, int W) + { + float s = 0f, amp = 0.5f; int period = 32; + for (int o = 0; o < 3; o++) + { + s += amp * Value(x, y, W, period); + amp *= 0.5f; period *= 2; + } + return Mathf.Clamp01(s + 0.25f); + } + static float Value(int x, int y, int W, int period) + { + float fx = x / (float)period, fy = y / (float)period; + int x0 = Mathf.FloorToInt(fx), y0 = Mathf.FloorToInt(fy); + float tx = fx - x0, ty = fy - y0; + tx = tx * tx * (3f - 2f * tx); ty = ty * ty * (3f - 2f * ty); + int wrap = Mathf.Max(1, W / period); + float a = H2(Mod(x0, wrap), y0), b = H2(Mod(x0 + 1, wrap), y0); + float c = H2(Mod(x0, wrap), y0 + 1), d = H2(Mod(x0 + 1, wrap), y0 + 1); + return Mathf.Lerp(Mathf.Lerp(a, b, tx), Mathf.Lerp(c, d, tx), ty); + } + static int Mod(int a, int m) { int r = a % m; return r < 0 ? r + m : r; } + static float H2(int x, int y) + { + uint h = (uint)(x * 374761393 + y * 668265263 + 1442695040); + h = (h ^ (h >> 13)) * 1274126177u; + return ((h ^ (h >> 16)) & 0xFFFFFF) / (float)0xFFFFFF; + } +} diff --git a/AgentScripts/WL816j2_DIAG.txt b/AgentScripts/WL816j2_DIAG.txt new file mode 100644 index 000000000..2f1494458 --- /dev/null +++ b/AgentScripts/WL816j2_DIAG.txt @@ -0,0 +1,11 @@ +=== WL-816j2 ③ 띠 진단 === +띠 머티리얼 = WL_ShoreFoam(runtime) shader=Universal Render Pipeline/Unlit renderQueue=3050 _Surface=1 _ZWrite=0 _SrcBlend=5 _DstBlend=10 _BaseMap=WL_ShoreFoam keyword_TRANSPARENT=True +물 머티리얼 renderQueue = 3000 +띠 메시 bounds = Center: (3.90, -0.98, 3.90), Extents: (9.50, 0.00, 9.50) · 렌더러 enabled=True bounds=Center: (3.90, -0.98, 3.90), Extents: (9.50, 0.00, 9.50) + +1) 지금 그대로 밝은픽셀 1.67% → Screenshots_WL/WL816j2/g_diag_asis.png +2) renderQueue 3100 강제 밝은픽셀 1.64% → Screenshots_WL/WL816j2/g_diag_q3100.png +3) 물 끄고 띠만 밝은픽셀 0.09% → Screenshots_WL/WL816j2/g_diag_bandonly.png +4) 띠 메시 위치(빨강) 밝은픽셀 0.05% → Screenshots_WL/WL816j2/g_diag_red.png + PD 구도에서도 밝은픽셀 0.09% → Screenshots_WL/WL816j2/g_diag_red_pd.png +5) 큐 3100 · PD 구도 밝은픽셀 0.35% → Screenshots_WL/WL816j2/g_diag_q3100_pd.png diff --git a/AgentScripts/WL816j2_Diag.cs b/AgentScripts/WL816j2_Diag.cs new file mode 100644 index 000000000..4bc04445c --- /dev/null +++ b/AgentScripts/WL816j2_Diag.cs @@ -0,0 +1,116 @@ +// WL-816j2 — ③ 띠가 왜 안 보이나 (실측) · 물보다 뒤에 그려지는지 · 알파가 살아 있는지 +using System.Collections; +using System.Text; +using UnityEngine; +using UnityEngine.Rendering.Universal; +using WL.Look.Farm; + +public static class WL816j2_Diag +{ + public const string D = "Screenshots_WL/WL816j2/"; + public static void Start() + { + var go = GameObject.Find("~WL816j2D"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816j2D"); + go.AddComponent(); + } +} + +public class WL816j2_DiagRunner : MonoBehaviour +{ + static StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + const string D = WL816j2_Diag.D; + const int PW = 1280, PH = 720; + + void Start() { StartCoroutine(Co()); } + + string Shot(Camera cam, string path) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new RenderTexture(PW, PH, 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 t = new Texture2D(PW, PH, TextureFormat.RGB24, false); + t.ReadPixels(new Rect(0, 0, PW, PH), 0, 0); t.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; rt.Release(); Object.DestroyImmediate(rt); + System.IO.File.WriteAllBytes(path, t.EncodeToPNG()); + var px = t.GetPixels32(); + int white = 0; + for (int i = 0; i < px.Length; i += 3) if (px[i].r > 205 && px[i].g > 200 && px[i].b > 200) white++; + Object.DestroyImmediate(t); + return "밝은픽셀 " + (300f * white / px.Length).ToString("F2") + "% → " + path; + } + + IEnumerator Co() + { + sb = new StringBuilder(); + L("=== WL-816j2 ③ 띠 진단 ==="); + 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("~WL816")) continue; + if (live == null || c.depth > live.depth) live = c; + } + var foam = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (foam == null || water == null || live == null) { L("🔴 없음"); Done(); yield break; } + var mr = foam.GetComponent(); + var mf = foam.GetComponent(); + var m = mr.sharedMaterial; + L("띠 머티리얼 = " + m.name + " shader=" + m.shader.name + " renderQueue=" + m.renderQueue + + " _Surface=" + m.GetFloat("_Surface") + " _ZWrite=" + m.GetFloat("_ZWrite") + + " _SrcBlend=" + m.GetFloat("_SrcBlend") + " _DstBlend=" + m.GetFloat("_DstBlend") + + " _BaseMap=" + (m.GetTexture("_BaseMap") ? m.GetTexture("_BaseMap").name : "없음") + + " keyword_TRANSPARENT=" + m.IsKeywordEnabled("_SURFACE_TYPE_TRANSPARENT")); + L("물 머티리얼 renderQueue = " + water.sharedMaterial.renderQueue); + L("띠 메시 bounds = " + mf.sharedMesh.bounds + " · 렌더러 enabled=" + mr.enabled + " bounds=" + mr.bounds); + + // 카메라를 물가로 가까이 — 띠가 잘 보이는 확대 + var cgo = new GameObject("~WL816j2DCam"); cgo.hideFlags = HideFlags.DontSave; + var cam = cgo.AddComponent(); + cam.fieldOfView = 34f; cam.nearClipPlane = 0.1f; cam.farClipPlane = 2000f; + cam.clearFlags = CameraClearFlags.Skybox; cam.cullingMask = ~0; cam.depth = -100f; + cgo.AddComponent().renderPostProcessing = true; + var b = mr.bounds; + cgo.transform.position = new Vector3(b.center.x, 3.2f, b.min.z - 9f); + cgo.transform.LookAt(new Vector3(b.center.x, -1f, b.min.z + 2f)); + + L(""); + L("1) 지금 그대로 " + Shot(cam, D + "g_diag_asis.png")); + + // ① 큐를 강제로 물 뒤로 + var q = new Material(m); q.renderQueue = 3100; + mr.sharedMaterial = q; yield return null; yield return null; + L("2) renderQueue 3100 강제 " + Shot(cam, D + "g_diag_q3100.png")); + + // ② 물을 끄고 띠만 + water.enabled = false; yield return null; yield return null; + L("3) 물 끄고 띠만 " + Shot(cam, D + "g_diag_bandonly.png")); + water.enabled = true; yield return null; + + // ③ 알파 무시하고 불투명 빨강 — 띠 메시가 어디에 있는지 + var dbg = new Material(Shader.Find("Universal Render Pipeline/Unlit")); + dbg.SetColor("_BaseColor", new Color(1f, 0f, 0f, 1f)); + dbg.renderQueue = 3100; + mr.sharedMaterial = dbg; yield return null; yield return null; + L("4) 띠 메시 위치(빨강) " + Shot(cam, D + "g_diag_red.png")); + L(" PD 구도에서도 " + Shot(live, D + "g_diag_red_pd.png")); + + mr.sharedMaterial = q; yield return null; + L("5) 큐 3100 · PD 구도 " + Shot(live, D + "g_diag_q3100_pd.png")); + + Object.DestroyImmediate(cgo); + Done(); + } + void Done() + { + System.IO.File.WriteAllText("AgentScripts/WL816j2_DIAG.txt", sb.ToString()); + Debug.Log("[816j2 diag]\n" + sb); + } +} diff --git a/AgentScripts/WL816j2_FINAL.txt b/AgentScripts/WL816j2_FINAL.txt new file mode 100644 index 000000000..c0e4eb2ea --- /dev/null +++ b/AgentScripts/WL816j2_FINAL.txt @@ -0,0 +1,30 @@ +=== WL-816j2 ⑧ 최종 (PD 구도 1280×720) === +Load_Map(900) = 🔴 로그인 없이는 못 탄다 → LoadSceneAsync(Level01, Additive) +물 = Farm_Water_WL_E · 둘레 띠 = 타일 4 · 띠 삼각형 1280 · 정점 772 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 + +1) [전] main(물 D · 띠 없음 = PD 가 본 화면) 바다평균 #5C96C2 · 거품 10.19% #EDD9E6(R−B 7) → Screenshots_WL/WL816j2/z2_before_pd.png + [후] 816j2(물 E · 둘레 띠) 바다평균 #5794C1 · 거품 1.16% #F4F3FC(R−B -8) → Screenshots_WL/WL816j2/z2_after_pd.png + 세로(게임 기준 1080×1920) 바다평균 #5694C1 · 거품 3.32% #F6F5FC(R−B -6) → Screenshots_WL/WL816j2/z2_after_portrait.png + +2) 사방(위에서) 바다평균 #5894C1 · 거품 0.43% #F6F5FC(R−B -6) → Screenshots_WL/WL816j2/z2_top.png + 물가 확대 [전] 바다평균 #A2B3CE · 거품 38.36% #FBDFE4(R−B 23) → Screenshots_WL/WL816j2/z2_zoom_before.png + 물가 확대 [후] 바다평균 #5F93BD · 거품 2.08% #F0DFF1(R−B -1) → Screenshots_WL/WL816j2/z2_zoom_after.png + +3) 걷기 (0.00, 0.00, 0.00) → (4.24, 0.01, 4.24) · 이동 6.00 m · grounded=True + 확장 1칸 잠금 → 타일 3 · 띠 삼각형 1268 · 정점 766 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 + 바다평균 #5794C1 · 거품 0.33% #F6F7FB(R−B -5) → Screenshots_WL/WL816j2/z2_top_locked.png + 다시 열기 → 타일 4 · 띠 삼각형 1280 · 정점 772 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 · 띠 재생성 2회 + 바다평균 #5794C1 · 거품 0.80% #F7F5FD(R−B -6) → Screenshots_WL/WL816j2/z2_top_unlocked.png + +4) 성능 (PD 구도 1280×720 · 60프레임 · 교대 2회) + 0회차 전(물 D · 띠 없음) = 0.882 ms + 0회차 후(물 E · 띠 on) = 0.872 ms + 0회차 후(물 E · 띠 off) = 0.926 ms + 1회차 전(물 D · 띠 없음) = 0.944 ms + 1회차 후(물 E · 띠 on) = 0.916 ms + 1회차 후(물 E · 띠 off) = 0.851 ms + 띠 = 타일 4 · 띠 삼각형 1280 · 정점 772 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 + +5) 되돌리기 + shoreFoamEnabled 0 + waterTo=D → 816j 그대로 바다평균 #6098C3 · 거품 10.26% #EEDBE7(R−B 7) → Screenshots_WL/WL816j2/z2_rollback_816j.png + waterMode 0 → FI 원본 물 바다평균 #5DB6FD · 거품 1.66% #FAF9FC(R−B -2) → Screenshots_WL/WL816j2/z2_rollback_fi.png diff --git a/AgentScripts/WL816j2_Final.cs b/AgentScripts/WL816j2_Final.cs new file mode 100644 index 000000000..979b4b1fe --- /dev/null +++ b/AgentScripts/WL816j2_Final.cs @@ -0,0 +1,218 @@ +// WL-816j2 — ⑧ 최종 확인: PD 구도 전/후 · 사방 띠 · 확장 · 걷기 · 성능 · 되돌리기 +using System.Collections; +using System.Text; +using UnityEngine; +using UnityEngine.Rendering.Universal; +using UnityEngine.SceneManagement; +using WL.Look.Farm; +using FIIsland = CryingSnow.FarmingIsland.Island; + +public static class WL816j2_Final +{ + public const string D = "Screenshots_WL/WL816j2/"; + public const int PW = 1280, PH = 720; + public static void Open() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + } + public static void Start() + { + var go = GameObject.Find("~WL816j2F"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816j2F"); + go.AddComponent(); + } + public static string Shot(Camera cam, string path, int w, int h) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + 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 t = new Texture2D(w, h, TextureFormat.RGB24, false); + t.ReadPixels(new Rect(0, 0, w, h), 0, 0); t.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; rt.Release(); Object.DestroyImmediate(rt); + System.IO.File.WriteAllBytes(path, t.EncodeToPNG()); + var px = t.GetPixels32(); + long r = 0, g = 0, b = 0; int n = 0; long fr = 0, fg = 0, fb = 0; int fn = 0, tot = 0; + for (int y = 20; y < Mathf.Min(320, h); y += 2) + for (int x = 20; x < Mathf.Min(340, w); x += 2) { var c = px[y * w + x]; r += c.r; g += c.g; b += c.b; n++; } + int y1 = (int)(h * 0.55f); + for (int y = 0; y < y1; y++) + for (int x = 0; x < w; x += 2) { var c = px[y * w + x]; tot++; if (c.r > 200 && c.g > 195 && c.b > 195) { fr += c.r; fg += c.g; fb += c.b; fn++; } } + Object.DestroyImmediate(t); + string foam = fn == 0 ? "거품 0%" : "거품 " + (100f * fn / tot).ToString("F2") + "% #" + + ((int)(fr / fn)).ToString("X2") + ((int)(fg / fn)).ToString("X2") + ((int)(fb / fn)).ToString("X2") + + "(R−B " + ((int)(fr / fn) - (int)(fb / fn)) + ")"; + return "바다평균 #" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(b / n)).ToString("X2") + + " · " + foam + " → " + path; + } + public static float Ms(Camera cam, int frames) + { + var rt = new RenderTexture(PW, PH, 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++) + { + 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 = Mathf.Max(w, ts[i].width); h += ts[i].height; + } + var o = new Texture2D(w, h, TextureFormat.RGB24, false); + var f = new Color32[w * h]; + for (int i = 0; i < f.Length; i++) f[i] = new Color32(24, 24, 28, 255); + o.SetPixels32(f); + int y = h; + for (int i = 0; i < ts.Length; i++) { y -= ts[i].height; o.SetPixels32(0, y, ts[i].width, ts[i].height, ts[i].GetPixels32()); } + 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 WL816j2_FinalRunner : MonoBehaviour +{ + static StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + const string D = WL816j2_Final.D; + const string MatDir = "Assets/WL/Look/Farm/Materials/"; + const int PW = WL816j2_Final.PW, PH = WL816j2_Final.PH; + + void Start() { StartCoroutine(Co()); } + + IEnumerator Co() + { + sb = new StringBuilder(); + L("=== WL-816j2 ⑧ 최종 (PD 구도 1280×720) ==="); + bool viaGame = false; + try { if (InGameInfo.Ins != null) { InGameInfo.Ins.Load_Map(900); viaGame = true; } } catch { } + L("Load_Map(900) = " + (viaGame ? "성공" : "🔴 로그인 없이는 못 탄다 → LoadSceneAsync(Level01, Additive)")); + if (!viaGame) + { + var op = SceneManager.LoadSceneAsync("Level01", LoadSceneMode.Additive); + while (op != null && !op.isDone) yield return null; + } + for (int i = 0; i < 8; i++) yield return new WaitForSeconds(1f); + + 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("~WL816")) continue; + if (live == null || c.depth > live.depth) live = c; + } + var foam = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (water == null || live == null || foam == null) { L("🔴 없음"); Done(); yield break; } + var mr = foam.GetComponent(); + var E = water.sharedMaterial; + var Dm = UnityEditor.AssetDatabase.LoadAssetAtPath(MatDir + "Farm_Water_WL_D.mat"); + var FI = UnityEditor.AssetDatabase.LoadAssetAtPath(MatDir + "Farm_SimpleWater_Demo.mat"); + L("물 = " + E.name + " · 둘레 띠 = " + WLShoreFoam.LastLog); + + // 1) 전/후 + mr.enabled = false; water.sharedMaterial = Dm; yield return null; yield return new WaitForSeconds(0.4f); + L(""); + L("1) [전] main(물 D · 띠 없음 = PD 가 본 화면) " + WL816j2_Final.Shot(live, D + "z2_before_pd.png", PW, PH)); + mr.enabled = true; water.sharedMaterial = E; yield return null; yield return new WaitForSeconds(0.4f); + L(" [후] 816j2(물 E · 둘레 띠) " + WL816j2_Final.Shot(live, D + "z2_after_pd.png", PW, PH)); + WL816j2_Final.Strip(new[] { D + "z2_before_pd.png", D + "z2_after_pd.png" }, D + "z2_pd_before_after.png"); + L(" 세로(게임 기준 1080×1920) " + WL816j2_Final.Shot(live, D + "z2_after_portrait.png", 1080, 1920)); + + // 2) 사방 + 물가 확대 + var tgo = new GameObject("~WL816j2FT"); tgo.hideFlags = HideFlags.DontSave; + var tc = tgo.AddComponent(); + tc.fieldOfView = 55f; tc.nearClipPlane = 0.1f; tc.farClipPlane = 2000f; + tc.clearFlags = CameraClearFlags.Skybox; tc.cullingMask = ~0; tc.depth = -100f; + tgo.AddComponent().renderPostProcessing = true; + tgo.transform.position = new Vector3(0f, 42f, -12f); tgo.transform.LookAt(Vector3.zero); + L(""); + L("2) 사방(위에서) " + WL816j2_Final.Shot(tc, D + "z2_top.png", PW, PH)); + + var b = mr.bounds; + var zgo = new GameObject("~WL816j2FZ"); zgo.hideFlags = HideFlags.DontSave; + var zc = zgo.AddComponent(); + zc.fieldOfView = 34f; zc.nearClipPlane = 0.1f; zc.farClipPlane = 2000f; + zc.clearFlags = CameraClearFlags.Skybox; zc.cullingMask = ~0; zc.depth = -100f; + zgo.AddComponent().renderPostProcessing = true; + zgo.transform.position = new Vector3(b.center.x, 3.2f, b.min.z - 9f); + zgo.transform.LookAt(new Vector3(b.center.x, -1f, b.min.z + 2f)); + mr.enabled = false; water.sharedMaterial = Dm; yield return null; yield return null; + L(" 물가 확대 [전] " + WL816j2_Final.Shot(zc, D + "z2_zoom_before.png", PW, PH)); + mr.enabled = true; water.sharedMaterial = E; yield return null; yield return null; + L(" 물가 확대 [후] " + WL816j2_Final.Shot(zc, D + "z2_zoom_after.png", PW, PH)); + WL816j2_Final.Strip(new[] { D + "z2_zoom_before.png", D + "z2_zoom_after.png" }, D + "z2_zoom_pair.png"); + + // 3) 걷기 + 확장 + var cc = Object.FindFirstObjectByType(FindObjectsInactive.Exclude); + if (cc != null) + { + var p0 = cc.transform.position; var dir = new Vector3(1f, 0f, 1f).normalized; float moved = 0f; + for (int i = 0; i < 120 && moved < 6f; i++) { cc.Move(dir * 0.05f + Vector3.down * 0.05f); moved += 0.05f; yield return null; } + L(""); + L("3) 걷기 " + p0.ToString("F2") + " → " + cc.transform.position.ToString("F2") + " · 이동 " + + Vector3.Distance(new Vector3(p0.x, 0, p0.z), new Vector3(cc.transform.position.x, 0, cc.transform.position.z)).ToString("F2") + + " m · grounded=" + cc.isGrounded); + } + var islands = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + FIIsland target = null; + foreach (var isl in islands) if (isl != null && isl.IsUnlocked && isl.gameObject.activeInHierarchy) target = isl; + if (target != null) + { + target.gameObject.SetActive(false); yield return new WaitForSeconds(2.5f); + L(" 확장 1칸 잠금 → " + WLShoreFoam.LastLog); + L(" " + WL816j2_Final.Shot(tc, D + "z2_top_locked.png", PW, PH)); + target.gameObject.SetActive(true); yield return new WaitForSeconds(3.5f); + L(" 다시 열기 → " + WLShoreFoam.LastLog + " · 띠 재생성 " + WL.Look.Farm.WLIslandLook.ShoreFoamRebuilds + "회"); + L(" " + WL816j2_Final.Shot(tc, D + "z2_top_unlocked.png", PW, PH)); + WL816j2_Final.Strip(new[] { D + "z2_top.png", D + "z2_top_locked.png", D + "z2_top_unlocked.png" }, D + "z2_expand_steps.png"); + } + + // 4) 성능 + L(""); + L("4) 성능 (PD 구도 1280×720 · 60프레임 · 교대 2회)"); + for (int round = 0; round < 2; round++) + { + mr.enabled = false; water.sharedMaterial = Dm; yield return null; yield return null; + L(" " + round + "회차 전(물 D · 띠 없음) = " + WL816j2_Final.Ms(live, 60).ToString("F3") + " ms"); + mr.enabled = true; water.sharedMaterial = E; yield return null; yield return null; + L(" " + round + "회차 후(물 E · 띠 on) = " + WL816j2_Final.Ms(live, 60).ToString("F3") + " ms"); + mr.enabled = false; yield return null; yield return null; + L(" " + round + "회차 후(물 E · 띠 off) = " + WL816j2_Final.Ms(live, 60).ToString("F3") + " ms"); + mr.enabled = true; + } + L(" 띠 = " + WLShoreFoam.LastLog); + + // 5) 되돌리기 + L(""); + L("5) 되돌리기"); + mr.enabled = false; water.sharedMaterial = Dm; yield return null; yield return new WaitForSeconds(0.3f); + L(" shoreFoamEnabled 0 + waterTo=D → 816j 그대로 " + WL816j2_Final.Shot(live, D + "z2_rollback_816j.png", PW, PH)); + water.sharedMaterial = FI; yield return null; yield return new WaitForSeconds(0.3f); + L(" waterMode 0 → FI 원본 물 " + WL816j2_Final.Shot(live, D + "z2_rollback_fi.png", PW, PH)); + water.sharedMaterial = E; mr.enabled = true; yield return null; + + Object.DestroyImmediate(tgo); Object.DestroyImmediate(zgo); + Done(); + } + void Done() + { + System.IO.File.WriteAllText("AgentScripts/WL816j2_FINAL.txt", sb.ToString()); + Debug.Log("[816j2 final]\n" + sb); + } +} diff --git a/AgentScripts/WL816j2_SWEEP.txt b/AgentScripts/WL816j2_SWEEP.txt new file mode 100644 index 000000000..e8ea2dbd1 --- /dev/null +++ b/AgentScripts/WL816j2_SWEEP.txt @@ -0,0 +1,9 @@ +=== WL-816j2 ⑥ 격자선 세기 스윕 (바다 평균색은 유지해야 한다) === +816h 채택색 WaterColor RGBA(0.360, 0.690, 0.820, 1.000) DarkColor RGBA(0.170, 0.420, 0.570, 1.000) +0 기준 E · 바다 평균 #5A96C3 → Screenshots_WL/WL816j2/m_sweep0.png +1 Shadow=0.30 회색 · 바다 평균 #5A96C2 → Screenshots_WL/WL816j2/m_sweep1.png +2 Shadow=흰색 · 바다 평균 #5896C3 → Screenshots_WL/WL816j2/m_sweep2.png +3 Dark→Water 50% · 바다 평균 #5A96C3 → Screenshots_WL/WL816j2/m_sweep3.png +4 Dark→Water 75% · 바다 평균 #5B97C3 → Screenshots_WL/WL816j2/m_sweep4.png +5 Dark→Water 75% + Light=Water + Shadow=흰색 · 바다 평균 #5695C2 → Screenshots_WL/WL816j2/m_sweep5.png +6 Dark→Water 90% + Light=Water + Shadow=흰색 · 바다 평균 #5695C2 → Screenshots_WL/WL816j2/m_sweep6.png diff --git a/AgentScripts/WL816j2_SWEEP2.txt b/AgentScripts/WL816j2_SWEEP2.txt new file mode 100644 index 000000000..c167dcc36 --- /dev/null +++ b/AgentScripts/WL816j2_SWEEP2.txt @@ -0,0 +1,8 @@ +=== WL-816j2 ⑦ FoamColor 후보 + 무늬 잘게 === +0 Light 부드럽게(2.5 m) · 바다 평균 #5795C2 → Screenshots_WL/WL816j2/n_s2_0.png +1 + FoamColor = 물색 · 바다 평균 #5695C2 → Screenshots_WL/WL816j2/n_s2_1.png +2 + FoamColor = 물색 · 1.5 m · 바다 평균 #5895C2 → Screenshots_WL/WL816j2/n_s2_2.png +3 + FoamColor = 물색 · 1.0 m · 바다 평균 #5896C2 → Screenshots_WL/WL816j2/n_s2_3.png +4 FoamColor 흰색 · 1.0 m · 바다 평균 #5895C2 → Screenshots_WL/WL816j2/n_s2_4.png +5 + FoamColor = 물색 · 0.6 m · 바다 평균 #5996C3 → Screenshots_WL/WL816j2/n_s2_5.png +6 FoamColor=물색 · Shadow=흰색 · Light=물색 · 1.0 m (선 최소) · 바다 평균 #5695C2 → Screenshots_WL/WL816j2/n_s2_6.png diff --git a/AgentScripts/WL816j2_Sweep.cs b/AgentScripts/WL816j2_Sweep.cs new file mode 100644 index 000000000..bddd7ecdb --- /dev/null +++ b/AgentScripts/WL816j2_Sweep.cs @@ -0,0 +1,99 @@ +// WL-816j2 — ⑥ 격자선 세기를 실제로 낮추는 값 찾기 (PD 구도 · 6단 스윕 · 평균 바다색은 유지) +using System.Collections; +using System.Text; +using UnityEngine; + +public static class WL816j2_Sweep +{ + public const string D = "Screenshots_WL/WL816j2/"; + public static void Start() + { + var go = GameObject.Find("~WL816j2S"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816j2S"); + go.AddComponent(); + } +} + +public class WL816j2_SweepRunner : MonoBehaviour +{ + static StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + const string D = WL816j2_Sweep.D; + const int PW = 1280, PH = 720; + const string C_Light = "Color_1ec6cccf8ead489ebb917674f7e8b3b1"; + const string C_FoamShadow = "Color_2323d69962e04c499c5d5f0925432b55"; + const string C_Water = "Color_9af4ad59934f40d1ae6565e6ab22c45e"; + const string C_Dark = "Color_ca031ae309ff42bdb95f777248fb961d"; + const string R_DeepDistance = "Vector1_566e288f42864b8e9432d81fbdb83f38"; + + void Start() { StartCoroutine(Co()); } + + string Shot(Camera cam, string path) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new RenderTexture(PW, PH, 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 t = new Texture2D(PW, PH, TextureFormat.RGB24, false); + t.ReadPixels(new Rect(0, 0, PW, PH), 0, 0); t.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; rt.Release(); Object.DestroyImmediate(rt); + System.IO.File.WriteAllBytes(path, t.EncodeToPNG()); + var px = t.GetPixels32(); + long r = 0, g = 0, b = 0; int n = 0; + for (int y = 20; y < 320; y += 2) + for (int x = 20; x < 340; x += 2) { var c = px[y * PW + x]; r += c.r; g += c.g; b += c.b; n++; } + Object.DestroyImmediate(t); + return "바다 평균 #" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(b / n)).ToString("X2") + " → " + path; + } + + IEnumerator Co() + { + sb = new StringBuilder(); + L("=== WL-816j2 ⑥ 격자선 세기 스윕 (바다 평균색은 유지해야 한다) ==="); + 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("~WL816")) continue; + if (live == null || c.depth > live.depth) live = c; + } + if (water == null || live == null) { L("🔴 없음"); Done(); yield break; } + var E = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat"); + Color W = E.GetColor(C_Water), Dk = E.GetColor(C_Dark); + L("816h 채택색 WaterColor " + W + " DarkColor " + Dk); + + var names = new string[] + { + "0 기준 E", + "1 Shadow=0.30 회색", + "2 Shadow=흰색", + "3 Dark→Water 50%", + "4 Dark→Water 75%", + "5 Dark→Water 75% + Light=Water + Shadow=흰색", + "6 Dark→Water 90% + Light=Water + Shadow=흰색", + }; + for (int i = 0; i < names.Length; i++) + { + var m = new Material(E); + if (i == 1) m.SetColor(C_FoamShadow, new Color(0.30f, 0.30f, 0.30f, 1f)); + if (i == 2) m.SetColor(C_FoamShadow, Color.white); + if (i == 3) m.SetColor(C_Dark, Color.Lerp(Dk, W, 0.50f)); + if (i == 4) m.SetColor(C_Dark, Color.Lerp(Dk, W, 0.75f)); + if (i == 5) { m.SetColor(C_Dark, Color.Lerp(Dk, W, 0.75f)); m.SetColor(C_Light, W); m.SetColor(C_FoamShadow, Color.white); } + if (i == 6) { m.SetColor(C_Dark, Color.Lerp(Dk, W, 0.90f)); m.SetColor(C_Light, W); m.SetColor(C_FoamShadow, Color.white); } + water.sharedMaterial = m; yield return null; yield return new WaitForSeconds(0.25f); + L(names[i] + " · " + Shot(live, D + "m_sweep" + i + ".png")); + } + Done(); + } + void Done() + { + System.IO.File.WriteAllText("AgentScripts/WL816j2_SWEEP.txt", sb.ToString()); + Debug.Log("[816j2 sweep]\n" + sb); + } +} diff --git a/AgentScripts/WL816j2_Sweep2.cs b/AgentScripts/WL816j2_Sweep2.cs new file mode 100644 index 000000000..9530dce7a --- /dev/null +++ b/AgentScripts/WL816j2_Sweep2.cs @@ -0,0 +1,110 @@ +// WL-816j2 — ⑦ 격자선을 「금 간 유리」가 아니게: FoamColor 후보 + 무늬를 더 잘게 (PD 구도) +using System.Collections; +using System.Text; +using UnityEngine; + +public static class WL816j2_Sweep2 +{ + public const string D = "Screenshots_WL/WL816j2/"; + public static void Start() + { + var go = GameObject.Find("~WL816j2S2"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816j2S2"); + go.AddComponent(); + } +} + +public class WL816j2_Sweep2Runner : MonoBehaviour +{ + static StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + const string D = WL816j2_Sweep2.D; + const int PW = 1280, PH = 720; + const string C_Light = "Color_1ec6cccf8ead489ebb917674f7e8b3b1"; + const string C_Water = "Color_9af4ad59934f40d1ae6565e6ab22c45e"; + const string C_Foam = "Color_e1f155248d144786a62fc1585ca21e9a"; + const string C_FoamShadow = "Color_2323d69962e04c499c5d5f0925432b55"; + const string R_Size = "Vector1_07238257334b4a149e675e2451801030"; + const string R_Strength = "Vector1_99cc56b5c6fd474082f07f00a6df94f9"; + const string R_Speed = "Vector1_8e22a98f75c94b218c49e6c4805e799d"; + const string R_NoiseSpeed = "Vector1_ad7e0fcd37f44d68b68de6f06bb43b96"; + + void Start() { StartCoroutine(Co()); } + + string Shot(Camera cam, string path) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new RenderTexture(PW, PH, 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 t = new Texture2D(PW, PH, TextureFormat.RGB24, false); + t.ReadPixels(new Rect(0, 0, PW, PH), 0, 0); t.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; rt.Release(); Object.DestroyImmediate(rt); + System.IO.File.WriteAllBytes(path, t.EncodeToPNG()); + var px = t.GetPixels32(); + long r = 0, g = 0, b = 0; int n = 0; + for (int y = 20; y < 320; y += 2) + for (int x = 20; x < 340; x += 2) { var c = px[y * PW + x]; r += c.r; g += c.g; b += c.b; n++; } + Object.DestroyImmediate(t); + return "바다 평균 #" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(b / n)).ToString("X2") + " → " + path; + } + static void Cell(Material m, float cellM) + { + float size = 1000f / (cellM * 6f); + m.SetFloat(R_Size, size); + m.SetFloat(R_Strength, 0.3085f / size); + m.SetFloat(R_Speed, 0.8f * size / 1000f); + m.SetFloat(R_NoiseSpeed, 0.8f * size / 1000f); + } + + IEnumerator Co() + { + sb = new StringBuilder(); + L("=== WL-816j2 ⑦ FoamColor 후보 + 무늬 잘게 ==="); + 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("~WL816")) continue; + if (live == null || c.depth > live.depth) live = c; + } + if (water == null || live == null) { L("🔴 없음"); Done(); yield break; } + var E = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat"); + Color W = E.GetColor(C_Water); + var soft = new Color(0.42f, 0.72f, 0.845f, 1f); + + var names = new[] + { + "0 Light 부드럽게(2.5 m)", + "1 + FoamColor = 물색", + "2 + FoamColor = 물색 · 1.5 m", + "3 + FoamColor = 물색 · 1.0 m", + "4 FoamColor 흰색 · 1.0 m", + "5 + FoamColor = 물색 · 0.6 m", + "6 FoamColor=물색 · Shadow=흰색 · Light=물색 · 1.0 m (선 최소)", + }; + for (int i = 0; i < names.Length; i++) + { + var m = new Material(E); + m.SetColor(C_Light, soft); + if (i >= 1 && i != 4) m.SetColor(C_Foam, W); + if (i == 2) Cell(m, 1.5f); + if (i == 3 || i == 4) Cell(m, 1.0f); + if (i == 5) Cell(m, 0.6f); + if (i == 6) { Cell(m, 1.0f); m.SetColor(C_Foam, W); m.SetColor(C_FoamShadow, Color.white); m.SetColor(C_Light, W); } + water.sharedMaterial = m; yield return null; yield return new WaitForSeconds(0.25f); + L(names[i] + " · " + Shot(live, D + "n_s2_" + i + ".png")); + } + Done(); + } + void Done() + { + System.IO.File.WriteAllText("AgentScripts/WL816j2_SWEEP2.txt", sb.ToString()); + Debug.Log("[816j2 sweep2]\n" + sb); + } +} diff --git a/AgentScripts/WL816j2_TUNE.txt b/AgentScripts/WL816j2_TUNE.txt new file mode 100644 index 000000000..6eaf7affb --- /dev/null +++ b/AgentScripts/WL816j2_TUNE.txt @@ -0,0 +1,16 @@ +=== WL-816j2 ④ 마지막 조율 (PD 구도 1280×720) === + +1) 무늬 선 3단 — LightColor + FoamShadowColor(무늬의 어두운 쪽). 목표 = 데모(σ 0.0024)에 가깝게 + Light RGBA(0.450, 0.740, 0.860, 1.000) Shadow RGBA(0.780, 0.860, 0.920, 1.000) σ=0.0552 · 거품 1.16% #F9F5F9(R−B 0) → Screenshots_WL/WL816j2/h_soft1.png + Light RGBA(0.420, 0.720, 0.845, 1.000) Shadow RGBA(0.930, 0.960, 0.980, 1.000) σ=0.0515 · 거품 1.14% #F9F5F9(R−B 0) → Screenshots_WL/WL816j2/h_soft2.png + Light RGBA(0.385, 0.705, 0.830, 1.000) Shadow RGBA(1.000, 1.000, 1.000, 1.000) σ=0.0488 · 거품 1.20% #F9F4F8(R−B 1) → Screenshots_WL/WL816j2/h_soft3.png + +2) 띠 폭 3단 + 폭별 비용 (60프레임 × 2회) + [띠 off] σ=0.0525 · 거품 1.13% #FCF2F3(R−B 9) → Screenshots_WL/WL816j2/i_w0_off.png · 0.858 ms + [폭 0.9 m · 삼각형 744] σ=0.0517 · 거품 1.16% #FCF1F3(R−B 9) → Screenshots_WL/WL816j2/i_w1.png · 0.840 / 0.867 ms + [폭 1.6 m · 삼각형 1280] σ=0.0508 · 거품 1.22% #FBF4F7(R−B 4) → Screenshots_WL/WL816j2/i_w2.png · 0.878 / 0.882 ms + [폭 2.6 m · 삼각형 1864] σ=0.0502 · 거품 2.34% #F3EFFB(R−B -8) → Screenshots_WL/WL816j2/i_w3.png · 0.888 / 0.848 ms + +3) 최종 전/후 (PD 구도) + [전] main(물 D · 띠 없음) σ=0.0820 · 거품 10.16% #ECDAE7(R−B 5) → Screenshots_WL/WL816j2/z_before_pd.png + [후] 816j2(물 E · 띠 on) σ=0.0473 · 거품 1.14% #FBF3F6(R−B 5) → Screenshots_WL/WL816j2/z_after_pd.png diff --git a/AgentScripts/WL816j2_Tune.cs b/AgentScripts/WL816j2_Tune.cs new file mode 100644 index 000000000..ceec5319b --- /dev/null +++ b/AgentScripts/WL816j2_Tune.cs @@ -0,0 +1,173 @@ +// WL-816j2 — ④ PD 구도에서 마지막 조율: 무늬 선을 데모만큼 은은하게 · 띠 폭/비용 · 최종 전후 +using System.Collections; +using System.Text; +using UnityEngine; +using UnityEngine.Rendering.Universal; +using WL.Look.Farm; + +public static class WL816j2_Tune +{ + public const string D = "Screenshots_WL/WL816j2/"; + public const int PW = 1280, PH = 720; + public const string C_Light = "Color_1ec6cccf8ead489ebb917674f7e8b3b1"; + public const string C_FoamShadow = "Color_2323d69962e04c499c5d5f0925432b55"; + + public static void Start() + { + var go = GameObject.Find("~WL816j2T"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816j2T"); + go.AddComponent(); + } + public static string Shot(Camera cam, string path) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new RenderTexture(PW, PH, 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 t = new Texture2D(PW, PH, TextureFormat.RGB24, false); + t.ReadPixels(new Rect(0, 0, PW, PH), 0, 0); t.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; rt.Release(); Object.DestroyImmediate(rt); + System.IO.File.WriteAllBytes(path, t.EncodeToPNG()); + var px = t.GetPixels32(); + double sum = 0, sum2 = 0; int n = 0; + for (int y = 20; y < 320; y += 2) + for (int x = 20; x < 340; x += 2) + { var c = px[y * PW + x]; float l = (0.299f * c.r + 0.587f * c.g + 0.114f * c.b) / 255f; sum += l; sum2 += l * l; n++; } + double mean = sum / n, sd = System.Math.Sqrt(System.Math.Max(0, sum2 / n - mean * mean)); + long fr = 0, fg = 0, fb = 0; int fn = 0, tot = 0; + int y1 = (int)(PH * 0.55f); + for (int y = 0; y < y1; y++) + for (int x = 0; x < PW; x += 2) + { var c = px[y * PW + x]; tot++; if (c.r > 200 && c.g > 195 && c.b > 195) { fr += c.r; fg += c.g; fb += c.b; fn++; } } + Object.DestroyImmediate(t); + string foam = fn == 0 ? "거품 0%" : "거품 " + (100f * fn / tot).ToString("F2") + "% #" + + ((int)(fr / fn)).ToString("X2") + ((int)(fg / fn)).ToString("X2") + ((int)(fb / fn)).ToString("X2") + + "(R−B " + ((int)(fr / fn) - (int)(fb / fn)) + ")"; + return "σ=" + sd.ToString("F4") + " · " + foam + " → " + path; + } + public static float Ms(Camera cam, int frames) + { + var rt = new RenderTexture(PW, PH, 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++) + { + 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 = Mathf.Max(w, ts[i].width); h += ts[i].height; + } + var o = new Texture2D(w, h, TextureFormat.RGB24, false); + var f = new Color32[w * h]; + for (int i = 0; i < f.Length; i++) f[i] = new Color32(24, 24, 28, 255); + o.SetPixels32(f); + int y = h; + for (int i = 0; i < ts.Length; i++) { y -= ts[i].height; o.SetPixels32(0, y, ts[i].width, ts[i].height, ts[i].GetPixels32()); } + 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 WL816j2_TuneRunner : MonoBehaviour +{ + static StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + const string D = WL816j2_Tune.D; + const string MatDir = "Assets/WL/Look/Farm/Materials/"; + + void Start() { StartCoroutine(Co()); } + + IEnumerator Co() + { + sb = new StringBuilder(); + L("=== WL-816j2 ④ 마지막 조율 (PD 구도 1280×720) ==="); + 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("~WL816")) continue; + if (live == null || c.depth > live.depth) live = c; + } + var foam = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (water == null || live == null || foam == null) { L("🔴 없음"); Done(); yield break; } + var E = UnityEditor.AssetDatabase.LoadAssetAtPath(MatDir + "Farm_Water_WL_E.mat"); + var mr = foam.GetComponent(); + + // ── 1) 무늬 선을 데모만큼 은은하게 (기준 데모 σ=0.0024 · 지금 D σ=0.0823) ── + L(""); + L("1) 무늬 선 3단 — LightColor + FoamShadowColor(무늬의 어두운 쪽). 목표 = 데모(σ 0.0024)에 가깝게"); + var lc = new[] { new Color(0.45f, 0.74f, 0.86f), new Color(0.42f, 0.72f, 0.845f), new Color(0.385f, 0.705f, 0.83f) }; + var fs = new[] { new Color(0.78f, 0.86f, 0.92f), new Color(0.93f, 0.96f, 0.98f), new Color(1f, 1f, 1f) }; + var ps = new string[3]; + for (int i = 0; i < 3; i++) + { + var m = new Material(E); + m.SetColor(WL816j2_Tune.C_Light, lc[i]); + m.SetColor(WL816j2_Tune.C_FoamShadow, fs[i]); + water.sharedMaterial = m; yield return null; yield return new WaitForSeconds(0.2f); + ps[i] = D + "h_soft" + (i + 1) + ".png"; + L(" Light " + lc[i] + " Shadow " + fs[i] + " " + WL816j2_Tune.Shot(live, ps[i])); + } + WL816j2_Tune.Strip(ps, D + "h_soft_steps.png"); + + // 채택 후보 = 가운데 + var adopt = new Material(E); + adopt.SetColor(WL816j2_Tune.C_Light, lc[1]); + adopt.SetColor(WL816j2_Tune.C_FoamShadow, fs[1]); + water.sharedMaterial = adopt; yield return null; + + // ── 2) 띠 폭 3단 (고친 텍스처로 다시) + 폭별 비용 ──────────── + L(""); + L("2) 띠 폭 3단 + 폭별 비용 (60프레임 × 2회)"); + var ws = new string[4]; float[] wid = { 0.9f, 1.6f, 2.6f }; + mr.enabled = false; yield return null; yield return null; + ws[0] = D + "i_w0_off.png"; + L(" [띠 off] " + WL816j2_Tune.Shot(live, ws[0]) + " · " + WL816j2_Tune.Ms(live, 60).ToString("F3") + " ms"); + mr.enabled = true; + for (int i = 0; i < 3; i++) + { + foam.widthOverride = wid[i]; foam.Rebuild(); + yield return null; yield return new WaitForSeconds(0.2f); + ws[i + 1] = D + "i_w" + (i + 1) + ".png"; + float a = WL816j2_Tune.Ms(live, 60), b = WL816j2_Tune.Ms(live, 60); + L(" [폭 " + wid[i] + " m · 삼각형 " + WLShoreFoam.Tris + "] " + WL816j2_Tune.Shot(live, ws[i + 1]) + + " · " + a.ToString("F3") + " / " + b.ToString("F3") + " ms"); + } + WL816j2_Tune.Strip(ws, D + "i_band_steps.png"); + foam.widthOverride = 0f; foam.Rebuild(); yield return null; + + // ── 3) 최종 전/후 (PD 구도) ───────────────────────────────── + var Dm = UnityEditor.AssetDatabase.LoadAssetAtPath(MatDir + "Farm_Water_WL_D.mat"); + L(""); + L("3) 최종 전/후 (PD 구도)"); + mr.enabled = false; water.sharedMaterial = Dm; yield return null; yield return new WaitForSeconds(0.3f); + L(" [전] main(물 D · 띠 없음) " + WL816j2_Tune.Shot(live, D + "z_before_pd.png")); + mr.enabled = true; water.sharedMaterial = adopt; yield return null; yield return new WaitForSeconds(0.3f); + L(" [후] 816j2(물 E · 띠 on) " + WL816j2_Tune.Shot(live, D + "z_after_pd.png")); + WL816j2_Tune.Strip(new[] { D + "z_before_pd.png", D + "z_after_pd.png" }, D + "z_pd_before_after.png"); + + Done(); + } + void Done() + { + System.IO.File.WriteAllText("AgentScripts/WL816j2_TUNE.txt", sb.ToString()); + Debug.Log("[816j2 tune]\n" + sb); + } +} diff --git a/AgentScripts/WL816j2_VERIFY.txt b/AgentScripts/WL816j2_VERIFY.txt new file mode 100644 index 000000000..cf56ebb08 --- /dev/null +++ b/AgentScripts/WL816j2_VERIFY.txt @@ -0,0 +1,42 @@ +=== WL-816j2 — PD 구도(FOV 60 · 45° · 22 m · 1280×720)에서 다시 맞춘다 === +카메라 MainCamera pos=(0.00, 15.56, -15.56) 각=(45, 0, 0) fov=60 · 거리 22.0 m +물 = Farm_Water_WL_E · 둘레 띠 = 타일 4 · 띠 삼각형 1280 · 정점 772 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 + +1) 지금(main · 물 D · 띠 없음) 바다 무늬대비 σ=0.0823 · 거품 10.20% 색 #EDDAE7 (R−B 6) → Screenshots_WL/WL816j2/a_now_pd.png + +2) 무늬 흰 선 3단 — LightColor (물색 0.36/0.69/0.82 에 가까울수록 은은) + RGBA(0.800, 0.900, 0.960, 1.000) 바다 무늬대비 σ=0.0787 · 거품 6.47% 색 #E1D4E8 (R−B -7) → Screenshots_WL/WL816j2/b_line1.png + RGBA(0.600, 0.820, 0.910, 1.000) 바다 무늬대비 σ=0.0644 · 거품 1.09% 색 #FCF2F3 (R−B 9) → Screenshots_WL/WL816j2/b_line2.png + RGBA(0.450, 0.740, 0.860, 1.000) 바다 무늬대비 σ=0.0506 · 거품 1.12% 색 #FCF1F3 (R−B 9) → Screenshots_WL/WL816j2/b_line3.png + [기준] 데모 물(FI SimpleWater) 바다 무늬대비 σ=0.0024 · 거품 1.13% 색 #FAF8FC (R−B -2) → Screenshots_WL/WL816j2/b_line0_demo.png + +3) 둘레 띠 폭 3단 (PD 구도) + 폭 0.9 m · 타일 4 · 띠 삼각형 744 · 정점 496 · 폭 0.90 m · 격자 0.50 m · 드로우콜 +1 + 바다 무늬대비 σ=0.0637 · 거품 1.10% 색 #FCF2F3 (R−B 9) → Screenshots_WL/WL816j2/c_band1.png + 폭 1.6 m · 타일 4 · 띠 삼각형 1280 · 정점 772 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 + 바다 무늬대비 σ=0.0641 · 거품 1.23% 색 #FBF5FA (R−B 1) → Screenshots_WL/WL816j2/c_band2.png + 폭 2.6 m · 타일 4 · 띠 삼각형 1864 · 정점 1072 · 폭 2.60 m · 격자 0.50 m · 드로우콜 +1 + 바다 무늬대비 σ=0.0646 · 거품 2.51% 색 #F4F1FC (R−B -8) → Screenshots_WL/WL816j2/c_band3.png + +4) 거품 색 — PD 캡처가 분홍빛이었다. 화면에서 재서 중립 흰색으로 맞춘다(R−B 가 0 에 가까울수록 중립) + _BaseColor RGBA(1.000, 1.000, 1.000, 0.920) 바다 무늬대비 σ=0.0671 · 거품 2.58% 색 #F6F4FD (R−B -7) → Screenshots_WL/WL816j2/d_tint1.png + _BaseColor RGBA(0.930, 0.970, 1.000, 0.920) 바다 무늬대비 σ=0.0689 · 거품 2.42% 색 #F3F2FD (R−B -10) → Screenshots_WL/WL816j2/d_tint2.png + _BaseColor RGBA(0.860, 0.950, 1.000, 0.900) 바다 무늬대비 σ=0.0698 · 거품 2.17% 색 #E9F0FD (R−B -20) → Screenshots_WL/WL816j2/d_tint3.png + +4-b) 물가 확대 — 띠 off 바다 무늬대비 σ=0.0565 · 거품 2.51% 색 #FEE1E5 (R−B 25) → Screenshots_WL/WL816j2/f_zoom_off.png + 물가 확대 — 띠 on 바다 무늬대비 σ=0.0619 · 거품 6.24% 색 #F8F6FD (R−B -5) → Screenshots_WL/WL816j2/f_zoom_on.png + +5) 사방 확인(위에서) + 섬 확장 + 위에서 바다 무늬대비 σ=0.0648 · 거품 0.87% 색 #F6F4FC (R−B -6) → Screenshots_WL/WL816j2/e_top_allsides.png + 1칸 잠금 → 타일 3 · 띠 삼각형 1268 · 정점 766 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 + 바다 무늬대비 σ=0.0648 · 거품 0.34% 색 #FAF9FB (R−B -1) → Screenshots_WL/WL816j2/e_top_locked.png + 다시 열기 → 타일 4 · 띠 삼각형 1280 · 정점 772 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 · 띠 재생성 2회 + 바다 무늬대비 σ=0.0647 · 거품 0.80% 색 #F7F5FD (R−B -6) → Screenshots_WL/WL816j2/e_top_unlocked.png + +6) 성능 (PD 구도 1280×720 · 60프레임 오프스크린 · 순서 교대 2회) + 0회차 띠 off = 0.817 ms/frame + 0회차 띠 on = 0.896 ms/frame + 1회차 띠 off = 0.846 ms/frame + 1회차 띠 on = 0.942 ms/frame + 띠 = 렌더러 1 · 머티리얼 1 · 드로우콜 +1 · 타일 4 · 띠 삼각형 1280 · 정점 772 · 폭 1.60 m · 격자 0.50 m · 드로우콜 +1 + 화면 통계(에디터) drawCalls=6720 batches=6720 tris=1646610 diff --git a/AgentScripts/WL816j2_Verify.cs b/AgentScripts/WL816j2_Verify.cs new file mode 100644 index 000000000..c318cc71c --- /dev/null +++ b/AgentScripts/WL816j2_Verify.cs @@ -0,0 +1,285 @@ +// WL-816j2 — ② 🔴 판정은 **PD 카메라 구도**(MainCamera · FOV 60 · 45° · 22 m · 1280×720)에서. +// 무늬 선 3단 · 띠 폭 3단 · 거품 색 중립화 · 사방 확인 · 성능(드로우콜·ms/frame) 전/후. +// 🔴 Play 중 에셋 수정 0 — 물은 런타임 Material 복사본, 띠는 컴포넌트의 런타임 오버라이드. +using System.Collections; +using System.Text; +using UnityEngine; +using UnityEngine.Rendering.Universal; +using UnityEngine.SceneManagement; +using WL.Look.Farm; +using FIIsland = CryingSnow.FarmingIsland.Island; + +public static class WL816j2_Verify +{ + public const string D = "Screenshots_WL/WL816j2/"; + public const int PW = 1280, PH = 720; // 🔴 PD 화면과 같은 해상도·화각 + public const string C_Light = "Color_1ec6cccf8ead489ebb917674f7e8b3b1"; + public const string C_Foam = "Color_e1f155248d144786a62fc1585ca21e9a"; + public const string R_FoamDistance = "Vector1_101dc4546b684d40bc2ff2fc59ab43d5"; + public const string R_FoamEdge = "Vector1_34f757bec6b8422b9aef3b9d97117a6e"; + + public static void Open() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + } + public static void Start() + { + var go = GameObject.Find("~WL816j2V"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816j2V"); + go.AddComponent(); + } + + 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 t = new Texture2D(w, h, TextureFormat.RGB24, false); + t.ReadPixels(new Rect(0, 0, w, h), 0, 0); t.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; rt.Release(); Object.DestroyImmediate(rt); + return t; + } + /// PD 구도로 찍고 ⓐ 물 무늬 선 대비 ⓑ 거품(밝은 픽셀) 평균색·비율을 잰다. + public static string Shot(Camera cam, string path) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var t = Grab(cam, PW, PH); + System.IO.File.WriteAllBytes(path, t.EncodeToPNG()); + var px = t.GetPixels32(); + // ⓐ 무늬 선 대비 — 🔴 **바다만 있는 왼쪽 아래 구역**(섬·UI 가 절대 안 들어온다)에서만 잰다 + double sum = 0, sum2 = 0; int n = 0; + for (int y = 20; y < 320; y += 2) + for (int x = 20; x < 340; x += 2) + { + var c = px[y * PW + x]; + float lum = (0.299f * c.r + 0.587f * c.g + 0.114f * c.b) / 255f; + sum += lum; sum2 += lum * lum; n++; + } + double mean = n > 0 ? sum / n : 0, sd = n > 0 ? System.Math.Sqrt(System.Math.Max(0, sum2 / n - mean * mean)) : 0; + // ⓑ 거품(밝은 픽셀) — 화면 아래 55 %(UI 회피)에서 비율과 색 + long fr = 0, fg = 0, fb = 0; int fn = 0, tot = 0; + int y1 = (int)(PH * 0.55f); + for (int y = 0; y < y1; y++) + for (int x = 0; x < PW; x += 2) + { + var c = px[y * PW + x]; tot++; + if (c.r > 200 && c.g > 195 && c.b > 195) { fr += c.r; fg += c.g; fb += c.b; fn++; } + } + Object.DestroyImmediate(t); + string foam = fn == 0 ? "거품 0%" : + "거품 " + (100f * fn / tot).ToString("F2") + "% 색 #" + + ((int)(fr / fn)).ToString("X2") + ((int)(fg / fn)).ToString("X2") + ((int)(fb / fn)).ToString("X2") + + " (R−B " + ((int)(fr / fn) - (int)(fb / fn)) + ")"; + return "바다 무늬대비 σ=" + sd.ToString("F4") + " · " + foam + " → " + path; + } + public static float Ms(Camera cam, int frames) + { + var rt = new RenderTexture(PW, PH, 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++) + { + 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 = Mathf.Max(w, ts[i].width); h += ts[i].height; + } + var o = new Texture2D(w, h, TextureFormat.RGB24, false); + var f = new Color32[w * h]; + for (int i = 0; i < f.Length; i++) f[i] = new Color32(24, 24, 28, 255); + o.SetPixels32(f); + int y = h; + for (int i = 0; i < ts.Length; i++) { y -= ts[i].height; o.SetPixels32(0, y, ts[i].width, ts[i].height, ts[i].GetPixels32()); } + 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 class WL816j2_VerifyRunner : MonoBehaviour +{ + static StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + const string D = WL816j2_Verify.D; + const string MatDir = "Assets/WL/Look/Farm/Materials/"; + + void Start() { StartCoroutine(Co()); } + + IEnumerator Co() + { + sb = new StringBuilder(); + L("=== WL-816j2 — PD 구도(FOV 60 · 45° · 22 m · 1280×720)에서 다시 맞춘다 ==="); + var op = SceneManager.LoadSceneAsync("Level01", LoadSceneMode.Additive); + while (op != null && !op.isDone) yield return null; + for (int i = 0; i < 8; i++) yield return new WaitForSeconds(1f); + + 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("~WL816")) continue; + if (live == null || c.depth > live.depth) live = c; + } + var foam = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (water == null || live == null) { L("🔴 물/카메라 없음"); Done(); yield break; } + L("카메라 " + live.name + " pos=" + live.transform.position.ToString("F2") + " 각=" + live.transform.eulerAngles.ToString("F0") + + " fov=" + live.fieldOfView + " · 거리 " + live.transform.position.magnitude.ToString("F1") + " m"); + L("물 = " + water.sharedMaterial.name + " · 둘레 띠 = " + (foam == null ? "🔴 없음" : WLShoreFoam.LastLog)); + var E = water.sharedMaterial; + var Dm = UnityEditor.AssetDatabase.LoadAssetAtPath(MatDir + "Farm_Water_WL_D.mat"); + + // ── 1) 지금(main 상태 = 물 D · 띠 없음) ───────────────────── + if (foam != null) foam.GetComponent().enabled = false; + water.sharedMaterial = Dm; yield return null; yield return new WaitForSeconds(0.3f); + L(""); + L("1) 지금(main · 물 D · 띠 없음) " + WL816j2_Verify.Shot(live, D + "a_now_pd.png")); + + // ── 2) 무늬 선 3단 (띠 없이 · 물만) ────────────────────────── + L(""); + L("2) 무늬 흰 선 3단 — LightColor (물색 0.36/0.69/0.82 에 가까울수록 은은)"); + var lc = new[] { new Color(0.80f, 0.90f, 0.96f), new Color(0.60f, 0.82f, 0.91f), new Color(0.45f, 0.74f, 0.86f) }; + var ls = new string[3]; + for (int i = 0; i < 3; i++) + { + var m = new Material(E); m.SetColor(WL816j2_Verify.C_Light, lc[i]); + water.sharedMaterial = m; yield return null; yield return new WaitForSeconds(0.2f); + ls[i] = D + "b_line" + (i + 1) + ".png"; + L(" " + lc[i] + " " + WL816j2_Verify.Shot(live, ls[i])); + } + WL816j2_Verify.Strip(ls, D + "b_line_steps.png"); + + // 데모(FI 원본) 물의 선 대비 = 기준 + var FI = UnityEditor.AssetDatabase.LoadAssetAtPath(MatDir + "Farm_SimpleWater_Demo.mat"); + water.sharedMaterial = FI; yield return null; yield return new WaitForSeconds(0.2f); + L(" [기준] 데모 물(FI SimpleWater) " + WL816j2_Verify.Shot(live, D + "b_line0_demo.png")); + + // ── 3) 띠 폭 3단 (물 = E · 선 중간값) ─────────────────────── + var pick = new Material(E); pick.SetColor(WL816j2_Verify.C_Light, lc[1]); + water.sharedMaterial = pick; yield return null; + L(""); + L("3) 둘레 띠 폭 3단 (PD 구도)"); + if (foam != null) + { + foam.GetComponent().enabled = true; + var ws = new string[3]; float[] wid = { 0.9f, 1.6f, 2.6f }; + for (int i = 0; i < 3; i++) + { + foam.widthOverride = wid[i]; foam.Rebuild(); + yield return null; yield return new WaitForSeconds(0.2f); + ws[i] = D + "c_band" + (i + 1) + ".png"; + L(" 폭 " + wid[i] + " m · " + WLShoreFoam.LastLog); + L(" " + WL816j2_Verify.Shot(live, ws[i])); + } + WL816j2_Verify.Strip(ws, D + "c_band_steps.png"); + foam.widthOverride = 0f; foam.Rebuild(); yield return null; + } + else L(" 🔴 띠 컴포넌트가 없다"); + + // ── 4) 거품 색 중립화 — 화면에서 R−B 를 0 에 가깝게 ───────── + L(""); + L("4) 거품 색 — PD 캡처가 분홍빛이었다. 화면에서 재서 중립 흰색으로 맞춘다(R−B 가 0 에 가까울수록 중립)"); + if (foam != null) + { + var mr = foam.GetComponent(); + var tints = new[] { new Color(1f,1f,1f,0.92f), new Color(0.93f,0.97f,1f,0.92f), new Color(0.86f,0.95f,1f,0.90f) }; + var cs = new string[3]; + for (int i = 0; i < 3; i++) + { + var mi = new Material(foam.cfg.shoreFoamMaterial); mi.SetColor("_BaseColor", tints[i]); + mr.sharedMaterial = mi; yield return null; yield return new WaitForSeconds(0.2f); + cs[i] = D + "d_tint" + (i + 1) + ".png"; + L(" _BaseColor " + tints[i] + " " + WL816j2_Verify.Shot(live, cs[i])); + } + WL816j2_Verify.Strip(cs, D + "d_tint_steps.png"); + } + + // ── 5) 사방 확인 + 확장 ───────────────────────────────────── + // ── 4-b) 물가 확대 (띠가 어떻게 보이는지) ─────────────────── + if (foam != null) + { + var mr2 = foam.GetComponent(); + mr2.sharedMaterial = foam.cfg.shoreFoamMaterial; + var b = mr2.bounds; + var zgo = new GameObject("~WL816j2Zoom"); zgo.hideFlags = HideFlags.DontSave; + var zc = zgo.AddComponent(); + zc.fieldOfView = 34f; zc.nearClipPlane = 0.1f; zc.farClipPlane = 2000f; + zc.clearFlags = CameraClearFlags.Skybox; zc.cullingMask = ~0; zc.depth = -100f; + zgo.AddComponent().renderPostProcessing = true; + zgo.transform.position = new Vector3(b.center.x, 3.2f, b.min.z - 9f); + zgo.transform.LookAt(new Vector3(b.center.x, -1f, b.min.z + 2f)); + mr2.enabled = false; yield return null; yield return null; + L(""); + L("4-b) 물가 확대 — 띠 off " + WL816j2_Verify.Shot(zc, D + "f_zoom_off.png")); + mr2.enabled = true; yield return null; yield return null; + L(" 물가 확대 — 띠 on " + WL816j2_Verify.Shot(zc, D + "f_zoom_on.png")); + WL816j2_Verify.Strip(new[] { D + "f_zoom_off.png", D + "f_zoom_on.png" }, D + "f_zoom_pair.png"); + Object.DestroyImmediate(zgo); + } + + L(""); + L("5) 사방 확인(위에서) + 섬 확장"); + var top = new GameObject("~WL816j2Top"); top.hideFlags = HideFlags.DontSave; + var tc = top.AddComponent(); + tc.fieldOfView = 55f; tc.nearClipPlane = 0.1f; tc.farClipPlane = 2000f; + tc.clearFlags = CameraClearFlags.Skybox; tc.cullingMask = ~0; tc.depth = -100f; + top.AddComponent().renderPostProcessing = true; + top.transform.position = new Vector3(0f, 42f, -12f); top.transform.LookAt(Vector3.zero); + L(" 위에서 " + WL816j2_Verify.Shot(tc, D + "e_top_allsides.png")); + + var islands = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + FIIsland target = null; + foreach (var isl in islands) if (isl != null && isl.IsUnlocked && isl.gameObject.activeInHierarchy) target = isl; + if (target != null) + { + target.gameObject.SetActive(false); yield return new WaitForSeconds(2.5f); + L(" 1칸 잠금 → " + WLShoreFoam.LastLog); + L(" " + WL816j2_Verify.Shot(tc, D + "e_top_locked.png")); + target.gameObject.SetActive(true); yield return new WaitForSeconds(3.5f); + L(" 다시 열기 → " + WLShoreFoam.LastLog + " · 띠 재생성 " + WL.Look.Farm.WLIslandLook.ShoreFoamRebuilds + "회"); + L(" " + WL816j2_Verify.Shot(tc, D + "e_top_unlocked.png")); + } + WL816j2_Verify.Strip(new[] { D + "e_top_allsides.png", D + "e_top_locked.png", D + "e_top_unlocked.png" }, D + "e_expand_steps.png"); + + // ── 6) 성능 — 띠 on/off 교대 2회 ──────────────────────────── + L(""); + L("6) 성능 (PD 구도 1280×720 · 60프레임 오프스크린 · 순서 교대 2회)"); + var fr = foam != null ? foam.GetComponent() : null; + for (int round = 0; round < 2; round++) + { + if (fr != null) { fr.enabled = false; yield return null; yield return null; } + L(" " + round + "회차 띠 off = " + WL816j2_Verify.Ms(live, 60).ToString("F3") + " ms/frame"); + if (fr != null) { fr.enabled = true; yield return null; yield return null; } + L(" " + round + "회차 띠 on = " + WL816j2_Verify.Ms(live, 60).ToString("F3") + " ms/frame"); + } + L(" 띠 = 렌더러 1 · 머티리얼 1 · 드로우콜 +1 · " + WLShoreFoam.LastLog); + L(" 화면 통계(에디터) drawCalls=" + UnityEditor.UnityStats.drawCalls + " batches=" + UnityEditor.UnityStats.batches + + " tris=" + UnityEditor.UnityStats.triangles); + + Object.DestroyImmediate(top); + Done(); + } + + void Done() + { + System.IO.File.WriteAllText("AgentScripts/WL816j2_VERIFY.txt", sb.ToString()); + Debug.Log("[816j2 verify]\n" + sb); + } +} diff --git a/AgentScripts/WL816j2_WHICH.txt b/AgentScripts/WL816j2_WHICH.txt new file mode 100644 index 000000000..71bf7731d --- /dev/null +++ b/AgentScripts/WL816j2_WHICH.txt @@ -0,0 +1,8 @@ +=== WL-816j2 ⑤ 격자선의 정체 (색으로 찍기) === +1) Light=초록 Shadow=파랑 Foam=빨강 Water=흰 Dark=검 밝은점 #FEE3E6 어두운점 #1515F8 → Screenshots_WL/WL816j2/k_which_paint.png +2) 기준(E 그대로) 밝은점 #B6C4E3 어두운점 #5091BF → Screenshots_WL/WL816j2/k_off0.png +3) FoamEdge 100(가장자리 거품 완전 off) 밝은점 #B6C3E3 어두운점 #5091BF → Screenshots_WL/WL816j2/k_off1.png +4) LightColor = WaterColor 밝은점 #97B9DA 어두운점 #5091BF → Screenshots_WL/WL816j2/k_off2.png +5) FoamShadowColor = 흰색 밝은점 #B6C3E3 어두운점 #5091BF → Screenshots_WL/WL816j2/k_off3.png +6) 셋 다 밝은점 #97B9DA 어두운점 #5091BF → Screenshots_WL/WL816j2/k_off4.png +7) 셋 다 + Height 0(버텍스 파도 off) 밝은점 #97B9DA 어두운점 #5091BF → Screenshots_WL/WL816j2/k_off5.png diff --git a/AgentScripts/WL816j2_Which.cs b/AgentScripts/WL816j2_Which.cs new file mode 100644 index 000000000..0969d324d --- /dev/null +++ b/AgentScripts/WL816j2_Which.cs @@ -0,0 +1,111 @@ +// WL-816j2 — ⑤ 바다의 흰 격자선을 만드는 속성이 무엇인지 **색으로 찍어서** 가려낸다 +using System.Collections; +using System.Text; +using UnityEngine; + +public static class WL816j2_Which +{ + public const string D = "Screenshots_WL/WL816j2/"; + public static void Start() + { + var go = GameObject.Find("~WL816j2W"); + if (go != null) Object.DestroyImmediate(go); + go = new GameObject("~WL816j2W"); + go.AddComponent(); + } +} + +public class WL816j2_WhichRunner : MonoBehaviour +{ + static StringBuilder sb; + static void L(string s) { sb.AppendLine(s); } + const string D = WL816j2_Which.D; + const int PW = 1280, PH = 720; + const string C_Light = "Color_1ec6cccf8ead489ebb917674f7e8b3b1"; + const string C_FoamShadow = "Color_2323d69962e04c499c5d5f0925432b55"; + const string C_Water = "Color_9af4ad59934f40d1ae6565e6ab22c45e"; + const string C_Dark = "Color_ca031ae309ff42bdb95f777248fb961d"; + const string C_Foam = "Color_e1f155248d144786a62fc1585ca21e9a"; + const string R_FoamEdge = "Vector1_34f757bec6b8422b9aef3b9d97117a6e"; + const string R_FoamDistance = "Vector1_101dc4546b684d40bc2ff2fc59ab43d5"; + const string R_DeepDistance = "Vector1_566e288f42864b8e9432d81fbdb83f38"; + const string R_Height = "Vector1_d813b569a4d541d1b2ac4ea9bc66a3b9"; + + void Start() { StartCoroutine(Co()); } + + string Shot(Camera cam, string path) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new RenderTexture(PW, PH, 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 t = new Texture2D(PW, PH, TextureFormat.RGB24, false); + t.ReadPixels(new Rect(0, 0, PW, PH), 0, 0); t.Apply(); + RenderTexture.active = pA; cam.targetTexture = pT; rt.Release(); Object.DestroyImmediate(rt); + System.IO.File.WriteAllBytes(path, t.EncodeToPNG()); + // 바다만 있는 구역에서 가장 밝은/어두운 픽셀의 색 + var px = t.GetPixels32(); + int bi = 0, di = 0; float bl = -1, dl = 2; + for (int y = 20; y < 320; y += 2) + for (int x = 20; x < 340; x += 2) + { + int k = y * PW + x; var c = px[k]; + float l = (0.299f * c.r + 0.587f * c.g + 0.114f * c.b) / 255f; + if (l > bl) { bl = l; bi = k; } + if (l < dl) { dl = l; di = k; } + } + var b2 = px[bi]; var d2 = px[di]; + Object.DestroyImmediate(t); + return "밝은점 #" + b2.r.ToString("X2") + b2.g.ToString("X2") + b2.b.ToString("X2") + + " 어두운점 #" + d2.r.ToString("X2") + d2.g.ToString("X2") + d2.b.ToString("X2") + " → " + path; + } + + IEnumerator Co() + { + sb = new StringBuilder(); + L("=== WL-816j2 ⑤ 격자선의 정체 (색으로 찍기) ==="); + 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("~WL816")) continue; + if (live == null || c.depth > live.depth) live = c; + } + if (water == null || live == null) { L("🔴 없음"); Done(); yield break; } + var E = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat"); + + // ① 색 찍기 — LightColor=초록 · FoamShadowColor=파랑 · FoamColor=빨강 · Water=흰 · Dark=검 + var m = new Material(E); + m.SetColor(C_Water, Color.white); m.SetColor(C_Dark, Color.black); + m.SetColor(C_Light, Color.green); m.SetColor(C_FoamShadow, Color.blue); m.SetColor(C_Foam, Color.red); + water.sharedMaterial = m; yield return null; yield return new WaitForSeconds(0.3f); + L("1) Light=초록 Shadow=파랑 Foam=빨강 Water=흰 Dark=검 " + Shot(live, D + "k_which_paint.png")); + + // ② 하나씩 끄기 + var tests = new (string, System.Action)[] + { + ("기준(E 그대로)", mm => { }), + ("FoamEdge 100(가장자리 거품 완전 off)", mm => mm.SetFloat(R_FoamEdge, 100f)), + ("LightColor = WaterColor", mm => mm.SetColor(C_Light, mm.GetColor(C_Water))), + ("FoamShadowColor = 흰색", mm => mm.SetColor(C_FoamShadow, Color.white)), + ("셋 다", mm => { mm.SetFloat(R_FoamEdge, 100f); mm.SetColor(C_Light, mm.GetColor(C_Water)); mm.SetColor(C_FoamShadow, Color.white); }), + ("셋 다 + Height 0(버텍스 파도 off)", mm => { mm.SetFloat(R_FoamEdge, 100f); mm.SetColor(C_Light, mm.GetColor(C_Water)); mm.SetColor(C_FoamShadow, Color.white); mm.SetFloat(R_Height, 0f); }), + }; + for (int i = 0; i < tests.Length; i++) + { + var mm = new Material(E); tests[i].Item2(mm); + water.sharedMaterial = mm; yield return null; yield return new WaitForSeconds(0.25f); + L((i + 2) + ") " + tests[i].Item1 + " " + Shot(live, D + "k_off" + i + ".png")); + } + Done(); + } + void Done() + { + System.IO.File.WriteAllText("AgentScripts/WL816j2_WHICH.txt", sb.ToString()); + Debug.Log("[816j2 which]\n" + sb); + } +} diff --git a/Assets/WL/Look/Farm/Materials/Farm_ShoreFoam.mat b/Assets/WL/Look/Farm/Materials/Farm_ShoreFoam.mat new file mode 100644 index 000000000..b414d4a0a --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_ShoreFoam.mat @@ -0,0 +1,74 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Farm_ShoreFoam + m_Shader: {fileID: 4800000, guid: 650dd9526735d5b46b79224bc6e94025, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _SURFACE_TYPE_TRANSPARENT + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 1 + m_CustomRenderQueue: 3050 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: + - MOTIONVECTORS + - DepthOnly + - SHADOWCASTER + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 2800000, guid: 7501e662a59f9e94f975c263139cfc57, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: 7501e662a59f9e94f975c263139cfc57, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AddPrecomputedVelocity: 0 + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendOp: 0 + - _Cull: 0 + - _Cutoff: 0.5 + - _DstBlend: 10 + - _DstBlendAlpha: 10 + - _QueueOffset: 50 + - _SampleGI: 0 + - _SrcBlend: 5 + - _SrcBlendAlpha: 1 + - _Surface: 1 + - _XRMotionVectorsPass: 1 + - _ZWrite: 0 + m_Colors: + - _BaseColor: {r: 1, g: 1, b: 1, a: 0.92} + - _Color: {r: 1, g: 1, b: 1, a: 0.92} + m_BuildTextureStacks: [] + m_AllowLocking: 1 +--- !u!114 &4198336840671140510 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 diff --git a/Assets/WL/Look/Farm/Materials/Farm_ShoreFoam.mat.meta b/Assets/WL/Look/Farm/Materials/Farm_ShoreFoam.mat.meta new file mode 100644 index 000000000..32d7726f9 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_ShoreFoam.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d24a4c5ce16646c409631c112b57f2cf +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat new file mode 100644 index 000000000..d42a39512 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat @@ -0,0 +1,99 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Farm_Water_WL_E + m_Shader: {fileID: -6465566751694194690, guid: 85b142f88e4bec1488c6b275a0e5dc57, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - Texture2D_086cedcb1d864765840f9996e4b0002d: + m_Texture: {fileID: 2800000, guid: 945a5174b1a6da34ea1028ed2feeb1e9, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_1d2b8f4484d74423a27218f18b962ade: + m_Texture: {fileID: 2800000, guid: d48aae5302069a94f9410b8d6b76fed1, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_4094dc7f19e94579ac3bbc9c811f51a4: + m_Texture: {fileID: 2800000, guid: 62100fbe73f198245b71c0a6eeeb94f6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - Texture2D_685ca00e8e7c4cb2b45a9e11981bb64a: + m_Texture: {fileID: 2800000, guid: 5088aa2234cf5b24cad13def77f7da5f, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - Vector1_07238257334b4a149e675e2451801030: 166.66667 + - Vector1_101dc4546b684d40bc2ff2fc59ab43d5: 2.2 + - Vector1_11671d7a4cdb4d059a6c69e39f3f7b3f: 1333.3334 + - Vector1_21ae4fce4585460386edec89c9895501: 0 + - Vector1_2b4963cef1de4859983ffcbae94c6f60: 0 + - Vector1_3089b6a325a44c1686907af4ee3b0776: 0.6666667 + - Vector1_34f757bec6b8422b9aef3b9d97117a6e: 4 + - Vector1_566e288f42864b8e9432d81fbdb83f38: 7 + - Vector1_68f0c21b684f4b7c9ebef8789113b107: 2 + - Vector1_7090c8ca8ea84e2d85858ae4beb5be38: 1 + - Vector1_85ea9246e72a44ed9e0482861767c826: 0.03 + - Vector1_8e22a98f75c94b218c49e6c4805e799d: 0.13333334 + - Vector1_99cc56b5c6fd474082f07f00a6df94f9: 0.0018509999 + - Vector1_ad7e0fcd37f44d68b68de6f06bb43b96: 0.13333334 + - Vector1_c6f2603b30534ec8b0039379573789e3: 1 + - Vector1_d813b569a4d541d1b2ac4ea9bc66a3b9: 0.09 + - Vector1_dd1e17693dc14b10b91828bdfd5adaa8: 0 + - Vector1_ff7a2780aaa94ddd86e156c4707e39a2: 0.4 + - _QueueControl: 0 + - _QueueOffset: 0 + - _XRMotionVectorsPass: 1 + m_Colors: + - Color_1ec6cccf8ead489ebb917674f7e8b3b1: {r: 0.42, g: 0.72, b: 0.845, a: 1} + - Color_2323d69962e04c499c5d5f0925432b55: {r: 1, g: 1, b: 1, a: 1} + - Color_9af4ad59934f40d1ae6565e6ab22c45e: {r: 0.36, g: 0.69, b: 0.82, a: 1} + - Color_ca031ae309ff42bdb95f777248fb961d: {r: 0.17, g: 0.42, b: 0.57, a: 1} + - Color_e1f155248d144786a62fc1585ca21e9a: {r: 0.35999998, g: 0.69, b: 0.82, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 +--- !u!114 &7840779067325084833 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 diff --git a/Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat.meta b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat.meta new file mode 100644 index 000000000..9d4d458b0 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_Water_WL_E.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7432692635bf09d4eb5271f2b366839e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset index b86a165e6..e178818fe 100644 --- a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset @@ -64,7 +64,16 @@ MonoBehaviour: skyReleaseDelaySeconds: 1.5 waterMode: 1 waterFrom: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2} - waterTo: {fileID: 2100000, guid: 064fa97251666874e9da5473b09d43bd, type: 2} + waterTo: {fileID: 2100000, guid: 7432692635bf09d4eb5271f2b366839e, type: 2} + shoreFoamEnabled: 1 + shoreFoamMaterial: {fileID: 2100000, guid: d24a4c5ce16646c409631c112b57f2cf, type: 2} + shoreFoamWidth: 1.6 + shoreFoamInner: 0.5 + shoreFoamCell: 0.5 + shoreFoamY: 0.02 + shoreFoamFallbackY: -1 + shoreFoamTileLength: 6 + shoreFoamScroll: 0.06 grassEnabled: 1 scatter: - enabled_: 1 diff --git a/Assets/WL/Look/Farm/Textures/WL_ShoreFoam.png b/Assets/WL/Look/Farm/Textures/WL_ShoreFoam.png new file mode 100644 index 000000000..aef3bf3fc Binary files /dev/null and b/Assets/WL/Look/Farm/Textures/WL_ShoreFoam.png differ diff --git a/Assets/WL/Look/Farm/Textures/WL_ShoreFoam.png.meta b/Assets/WL/Look/Farm/Textures/WL_ShoreFoam.png.meta new file mode 100644 index 000000000..b9b4dcc90 --- /dev/null +++ b/Assets/WL/Look/Farm/Textures/WL_ShoreFoam.png.meta @@ -0,0 +1,181 @@ +fileFormatVersion: 2 +guid: 7501e662a59f9e94f975c263139cfc57 +TextureImporter: + internalIDToNameTable: + - first: + 213: -134333910614944054 + second: WL_ShoreFoam_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 1 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 512 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: iOS + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: WL_ShoreFoam_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 512 + height: 128 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: ac6d1802500c22ef0800000000000000 + internalID: -134333910614944054 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/WLIslandLook.cs b/Assets/WL/Look/Farm/WLIslandLook.cs index 04c8a228b..3070641a1 100644 --- a/Assets/WL/Look/Farm/WLIslandLook.cs +++ b/Assets/WL/Look/Farm/WLIslandLook.cs @@ -132,13 +132,15 @@ namespace WL.Look.Farm if (cfg.soilToneEnabled != 0) ToneSoil(cfg, scene); if (cfg.grassEnabled != 0) SpawnGrass(cfg, scene); + if (cfg.shoreFoamEnabled != 0) SpawnShoreFoam(cfg, scene); HookIslands(cfg, scene); cfg.Log("섬 룩 적용 — 렌더러 " + SwappedRenderers + " · 슬롯 " + SwappedSlots + " · 조명 " + (cfg.applyLighting != 0 ? "데모" : "원본") + " · ReferenceLook " + (ReferenceLookApplied ? "on" : "off") - + " · 풀 " + WLIslandGrass.LastLog); + + " · 풀 " + WLIslandGrass.LastLog + + " · 둘레거품 " + WLShoreFoam.LastLog); // 늦게 생기는 오브젝트(상인·작물·아이템)까지 다시 훑는다 if (cfg.rescanAtSeconds != null) @@ -221,11 +223,37 @@ namespace WL.Look.Farm static void RequestGrassRebuild(WLIslandLookSettings cfg) { - if (cfg.grassEnabled == 0) return; - var g = Object.FindFirstObjectByType(FindObjectsInactive.Include); - if (g == null) return; - g.Rebuild(); - GrassRebuilds++; + if (cfg.grassEnabled != 0) + { + var g = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (g != null) { g.Rebuild(); GrassRebuilds++; } + } + // 🔴 816j2 — 섬이 확장되면 **둘레 거품 띠도 같은 훅으로** 다시 만든다(새 가장자리 자동). + if (cfg.shoreFoamEnabled != 0) + { + var f = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (f != null) { f.cfg = cfg; f.Rebuild(); ShoreFoamRebuilds++; } + } + } + + public static int ShoreFoamRebuilds; + public static bool ShoreFoamSpawned; + + /// 섬 둘레 거품 띠 — 풀과 같은 자리에서 만들고 같은 훅으로 다시 만든다(816j2). + public static void SpawnShoreFoam(WLIslandLookSettings cfg, Scene scene) + { + var f = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (f == null) + { + var go = new GameObject(WLShoreFoam.ObjectName); + SceneManager.MoveGameObjectToScene(go, scene); + go.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); + f = go.AddComponent(); + f.cfg = cfg; + f.Rebuild(); // OnEnable 이 cfg 전에 돌 수 있다 + } + else { f.cfg = cfg; f.Rebuild(); } + ShoreFoamSpawned = true; } // ───────────────────────────────────────────────────────────────── diff --git a/Assets/WL/Look/Farm/WLIslandLookSettings.cs b/Assets/WL/Look/Farm/WLIslandLookSettings.cs index 9171060c8..0453268e3 100644 --- a/Assets/WL/Look/Farm/WLIslandLookSettings.cs +++ b/Assets/WL/Look/Farm/WLIslandLookSettings.cs @@ -197,6 +197,35 @@ namespace WL.Look.Farm [Tooltip("WL 워터 복사본(`Assets/WL/Materials/WL_Water_Ocean.mat` 기준). 비우면 교체하지 않는다.")] public Material waterTo; + // ───────────────────────────────── §816j2 섬 둘레 거품 띠 + [Header("§816j2 — 섬 둘레 흰 거품 띠 (PD 「둘레에 띠를 둘러줘」)")] + [Tooltip("0 = 띠 없음(816j 상태로 100 % 복귀) · 1 = 섬 둘레를 따라 메시 띠 1장(드로우콜 +1).")] + public int shoreFoamEnabled = 1; + + [Tooltip("띠에 쓰는 머티리얼(URP Unlit · 반투명). 비우면 띠를 만들지 않는다.")] + public Material shoreFoamMaterial; + + [Tooltip("띠가 물 쪽으로 뻗는 폭(m). 캐릭터 키 1.19 m · 섬 타일 8 m 기준.")] + public float shoreFoamWidth = 1.6f; + + [Tooltip("섬 안쪽으로 겹쳐 들어가는 길이(m) — 물가에 틈이 생기지 않게. 섬 벽이 가려 준다.")] + public float shoreFoamInner = 0.5f; + + [Tooltip("띠 메시의 격자 간격(m). 크면 싸고 거칠다. 비용이 문제면 이것부터 키운다.")] + public float shoreFoamCell = 0.5f; + + [Tooltip("물 표면보다 얼마나 위에 깔 것인가(m). 너무 작으면 z-fighting.")] + public float shoreFoamY = 0.02f; + + [Tooltip("씬에서 `Water` 를 못 찾았을 때 쓸 물 높이(m).")] + public float shoreFoamFallbackY = -1f; + + [Tooltip("거품 무늬가 해안을 따라 반복되는 길이(m).")] + public float shoreFoamTileLength = 6f; + + [Tooltip("거품이 흐르는 속도(1 = 초당 무늬 1칸). 0 이면 정지.")] + public float shoreFoamScroll = 0.06f; + // ───────────────────────────────── §1 풀밭 [Header("§1 — 섬 타일 위 풀밭 (데모와 같은 인스턴싱)")] [Tooltip("0 이면 풀을 깔지 않는다.")] diff --git a/Assets/WL/Look/Farm/WLShoreFoam.cs b/Assets/WL/Look/Farm/WLShoreFoam.cs new file mode 100644 index 000000000..359b8f641 --- /dev/null +++ b/Assets/WL/Look/Farm/WLShoreFoam.cs @@ -0,0 +1,269 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLShoreFoam.cs — 섬 **둘레**를 따라 흰 거품 띠를 두른다 (WL-816j2 · #816) +// +// 왜 필요한가 (816j 실측) +// 셰이더의 깊이 기반 Foam 은 **카메라가 보는 쪽 가장자리에만** 거품이 생긴다 +// (섬이 수직 벽 덩어리라 시선이 물을 뚫고 잠긴 벽에 닿는 쪽만 「얕은 물」로 읽힌다). +// PD 가 「둘레에 띠를 둘러줘」로 결정 → 섬 윗면 footprint 의 **바깥 테두리**를 따라 +// 메시 띠를 하나 만들어 물 위에 깐다. 사방 전부에 생기고, 카메라 방향과 무관하다. +// +// 비용 +// 메시 1장 · 머티리얼 1개 · **드로우콜 +1**. 섬 타일 4칸 기준 삼각형 1천 대. +// 비싸면 `shoreFoamCell`(격자 간격)을 키우거나 `shoreFoamWidth`(폭)를 줄인다. +// +// 섬 확장 +// `WLIslandLook.RequestIslandRebuild` 가 풀과 함께 이 띠도 다시 만든다 +// (816e 확장 훅 = `FIIsland.OnActivated` + 잠금 해제 수 폴링) → 새 가장자리 자동. +// +// 🔴 FI 코드 0줄 · 씬 파일 0줄 · 원본 셰이더 0줄. 런타임에 메시만 만든다. +// ───────────────────────────────────────────────────────────────────────────── +using System.Collections.Generic; +using UnityEngine; +using FIIsland = CryingSnow.FarmingIsland.Island; + +namespace WL.Look.Farm +{ + [DisallowMultipleComponent] + public sealed class WLShoreFoam : MonoBehaviour + { + public const string ObjectName = "~WL_ShoreFoam"; + + public static int Rebuilds, Verts, Tris, Tiles; + public static string LastLog = ""; + + public WLIslandLookSettings cfg; + + // 검증용 런타임 오버라이드 — 0/음수면 설정값을 쓴다. 🔴 에셋을 고치지 않고 3단 비교를 하기 위한 것. + [System.NonSerialized] public float widthOverride = 0f; + [System.NonSerialized] public float innerOverride = -1f; + [System.NonSerialized] public float cellOverride = 0f; + + MeshFilter _mf; + MeshRenderer _mr; + Mesh _mesh; + float _scroll; + + void OnEnable() { Ensure(); Rebuild(); } + void OnDisable() { if (_mr != null) _mr.enabled = false; } + + void Ensure() + { + if (_mf == null) _mf = GetComponent(); + if (_mf == null) _mf = gameObject.AddComponent(); + if (_mr == null) _mr = GetComponent(); + if (_mr == null) _mr = gameObject.AddComponent(); + _mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + _mr.receiveShadows = false; + _mr.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; + _mr.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off; + _mr.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion; + if (_mesh == null) + { + _mesh = new Mesh { name = "WL_ShoreFoamMesh" }; + _mesh.MarkDynamic(); + _mesh.hideFlags = HideFlags.DontSave; + } + _mf.sharedMesh = _mesh; + } + + /// 거품이 해안을 따라 천천히 흐른다. 🔴 에셋이 아니라 **런타임 복사본**의 오프셋만 만진다. + void Update() + { + if (cfg == null || _mr == null || _inst == null) return; + float sp = cfg.shoreFoamScroll; + if (sp == 0f) return; + _scroll += Time.deltaTime * sp; + if (_scroll > 1f) _scroll -= 1f; + _inst.SetTextureOffset(IdBase, new Vector2(_scroll, _scroll * 0.37f)); + } + Material _inst; + static readonly int IdBase = Shader.PropertyToID("_BaseMap"); + + // ───────────────────────────────────────────────────────────────── + public void Rebuild() + { + Ensure(); + if (cfg == null) cfg = WLIslandLookSettings.Instance; + if (cfg == null || cfg.enabled_ == 0 || cfg.shoreFoamEnabled == 0) + { _mr.enabled = false; LastLog = "꺼짐"; return; } + + if (cfg.shoreFoamMaterial == null) { _mr.enabled = false; LastLog = "🔴 머티리얼 없음"; return; } + if (_inst == null || _inst.shader != cfg.shoreFoamMaterial.shader) + { + if (_inst != null) Destroy(_inst); + _inst = new Material(cfg.shoreFoamMaterial) { name = "WL_ShoreFoam(runtime)", hideFlags = HideFlags.DontSave }; + } + _mr.sharedMaterial = _inst; + + var tiles = CollectTiles(); + Tiles = tiles.Count; + if (tiles.Count == 0) { _mesh.Clear(); _mr.enabled = false; LastLog = "타일 0"; return; } + + float cell = Mathf.Max(0.15f, cellOverride > 0f ? cellOverride : cfg.shoreFoamCell); + float w = Mathf.Max(0.1f, widthOverride > 0f ? widthOverride : cfg.shoreFoamWidth); + float inner = Mathf.Max(0f, innerOverride >= 0f ? innerOverride : cfg.shoreFoamInner); + float pad = w + inner + cell * 2f; + + // 섬 전체를 덮는 격자 + float minX = float.MaxValue, minZ = float.MaxValue, maxX = float.MinValue, maxZ = float.MinValue; + for (int i = 0; i < tiles.Count; i++) + { + var t = tiles[i]; float h = t.half; + minX = Mathf.Min(minX, t.center.x - h); maxX = Mathf.Max(maxX, t.center.x + h); + minZ = Mathf.Min(minZ, t.center.z - h); maxZ = Mathf.Max(maxZ, t.center.z + h); + } + minX -= pad; minZ -= pad; maxX += pad; maxZ += pad; + int nx = Mathf.Clamp(Mathf.CeilToInt((maxX - minX) / cell) + 1, 2, 1024); + int nz = Mathf.Clamp(Mathf.CeilToInt((maxZ - minZ) / cell) + 1, 2, 1024); + + // ① 안/밖 + var inside = new bool[nx * nz]; + for (int j = 0; j < nz; j++) + { + float z = minZ + j * cell; + for (int i = 0; i < nx; i++) + { + float x = minX + i * cell; + inside[j * nx + i] = OnAnyTile(tiles, x, z); + } + } + + // ② 부호 있는 거리(챔퍼 2패스) — 밖은 +, 안은 − + var d = Chamfer(inside, nx, nz, cell, false); // 밖 → 가장 가까운 안까지 + var e = Chamfer(inside, nx, nz, cell, true); // 안 → 가장 가까운 밖까지 + var sd = new float[nx * nz]; + for (int k = 0; k < sd.Length; k++) sd[k] = inside[k] ? -e[k] : d[k]; + + // ③ 띠 구간([-inner, w])에 걸치는 칸만 삼각형으로 + float y = WaterY(tiles) + cfg.shoreFoamY; + float tileLen = Mathf.Max(0.5f, cfg.shoreFoamTileLength); + var vmap = new int[nx * nz]; + for (int k = 0; k < vmap.Length; k++) vmap[k] = -1; + var verts = new List(4096); + var uvs = new List(4096); + var tris = new List(8192); + + for (int j = 0; j < nz - 1; j++) + for (int i = 0; i < nx - 1; i++) + { + int a = j * nx + i, b = a + 1, c = a + nx, dd = c + 1; + float lo = Mathf.Min(Mathf.Min(sd[a], sd[b]), Mathf.Min(sd[c], sd[dd])); + float hi = Mathf.Max(Mathf.Max(sd[a], sd[b]), Mathf.Max(sd[c], sd[dd])); + if (lo > w || hi < -inner) continue; + int va = V(a), vb = V(b), vc = V(c), vd = V(dd); + tris.Add(va); tris.Add(vc); tris.Add(vb); + tris.Add(vb); tris.Add(vc); tris.Add(vd); + } + + int V(int k) + { + if (vmap[k] >= 0) return vmap[k]; + int i = k % nx, j = k / nx; + float x = minX + i * cell, z = minZ + j * cell; + verts.Add(new Vector3(x, y, z)); + float v = Mathf.Clamp01((sd[k] + inner) / (w + inner)); + uvs.Add(new Vector2((x * 0.7071f + z * 0.7071f) / tileLen, v)); + vmap[k] = verts.Count - 1; + return vmap[k]; + } + + _mesh.Clear(); + if (verts.Count == 0 || tris.Count == 0) { _mr.enabled = false; LastLog = "띠 0"; return; } + _mesh.indexFormat = verts.Count > 65000 + ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16; + _mesh.SetVertices(verts); + _mesh.SetUVs(0, uvs); + _mesh.SetTriangles(tris, 0); + _mesh.RecalculateBounds(); + _mr.enabled = true; + + Rebuilds++; Verts = verts.Count; Tris = tris.Count / 3; + LastLog = "타일 " + Tiles + " · 띠 삼각형 " + Tris + " · 정점 " + Verts + + " · 폭 " + w.ToString("F2") + " m · 격자 " + cell.ToString("F2") + " m · 드로우콜 +1"; + cfg.Log("둘레 거품 띠 — " + LastLog); + } + + // ───────────────────────────────────────────────────────────────── + struct TileRef { public Transform tf; public Vector3 center; public WLTileMask mask; public float half; } + + readonly List _tiles = new List(96); + + List CollectTiles() + { + _tiles.Clear(); + var islands = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + for (int i = 0; i < islands.Length; i++) + { + var isl = islands[i]; + if (isl == null || !isl.IsUnlocked) continue; + if (!isl.gameObject.activeInHierarchy) continue; + if (isl.transform.localScale.x < 0.99f) continue; // 확장 애니메이션 중 + var mf = isl.GetComponent(); + if (mf == null || mf.sharedMesh == null) continue; + var mask = cfg.FindTileMask(mf.sharedMesh.name); + if (mask == null) continue; + _tiles.Add(new TileRef { tf = isl.transform, center = isl.transform.position, mask = mask, half = mask.half }); + } + return _tiles; + } + + static bool OnAnyTile(List tiles, float wx, float wz) + { + for (int i = 0; i < tiles.Count; i++) + { + var t = tiles[i]; + var l = t.tf.InverseTransformPoint(new Vector3(wx, t.center.y, wz)); + if (t.mask.At(l.x, l.z)) return true; + } + return false; + } + + /// 물 평면의 높이 — 섬 씬의 `Water` 를 찾고 없으면 설정값. + float WaterY(List tiles) + { + var scene = tiles.Count > 0 ? tiles[0].tf.gameObject.scene : gameObject.scene; + var roots = scene.GetRootGameObjects(); + for (int i = 0; i < roots.Length; i++) + { + var t = roots[i].transform.Find("Water"); + if (t != null) return t.position.y; + if (roots[i].name == "Water") return roots[i].transform.position.y; + } + return cfg.shoreFoamFallbackY; + } + + /// 챔퍼 거리 변환 — seed 가 true 인 칸에서의 거리(m). + static float[] Chamfer(bool[] inside, int nx, int nz, float cell, bool seedOutside) + { + const float BIG = 1e9f; + var d = new float[nx * nz]; + for (int k = 0; k < d.Length; k++) + { + bool seed = seedOutside ? !inside[k] : inside[k]; + d[k] = seed ? 0f : BIG; + } + float a = cell, b = cell * 1.41421356f; + for (int j = 0; j < nz; j++) + for (int i = 0; i < nx; i++) + { + int k = j * nx + i; float m = d[k]; + if (i > 0) m = Mathf.Min(m, d[k - 1] + a); + if (j > 0) m = Mathf.Min(m, d[k - nx] + a); + if (i > 0 && j > 0) m = Mathf.Min(m, d[k - nx - 1] + b); + if (i < nx - 1 && j > 0) m = Mathf.Min(m, d[k - nx + 1] + b); + d[k] = m; + } + for (int j = nz - 1; j >= 0; j--) + for (int i = nx - 1; i >= 0; i--) + { + int k = j * nx + i; float m = d[k]; + if (i < nx - 1) m = Mathf.Min(m, d[k + 1] + a); + if (j < nz - 1) m = Mathf.Min(m, d[k + nx] + a); + if (i < nx - 1 && j < nz - 1) m = Mathf.Min(m, d[k + nx + 1] + b); + if (i > 0 && j < nz - 1) m = Mathf.Min(m, d[k + nx - 1] + b); + d[k] = m; + } + return d; + } + } +} diff --git a/Assets/WL/Look/Farm/WLShoreFoam.cs.meta b/Assets/WL/Look/Farm/WLShoreFoam.cs.meta new file mode 100644 index 000000000..fe57fc9d1 --- /dev/null +++ b/Assets/WL/Look/Farm/WLShoreFoam.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eb10258ab89aad24c991df4bff681ba4 \ No newline at end of file