2026-09-14 23:31:20 +00:00
|
|
|
// WL-816q 2회차 — 띠 단계 수·대비를 데모 수준(띠단계 8 · LF 16.3)까지 올린다.
|
|
|
|
|
// 바닥·풀 **양쪽** 구름 값을 똑같이 바꾼다(같아야 풀이 띠 안에서 녹아든다). 에셋 무변경(복제본 교체).
|
|
|
|
|
public static class WL816q_Sweep
|
|
|
|
|
{
|
|
|
|
|
public static void Start()
|
|
|
|
|
{
|
|
|
|
|
var go = UnityEngine.GameObject.Find("~WL816qSweep");
|
|
|
|
|
if (go != null) UnityEngine.Object.DestroyImmediate(go);
|
|
|
|
|
go = new UnityEngine.GameObject("~WL816qSweep");
|
|
|
|
|
go.AddComponent<WL816q_SweepRunner>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class WL816q_SweepRunner : UnityEngine.MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
static System.Text.StringBuilder sb;
|
|
|
|
|
const int TS = 768;
|
|
|
|
|
static string Dir = "Screenshots_WL/WL816q";
|
|
|
|
|
|
|
|
|
|
void Start() { StartCoroutine(Co()); }
|
|
|
|
|
|
|
|
|
|
System.Collections.IEnumerator Co()
|
|
|
|
|
{
|
|
|
|
|
sb = new System.Text.StringBuilder();
|
|
|
|
|
System.IO.Directory.CreateDirectory(Dir);
|
|
|
|
|
if (UnityEngine.SceneManagement.SceneManager.sceneCount < 2)
|
|
|
|
|
{
|
|
|
|
|
var op = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Level01", UnityEngine.SceneManagement.LoadSceneMode.Additive);
|
|
|
|
|
while (op != null && !op.isDone) yield return null;
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < 6; i++) yield return new UnityEngine.WaitForSeconds(1f);
|
|
|
|
|
|
|
|
|
|
var g = UnityEngine.Object.FindFirstObjectByType<WL.Look.Farm.WLIslandGrass>(UnityEngine.FindObjectsInactive.Include);
|
|
|
|
|
if (g == null) { L("grass 없음"); Flush(); yield break; }
|
|
|
|
|
var rt = UnityEngine.Object.Instantiate(WL.Look.Farm.WLIslandLookSettings.Instance);
|
|
|
|
|
var arr = new WL.Look.Farm.WLScatterDef[rt.scatter.Length];
|
|
|
|
|
for (int i = 0; i < arr.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var s = rt.scatter[i];
|
|
|
|
|
arr[i] = new WL.Look.Farm.WLScatterDef { enabled_ = s.enabled_, label = s.label, mesh = s.mesh, material = s.material,
|
|
|
|
|
probability = s.probability, scale = s.scale, normalOffset = s.normalOffset, density = s.density };
|
|
|
|
|
}
|
|
|
|
|
rt.scatter = arr; g.cfg = rt; rt.viewCullEnabled = 0;
|
|
|
|
|
|
|
|
|
|
// 바닥 머티리얼을 쓰는 렌더러를 전부 복제본으로 바꿔 둔다(에셋 무변경)
|
|
|
|
|
var topAsset = rt.islandTopMaterial;
|
|
|
|
|
var topClone = new UnityEngine.Material(topAsset); topClone.name = topAsset.name + "_sweep";
|
|
|
|
|
int swapped = 0;
|
|
|
|
|
var rends = UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(UnityEngine.FindObjectsSortMode.None);
|
|
|
|
|
for (int i = 0; i < rends.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var ms = rends[i].sharedMaterials; bool hit = false;
|
|
|
|
|
for (int m = 0; m < ms.Length; m++) if (ms[m] == topAsset) { ms[m] = topClone; hit = true; }
|
|
|
|
|
if (hit) { rends[i].sharedMaterials = ms; swapped++; }
|
|
|
|
|
}
|
|
|
|
|
var grassAsset = arr[0].material;
|
|
|
|
|
var grassClone = new UnityEngine.Material(grassAsset); grassClone.name = grassAsset.name + "_sweep";
|
|
|
|
|
for (int i = 0; i < arr.Length; i++) if (arr[i].material == grassAsset) arr[i].material = grassClone;
|
|
|
|
|
L("바닥 복제 교체 렌더러=" + swapped + " · 풀 복제 교체 완료");
|
|
|
|
|
|
|
|
|
|
var b = g.CalculateInstancesBounds();
|
|
|
|
|
var cam = MakeTop(b.center.x, b.center.z, 6f);
|
|
|
|
|
UnityEngine.Time.timeScale = 0f; yield return null;
|
|
|
|
|
|
2026-09-14 23:36:54 +00:00
|
|
|
L("목표(데모 참고 x_demo_ground) : 띠단계 8 · LF 16.27 · HF 0.33 · 에지% 1.22 · 밝기 174.2");
|
|
|
|
|
float[] strs = { 1f, 1.4f, 1.8f, 2.2f };
|
|
|
|
|
float[] covs = { 0.25f, 0.9f, 1.6f, 2.4f }; // = _Brightness (밝기 보정)
|
2026-09-14 23:31:20 +00:00
|
|
|
for (int ci = 0; ci < covs.Length; ci++)
|
|
|
|
|
{
|
|
|
|
|
for (int si = 0; si < strs.Length; si++)
|
|
|
|
|
{
|
|
|
|
|
Set(topClone, strs[si], covs[ci]); Set(grassClone, strs[si], covs[ci]);
|
|
|
|
|
// 바닥만
|
|
|
|
|
rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null;
|
|
|
|
|
var A = Grab(cam, TS, TS); var apx = A.GetPixels32();
|
|
|
|
|
string mg = Metric(apx, TS, TS);
|
|
|
|
|
// 풀까지
|
|
|
|
|
rt.grassEnabled = 1; g.Rebuild(); yield return null; yield return null;
|
|
|
|
|
var B = Grab(cam, TS, TS); var bpx = B.GetPixels32();
|
2026-09-14 23:36:54 +00:00
|
|
|
L(string.Format("Strength={0:F1} Brightness={1:F2} | 바닥만 {2} | 풀포함 {3} | 보이는 풀 {4:F2}%",
|
2026-09-14 23:31:20 +00:00
|
|
|
strs[si], covs[ci], mg, Metric(bpx, TS, TS), Cover(apx, bpx)));
|
2026-09-14 23:36:54 +00:00
|
|
|
System.IO.File.WriteAllBytes(Dir + "/sweep_s" + strs[si].ToString("F1") + "_b" + covs[ci].ToString("F2") + ".png",
|
|
|
|
|
UnityEngine.ImageConversion.EncodeToPNG(B));
|
2026-09-14 23:31:20 +00:00
|
|
|
UnityEngine.Object.DestroyImmediate(A); UnityEngine.Object.DestroyImmediate(B);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-14 23:36:54 +00:00
|
|
|
// 🔴 원래 값으로 되돌린다 — 안 하면 다음 실행이 오염된 씬에서 재기 시작한다(2회차 사고)
|
|
|
|
|
Set(topClone, topAsset.GetFloat("_Cloud_Strength"), topAsset.GetFloat("_Brightness"));
|
|
|
|
|
Set(grassClone, grassAsset.GetFloat("_Cloud_Strength"), grassAsset.GetFloat("_Brightness"));
|
|
|
|
|
g.Rebuild();
|
2026-09-14 23:31:20 +00:00
|
|
|
UnityEngine.Time.timeScale = 1f;
|
|
|
|
|
Flush();
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-14 23:36:54 +00:00
|
|
|
static void Set(UnityEngine.Material m, float str, float bright)
|
2026-09-14 23:31:20 +00:00
|
|
|
{
|
2026-09-14 23:36:54 +00:00
|
|
|
m.SetFloat("_Cloud_Strength", str); m.SetFloat("_Brightness", bright);
|
2026-09-14 23:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static float Cover(UnityEngine.Color32[] a, UnityEngine.Color32[] b)
|
|
|
|
|
{
|
|
|
|
|
int n = 0, tot = 0;
|
|
|
|
|
for (int i = 0; i < a.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var p = a[i]; if (!Gr(p)) continue; tot++;
|
|
|
|
|
var q = b[i]; if (q.g <= q.b + 4) continue;
|
|
|
|
|
if (System.Math.Abs(p.r - q.r) + System.Math.Abs(p.g - q.g) + System.Math.Abs(p.b - q.b) > 6) n++;
|
|
|
|
|
}
|
|
|
|
|
return tot == 0 ? 0f : 100f * n / tot;
|
|
|
|
|
}
|
|
|
|
|
static string Metric(UnityEngine.Color32[] px, int w, int h)
|
|
|
|
|
{
|
|
|
|
|
int bs = 8, bw = w / bs, bh = h / bs;
|
|
|
|
|
var bm = new float[bw * bh]; var bok = new bool[bw * bh];
|
|
|
|
|
double hf = 0; int hfn = 0;
|
|
|
|
|
for (int by = 0; by < bh; by++) for (int bx = 0; bx < bw; bx++)
|
|
|
|
|
{
|
|
|
|
|
double s = 0; int n = 0;
|
|
|
|
|
for (int y = 0; y < bs; y++) for (int x = 0; x < bs; x++)
|
|
|
|
|
{ var p = px[(by * bs + y) * w + (bx * bs + x)]; if (!Gr(p)) continue; s += Lum(p); n++; }
|
|
|
|
|
if (n < bs * bs / 2) continue;
|
|
|
|
|
float mean = (float)(s / n); bm[by * bw + bx] = mean; bok[by * bw + bx] = true;
|
|
|
|
|
for (int y = 0; y < bs; y++) for (int x = 0; x < bs; x++)
|
|
|
|
|
{ var p = px[(by * bs + y) * w + (bx * bs + x)]; if (!Gr(p)) continue; hf += System.Math.Abs(Lum(p) - mean); hfn++; }
|
|
|
|
|
}
|
|
|
|
|
double s1 = 0, s2 = 0; int bn = 0; var lv = new int[40];
|
|
|
|
|
for (int i = 0; i < bm.Length; i++) { if (!bok[i]) continue; s1 += bm[i]; s2 += (double)bm[i] * bm[i]; bn++; int q = (int)(bm[i] / 8f); if (q >= 0 && q < 40) lv[q]++; }
|
|
|
|
|
if (bn == 0 || hfn == 0) return "지면 0";
|
|
|
|
|
double mu = s1 / bn, lf = System.Math.Sqrt(System.Math.Max(0, s2 / bn - mu * mu));
|
|
|
|
|
int levels = 0; for (int i = 0; i < 40; i++) if (lv[i] >= bn * 0.005) levels++;
|
|
|
|
|
int ed = 0, edn = 0;
|
|
|
|
|
for (int y = 0; y < h - 1; y++) for (int x = 0; x < w - 1; x++)
|
|
|
|
|
{
|
|
|
|
|
var p = px[y * w + x]; if (!Gr(p)) continue; var r = px[y * w + x + 1]; var d = px[(y + 1) * w + x]; edn++;
|
|
|
|
|
if ((Gr(r) && System.Math.Abs(Lum(p) - Lum(r)) > 8) || (Gr(d) && System.Math.Abs(Lum(p) - Lum(d)) > 8)) ed++;
|
|
|
|
|
}
|
|
|
|
|
return string.Format("단계={0} LF={1:F2} HF={2:F2} 에지%={3:F2} 밝기={4:F1}", levels, lf, hf / hfn, 100.0 * ed / System.Math.Max(1, edn), mu);
|
|
|
|
|
}
|
|
|
|
|
static bool Gr(UnityEngine.Color32 p) { return p.g > p.b + 8 && p.g > 45; }
|
|
|
|
|
static float Lum(UnityEngine.Color32 p) { return 0.299f * p.r + 0.587f * p.g + 0.114f * p.b; }
|
|
|
|
|
|
|
|
|
|
static UnityEngine.Camera MakeTop(float cx, float cz, float size)
|
|
|
|
|
{
|
|
|
|
|
var go = UnityEngine.GameObject.Find("~816qTop");
|
|
|
|
|
if (go == null) go = new UnityEngine.GameObject("~816qTop");
|
|
|
|
|
var c = go.GetComponent<UnityEngine.Camera>();
|
|
|
|
|
if (c == null) c = go.AddComponent<UnityEngine.Camera>();
|
|
|
|
|
c.orthographic = true; c.orthographicSize = size; c.nearClipPlane = 0.1f; c.farClipPlane = 200f;
|
|
|
|
|
c.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f);
|
|
|
|
|
c.transform.position = new UnityEngine.Vector3(cx, 60f, cz);
|
|
|
|
|
c.enabled = false; return c;
|
|
|
|
|
}
|
|
|
|
|
static UnityEngine.Texture2D Grab(UnityEngine.Camera cam, int w, int h)
|
|
|
|
|
{
|
|
|
|
|
var rt = new UnityEngine.RenderTexture(w, h, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB);
|
|
|
|
|
rt.Create(); cam.targetTexture = rt; cam.Render();
|
|
|
|
|
var prev = UnityEngine.RenderTexture.active; UnityEngine.RenderTexture.active = rt;
|
|
|
|
|
var tex = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false);
|
|
|
|
|
tex.ReadPixels(new UnityEngine.Rect(0, 0, w, h), 0, 0); tex.Apply();
|
|
|
|
|
UnityEngine.RenderTexture.active = prev; cam.targetTexture = null;
|
|
|
|
|
rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
|
|
|
|
|
return tex;
|
|
|
|
|
}
|
|
|
|
|
static void L(string s) { sb.AppendLine(s); Flush(); }
|
|
|
|
|
static void Flush()
|
|
|
|
|
{
|
|
|
|
|
System.IO.File.WriteAllText("AgentScripts/WL816q_SWEEP.txt", sb.ToString());
|
|
|
|
|
UnityEngine.Debug.Log("[816q sweep]\n" + sb.ToString());
|
|
|
|
|
}
|
|
|
|
|
}
|