254 lines
14 KiB
C#
254 lines
14 KiB
C#
// 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);
|
||
}
|
||
}
|