Project_WL/AgentScripts/WL816k_Tune.cs

260 lines
13 KiB
C#
Raw Normal View History

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