Project_WL/AgentScripts/WL816k_Tune.cs

260 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// WL-816k — ③ 데모 물 셰이더 값 맞추기 (PD 구도 · 🔴 거리 15 m 강제)
// A) _FoamScale 스윕 (UV 기반 — 섬 물 평면이 데모의 100배)
// B) _Bands 스윕 (깊이 밴딩 = 섬과 만나는 선의 반응)
// C) _Color 스윕 (814t 톤)
// 측정: 바다색 · 무늬 σ · 밝은 픽셀 · 🔴 물가Δ(물가 밝기 먼 바다 밝기 = 섬에 반응하는가)
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Rendering.Universal;
using UnityEngine.SceneManagement;
using WL.Look.Farm;
public static class WL816k_Tune
{
public const string D = "Screenshots_WL/WL816k/";
public const int PW = 1280, PH = 720;
public const string MatDir = "Assets/WL/Look/Farm/Materials/";
public static void Open()
{
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(
"Assets/Scenes/InGame.unity", UnityEditor.SceneManagement.OpenSceneMode.Single);
}
public static void Start()
{
var go = GameObject.Find("~WL816kT");
if (go != null) Object.DestroyImmediate(go);
go = new GameObject("~WL816kT");
go.AddComponent<WL816k_TuneRunner>();
}
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;
}
static float Lum(Color32 c) { return (0.299f * c.r + 0.587f * c.g + 0.114f * c.b) / 255f; }
/// <summary>PD 구도 캡처 + 지표. shoreRows != 0 이면 물가Δ 도 잰다(확대 카메라용).</summary>
public static string Shot(Camera cam, string path, int w, int h, bool shoreMetric)
{
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path));
var t = Grab(cam, w, h);
System.IO.File.WriteAllBytes(path, t.EncodeToPNG());
var px = t.GetPixels32();
long r = 0, g = 0, b = 0; int n = 0; double sum = 0, sum2 = 0;
int xm = Mathf.Min(340, w), ym = Mathf.Min(320, h);
for (int y = 20; y < ym; y += 2)
for (int x = 20; x < xm; x += 2)
{
var c = px[y * w + x]; r += c.r; g += c.g; b += c.b; n++;
float l = Lum(c); sum += l; sum2 += l * l;
}
double mean = n > 0 ? sum / n : 0;
double sd = n > 0 ? System.Math.Sqrt(System.Math.Max(0, sum2 / n - mean * mean)) : 0;
int fn = 0, tot = 0; long fr = 0, fg = 0, fb = 0;
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++; }
}
string extra = "";
if (shoreMetric)
{
// 🔴 물가Δ — 섬과 맞닿은 띠(위쪽 물)와 먼 바다(아래쪽 물)의 밝기 차
double a = 0; int an = 0, bn2 = 0; double b2 = 0;
for (int y = 300; y < 350; y++) for (int x = 100; x < w - 100; x += 2) { a += Lum(px[y * w + x]); an++; }
for (int y = 20; y < 70; y++) for (int x = 100; x < w - 100; x += 2) { b2 += Lum(px[y * w + x]); bn2++; }
double na = an > 0 ? a / an : 0, nb = bn2 > 0 ? b2 / bn2 : 0;
extra = " · 물가Δ " + (na - nb).ToString("F4") + "(물가 " + na.ToString("F3") + " 먼바다 " + nb.ToString("F3") + ")";
}
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");
return "바다 #" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(b / n)).ToString("X2")
+ " σ=" + sd.ToString("F4") + " · " + foam + extra;
}
/// <summary>세로 스트립 — div 로 축소해서 저장(읽기 비용 절감).</summary>
public static void Strip(string[] paths, string outPath, int div)
{
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 / div); h += ts[i].height / div;
}
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 yo = h;
for (int i = 0; i < ts.Length; i++)
{
int sw = ts[i].width / div, sh = ts[i].height / div;
var src = ts[i].GetPixels32(); int fw = ts[i].width;
var dst = new Color32[sw * sh];
for (int y = 0; y < sh; y++)
for (int x = 0; x < sw; x++)
{
int rr = 0, gg = 0, bb = 0;
for (int dy = 0; dy < div; dy++) for (int dx = 0; dx < div; dx++)
{ var c = src[(y * div + dy) * fw + (x * div + dx)]; rr += c.r; gg += c.g; bb += c.b; }
int q = div * div;
dst[y * sw + x] = new Color32((byte)(rr / q), (byte)(gg / q), (byte)(bb / q), 255);
}
yo -= sh; o.SetPixels32(0, yo, sw, sh, dst);
}
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 WL816k_TuneRunner : MonoBehaviour
{
static StringBuilder sb;
static void L(string s) { sb.AppendLine(s); }
const string D = WL816k_Tune.D;
const string MatDir = WL816k_Tune.MatDir;
const int PW = WL816k_Tune.PW, PH = WL816k_Tune.PH;
Camera live, zoom;
Renderer water;
MeshRenderer band;
Material Demo;
void Start() { StartCoroutine(Co()); }
IEnumerator Co()
{
sb = new StringBuilder();
L("=== WL-816k ③ 데모 물 값 맞추기 (PD 구도 · 거리 15 m 강제) ===");
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);
foreach (var r in Object.FindObjectsByType<Renderer>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
if (r != null && r.gameObject.scene.name == "Level01" && r.name == "Water") water = r;
foreach (var c in Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
{
if (!c.gameObject.activeInHierarchy || !c.enabled || c.targetTexture != null) continue;
if (c.name.StartsWith("~WL816")) continue;
if (live == null || c.depth > live.depth) live = c;
}
var foam = Object.FindFirstObjectByType<WLShoreFoam>(FindObjectsInactive.Include);
band = foam != null ? foam.GetComponent<MeshRenderer>() : null;
if (water == null || live == null) { L("🔴 물/카메라 없음"); Done(); yield break; }
// 🔴 PD 구도 = 45° · fov 60 · 거리 15 m (Lead 가 오늘 당긴 값)
L("카메라 [전] pos=" + live.transform.position.ToString("F2") + " 거리 " + live.transform.position.magnitude.ToString("F1") + " m");
float d = 15f, k = d * 0.70710678f;
live.transform.position = new Vector3(0f, k, -k);
live.transform.rotation = Quaternion.Euler(45f, 0f, 0f);
live.fieldOfView = 60f;
yield return null;
L("카메라 [후] pos=" + live.transform.position.ToString("F2") + " 각=" + live.transform.eulerAngles.ToString("F0")
+ " fov=" + live.fieldOfView + " · 거리 " + live.transform.position.magnitude.ToString("F1") + " m");
var E = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(MatDir + "Farm_Water_WL_E.mat");
Demo = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(MatDir + "Farm_Water_Demo.mat");
L("물 mat 지금 " + water.sharedMaterial.name + " q" + water.sharedMaterial.renderQueue
+ " · Demo q" + (Demo == null ? "?" : Demo.renderQueue.ToString()));
var zgo = new GameObject("~WL816kTZ"); zgo.hideFlags = HideFlags.DontSave;
zoom = zgo.AddComponent<Camera>();
zoom.fieldOfView = 34f; zoom.nearClipPlane = 0.1f; zoom.farClipPlane = 2000f;
zoom.clearFlags = CameraClearFlags.Skybox; zoom.cullingMask = ~0; zoom.depth = -100f;
zgo.AddComponent<UniversalAdditionalCameraData>().renderPostProcessing = true;
Bounds bb = band != null ? band.bounds : new Bounds(Vector3.zero, new Vector3(16, 1, 16));
zgo.transform.position = new Vector3(bb.center.x, 3.2f, bb.min.z - 9f);
zgo.transform.LookAt(new Vector3(bb.center.x, -1f, bb.min.z + 2f));
// 기준 = 지금 물 E + 띠 on / off
L("");
L("0) 기준");
yield return One(E, true, "t0_now_band", " 지금(물 E · 띠 on) ");
yield return One(E, false, "t0_now_noband", " 지금(물 E · 띠 off)");
// ── A) _FoamScale 스윕 ────────────────────────────────
L("");
L("A) _FoamScale (거품 노이즈 = UV 기반 · 섬 물 평면은 데모의 100배) · _Bands 78.52 고정");
float[] fsA = { 50f, 250f, 800f, 2500f };
var pa = new string[fsA.Length];
for (int i = 0; i < fsA.Length; i++)
{
var m = V(_ => { }); m.SetFloat("_FoamScale", fsA[i]);
pa[i] = D + "tA" + (i + 1) + "_zoom.png";
yield return One(m, false, "tA" + (i + 1), " _FoamScale " + fsA[i] + " ");
}
WL816k_Tune.Strip(pa, D + "sA_foamscale.png", 2);
// ── B) _Bands 스윕 ───────────────────────────────────
L("");
L("B) _Bands (깊이 밴딩 = 섬과 만나는 선의 반응) · _FoamScale 800 고정");
float[] bsB = { 78.52f, 400f, 1500f, 5000f };
var pb = new string[bsB.Length];
for (int i = 0; i < bsB.Length; i++)
{
var m = V(_ => { }); m.SetFloat("_FoamScale", 800f); m.SetFloat("_Bands", bsB[i]);
pb[i] = D + "tB" + (i + 1) + "_zoom.png";
yield return One(m, false, "tB" + (i + 1), " _Bands " + bsB[i] + " ");
}
WL816k_Tune.Strip(pb, D + "sB_bands.png", 2);
// ── C) _Color 스윕 (814t 톤) ─────────────────────────
L("");
L("C) _Color (814t 톤 · 쨍한 원색 금지) · _FoamScale 800 · _Bands 78.52");
var cols = new[] {
new Color(0.298f, 0.800f, 1.000f, 0.573f), // 데모 원본
new Color(0.360f, 0.690f, 0.820f, 0.573f), // 816h WaterColor
new Color(0.300f, 0.600f, 0.780f, 0.700f), // 더 짙게
new Color(0.420f, 0.720f, 0.845f, 0.500f), // 더 밝게
};
var pc = new string[cols.Length]; var pcPD = new string[cols.Length];
for (int i = 0; i < cols.Length; i++)
{
var m = V(_ => { }); m.SetFloat("_FoamScale", 800f); m.SetColor("_Color", cols[i]);
pc[i] = D + "tC" + (i + 1) + "_zoom.png"; pcPD[i] = D + "tC" + (i + 1) + ".png";
yield return One(m, false, "tC" + (i + 1), " _Color " + cols[i].ToString("F3") + " ");
}
WL816k_Tune.Strip(pc, D + "sC_color_zoom.png", 2);
WL816k_Tune.Strip(pcPD, D + "sC_color_pd.png", 2);
Object.DestroyImmediate(zgo);
water.sharedMaterial = E; if (band != null) band.enabled = true;
Done();
}
Material V(System.Action<Material> f) { var m = new Material(Demo); f(m); return m; }
IEnumerator One(Material m, bool bandOn, string tag, string note)
{
water.sharedMaterial = m;
if (band != null) band.enabled = bandOn;
yield return null; yield return new WaitForSeconds(0.3f);
L(note + "PD " + WL816k_Tune.Shot(live, D + tag + ".png", PW, PH, false));
L(new string(' ', note.Length) + "물가 " + WL816k_Tune.Shot(zoom, D + tag + "_zoom.png", PW, PH, true));
}
void Done()
{
System.IO.File.WriteAllText("AgentScripts/WL816k_TUNE.txt", sb.ToString());
Debug.Log("[816k tune]\n" + sb);
}
}