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