Project_WL/AgentScripts/WL816k_Final.cs

254 lines
14 KiB
C#
Raw Permalink 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 — ④ 최종: 데모 물 셰이더 색 6안 → 814t 톤(#5C96C2)에 가장 가까운 안 자동 선정 → 전/후 2장
// 🔴 카메라 = PD 구도 거리 15 m 강제(Cinemachine 브레인 끔). Play 중 에셋 수정 0.
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Rendering.Universal;
using UnityEngine.SceneManagement;
using WL.Look.Farm;
public static class WL816k_Final
{
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("~WL816kF");
if (go != null) Object.DestroyImmediate(go);
go = new GameObject("~WL816kF");
go.AddComponent<WL816k_FinalRunner>();
}
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>캡처 + 지표. outRGB = 바다 평균색, outShore = 물가Δ(섬에 반응하는가)</summary>
public static string Shot(Camera cam, string path, int w, int h, bool shoreMetric, out Vector3 outRGB, out float outShore)
{
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;
// 바다만 있는 구역 — 확대 카메라는 아래쪽 절반, PD 구도는 왼쪽 아래
int y0 = shoreMetric ? 20 : 20, y1s = shoreMetric ? 200 : Mathf.Min(320, h);
int x0 = shoreMetric ? 100 : 20, x1s = shoreMetric ? w - 100 : Mathf.Min(340, w);
for (int y = y0; y < y1s; y += 2)
for (int x = x0; x < x1s; 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;
outRGB = new Vector3((float)r / n, (float)g / n, (float)b / n);
outShore = 0f;
string extra = "";
if (shoreMetric)
{
double a = 0; int an = 0, bn = 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]); bn++; }
outShore = (float)((an > 0 ? a / an : 0) - (bn > 0 ? b2 / bn : 0));
extra = " · 물가Δ " + outShore.ToString("F4");
}
int fn = 0, tot = 0;
int yh = (int)(h * 0.55f);
for (int y = 0; y < yh; 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) fn++; }
Object.DestroyImmediate(t);
return "바다 #" + ((int)(r / n)).ToString("X2") + ((int)(g / n)).ToString("X2") + ((int)(b / n)).ToString("X2")
+ " σ=" + sd.ToString("F4") + " · 밝음 " + (100f * fn / tot).ToString("F2") + "%" + extra;
}
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_FinalRunner : MonoBehaviour
{
static StringBuilder sb;
static void L(string s) { sb.AppendLine(s); }
const string D = WL816k_Final.D;
const string MatDir = WL816k_Final.MatDir;
const int PW = WL816k_Final.PW, PH = WL816k_Final.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; }
// 🔴 거리 15 m — 섬 카메라는 Level01 의 Cinemachine vcam(m_CameraDistance 22)이 매 프레임 덮어쓴다.
// FarmingIsland 씬 0줄 원칙이라 **런타임에 브레인만 끄고** 카메라를 직접 놓는다.
int off = 0;
foreach (var mb in live.GetComponents<MonoBehaviour>())
if (mb != null && mb.GetType().Name.Contains("Cinemachine")) { mb.enabled = false; off++; }
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; yield return new WaitForSeconds(0.3f);
L("카메라 " + live.name + " Cinemachine 컴포넌트 " + off + "개 끔 · 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");
if (Demo == null) { L("🔴 Farm_Water_Demo 없음"); Done(); yield break; }
var zgo = new GameObject("~WL816kFZ"); 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));
Vector3 rgb; float sh;
// ── 전 (지금 = 물 E + 띠 on) ────────────────────────────
water.sharedMaterial = E; if (band != null) band.enabled = true;
yield return null; yield return new WaitForSeconds(0.35f);
L("");
L("[전] 지금(물 E · 띠 on) PD " + WL816k_Final.Shot(live, D + "z_before_pd.png", PW, PH, false, out rgb, out sh));
L(" 물가 " + WL816k_Final.Shot(zoom, D + "z_before_zoom.png", PW, PH, true, out rgb, out sh));
// ── 색 6안 (목표 = 814t 화면색 #5C96C2) ────────────────
var TARGET = new Vector3(0x5C, 0x96, 0xC2);
var C = new[] {
new Color(0.298f,0.800f,1.000f,0.573f), new Color(0.55f,0.78f,0.90f,0.60f),
new Color(0.70f,0.85f,0.95f,0.60f), new Color(0.55f,0.78f,0.90f,0.60f),
new Color(0.45f,0.70f,0.88f,0.65f), new Color(0.70f,0.85f,0.95f,0.60f) };
var BR = new[] { 0.25f, 0.45f, 0.45f, 0.60f, 0.55f, 0.60f };
var MD = new[] { 0.20f, 0.40f, 0.40f, 0.50f, 0.45f, 0.50f };
var MT = new[] { -0.1f, -0.1f, -0.1f, 0.0f, -0.1f, 0.0f };
var TS = new[] { 0.13f, 0.13f, 0.13f, 0.13f, 0.05f, 0.05f };
L("");
L("[데모 물 색 6안] _FoamScale 800 고정 · 목표 = 화면 #5C96C2");
int best = -1; float bestD = 1e9f; var zp = new string[6];
for (int i = 0; i < 6; i++)
{
var m = new Material(Demo);
m.SetFloat("_FoamScale", 800f); m.SetColor("_Color", C[i]);
m.SetFloat("_Brightness", BR[i]); m.SetFloat("_MinimumDarkness", MD[i]);
m.SetFloat("_MinimumTint", MT[i]); m.SetFloat("_TintStrength", TS[i]);
water.sharedMaterial = m; if (band != null) band.enabled = false;
yield return null; yield return new WaitForSeconds(0.3f);
zp[i] = D + "v" + (i + 1) + "_zoom.png";
string s = WL816k_Final.Shot(zoom, zp[i], PW, PH, true, out rgb, out sh);
float dist = Vector3.Distance(rgb, TARGET);
L(" v" + (i + 1) + " C" + C[i].ToString("F2") + " Br" + BR[i] + " MD" + MD[i] + " MT" + MT[i] + " TS" + TS[i]);
L(" " + s + " · 목표거리 " + dist.ToString("F1"));
if (dist < bestD) { bestD = dist; best = i; }
}
WL816k_Final.Strip(zp, D + "v_color6.png", 3);
L("");
L("🔴 채택 = v" + (best + 1) + " · _Color" + C[best].ToString("F3") + " _Brightness " + BR[best]
+ " _MinimumDarkness " + MD[best] + " _MinimumTint " + MT[best] + " _TintStrength " + TS[best]
+ " _FoamScale 800 · 목표거리 " + bestD.ToString("F1"));
// ── 후 (채택안 · 띠 off) ────────────────────────────────
var win = new Material(Demo);
win.SetFloat("_FoamScale", 800f); win.SetColor("_Color", C[best]);
win.SetFloat("_Brightness", BR[best]); win.SetFloat("_MinimumDarkness", MD[best]);
win.SetFloat("_MinimumTint", MT[best]); win.SetFloat("_TintStrength", TS[best]);
water.sharedMaterial = win; if (band != null) band.enabled = false;
yield return null; yield return new WaitForSeconds(0.35f);
L("");
L("[후] 데모 물 v" + (best + 1) + " · 띠 off PD " + WL816k_Final.Shot(live, D + "z_after_pd.png", PW, PH, false, out rgb, out sh));
L(" 물가 " + WL816k_Final.Shot(zoom, D + "z_after_zoom.png", PW, PH, true, out rgb, out sh));
WL816k_Final.Strip(new[] { D + "z_before_pd.png", D + "z_after_pd.png" }, D + "z_pd_before_after.png", 1);
WL816k_Final.Strip(new[] { D + "z_before_zoom.png", D + "z_after_zoom.png" }, D + "z_zoom_shore.png", 1);
// ── 걷기(기능 무영향) ───────────────────────────────────
var cc = Object.FindFirstObjectByType<CharacterController>(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("걷기 " + 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);
}
// 채택값을 파일로 (에셋 반영은 Play 밖에서)
System.IO.File.WriteAllText("AgentScripts/WL816k_PICK.txt",
C[best].r + "," + C[best].g + "," + C[best].b + "," + C[best].a + "|" + BR[best] + "|" + MD[best] + "|" + MT[best] + "|" + TS[best] + "|800");
Object.DestroyImmediate(zgo);
water.sharedMaterial = E; if (band != null) band.enabled = true;
Done();
}
void Done()
{
System.IO.File.WriteAllText("AgentScripts/WL816k_FINAL.txt", sb.ToString());
Debug.Log("[816k final]\n" + sb);
}
}