[WL-816q] 범인 = 풀이 바닥보다 늘 어둡다 · 풀을 바닥과 같은 톤으로 맞춰 띠 복원 + 가시 범위 배치 (#816)
- 실측: 바닥만 렌더하면 띠세기LF 7.31 / 포기잡음HF 0.39 (매끈한 띠). 풀을 켜면 HF 4.22(10.8배) 에지% 0.86 → 14.06(16.3배). 풀이 바닥 대비 0.943/0.900/0.882 배로 「항상 더 어두워」 띠를 덮었다. - 수렴 실측: 풀 색 × (1.0737, 1.1189, 1.1365) 에서 보이는 풀 31.0% → 0.6% (구름 위상 3곳 전부 0.56~0.68%). 띠 안쪽에서 바닥과 같은 픽셀 = 데모와 같은 원리. - _Cloud_Change 0.001→0.005 · _Cloud_Movement (0.2,0.2)→(1,1) = 데모 원값 복귀(풀·바닥 동일). - 가시 범위 배치(816q §3): 월드 고정 격자는 그대로 두고 카메라 시야 사각형 + 여유 8 m 안만 남긴다. 여유(8) > 재선정 보폭(2.5) 이라 늘고 주는 것은 화면 밖에서만 — 좌표는 불변(순간이동 0). 되돌리기 = viewCullEnabled 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
433540dcb9
commit
7e0733307e
|
|
@ -0,0 +1,140 @@
|
|||
// WL-816q 회차 — 풀이 「띠 안쪽에서 바닥과 같은 픽셀」이 되도록 풀 색을 수렴시킨다.
|
||||
// 바닥만 찍은 기준 이미지와 같은 자리를 비교해 채널별 배율을 뉴턴식으로 갱신.
|
||||
// 에셋 무변경(런타임 복제본만).
|
||||
public static class WL816q_Iter
|
||||
{
|
||||
public static void Start()
|
||||
{
|
||||
var go = UnityEngine.GameObject.Find("~WL816qIter");
|
||||
if (go != null) UnityEngine.Object.DestroyImmediate(go);
|
||||
go = new UnityEngine.GameObject("~WL816qIter");
|
||||
go.AddComponent<WL816q_IterRunner>();
|
||||
}
|
||||
}
|
||||
|
||||
public class WL816q_IterRunner : 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 < 5; i++) yield return new UnityEngine.WaitForSeconds(1f);
|
||||
|
||||
var g = UnityEngine.Object.FindFirstObjectByType<WL.Look.Farm.WLIslandGrass>(UnityEngine.FindObjectsInactive.Include);
|
||||
if (g == null) { L("WLIslandGrass 없음"); 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;
|
||||
|
||||
var baseMat = arr[0].material;
|
||||
var dif0 = baseMat.GetColor("_DiffuseColor");
|
||||
var shd0 = baseMat.GetColor("_ShadowDiffuseColor");
|
||||
var clone = new UnityEngine.Material(baseMat); clone.name = baseMat.name + "_iter";
|
||||
for (int i = 0; i < arr.Length; i++) if (arr[i].material == baseMat) arr[i].material = clone;
|
||||
|
||||
var b = g.CalculateInstancesBounds();
|
||||
var cam = MakeTop(b.center.x, b.center.z, 6f);
|
||||
|
||||
// 시간을 멈춘다 — 구름이 흐르면 회차마다 바닥이 달라져 비교가 무효가 된다
|
||||
float ts = UnityEngine.Time.timeScale;
|
||||
UnityEngine.Time.timeScale = 0f;
|
||||
yield return null;
|
||||
|
||||
// 기준 = 바닥만
|
||||
rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null;
|
||||
var A = Grab(cam, TS, TS);
|
||||
System.IO.File.WriteAllBytes(Dir + "/iter_A_ground.png", UnityEngine.ImageConversion.EncodeToPNG(A));
|
||||
var apx = A.GetPixels32();
|
||||
|
||||
rt.grassEnabled = 1;
|
||||
float kr = 1f, kg = 1f, kb = 1f;
|
||||
for (int it = 0; it < 5; it++)
|
||||
{
|
||||
clone.SetColor("_DiffuseColor", new UnityEngine.Color(dif0.r * kr, dif0.g * kg, dif0.b * kb, dif0.a));
|
||||
clone.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(shd0.r * kr, shd0.g * kg, shd0.b * kb, shd0.a));
|
||||
g.Rebuild(); yield return null; yield return null;
|
||||
var Bt = Grab(cam, TS, TS);
|
||||
var bpx = Bt.GetPixels32();
|
||||
double rr, rg, rb; int cov;
|
||||
Ratio(apx, bpx, out rr, out rg, out rb, out cov);
|
||||
L(string.Format("회차{0} k=({1:F4},{2:F4},{3:F4}) → 풀/바닥 비=({4:F4},{5:F4},{6:F4}) 덮임={7:F1}% Dif=({8:F3},{9:F3},{10:F3})",
|
||||
it, kr, kg, kb, rr, rg, rb, cov * 100.0 / (TS * TS), dif0.r * kr, dif0.g * kg, dif0.b * kb));
|
||||
System.IO.File.WriteAllBytes(Dir + "/iter_" + it + ".png", UnityEngine.ImageConversion.EncodeToPNG(Bt));
|
||||
UnityEngine.Object.DestroyImmediate(Bt);
|
||||
if (System.Math.Abs(rr - 1) < 0.004 && System.Math.Abs(rg - 1) < 0.004 && System.Math.Abs(rb - 1) < 0.004) { L("수렴"); break; }
|
||||
kr = (float)UnityEngine.Mathf.Clamp((float)(kr / System.Math.Max(0.5, rr)), 0.5f, 2.5f);
|
||||
kg = (float)UnityEngine.Mathf.Clamp((float)(kg / System.Math.Max(0.5, rg)), 0.5f, 2.5f);
|
||||
kb = (float)UnityEngine.Mathf.Clamp((float)(kb / System.Math.Max(0.5, rb)), 0.5f, 2.5f);
|
||||
}
|
||||
L(string.Format("채택 _DiffuseColor = {0:F4} {1:F4} {2:F4}", dif0.r * kr, dif0.g * kg, dif0.b * kb));
|
||||
L(string.Format("채택 _ShadowDiffuseColor = {0:F4} {1:F4} {2:F4}", shd0.r * kr, shd0.g * kg, shd0.b * kb));
|
||||
UnityEngine.Object.DestroyImmediate(A);
|
||||
UnityEngine.Time.timeScale = ts;
|
||||
Flush();
|
||||
}
|
||||
|
||||
// 바닥만 이미지와 다른 픽셀 = 풀. 그 자리의 바닥색 대비 평균비.
|
||||
static void Ratio(UnityEngine.Color32[] a, UnityEngine.Color32[] b, out double rr, out double rg, out double rb, out int cov)
|
||||
{
|
||||
double sa = 0, sb = 0, sa2 = 0, sb2 = 0, sa3 = 0, sb3 = 0; int n = 0;
|
||||
for (int i = 0; i < a.Length; i++)
|
||||
{
|
||||
var p = a[i]; var q = b[i];
|
||||
if (!(p.g > p.b + 8 && p.g > 45)) continue; // 초록 지면만
|
||||
int d = System.Math.Abs(p.r - q.r) + System.Math.Abs(p.g - q.g) + System.Math.Abs(p.b - q.b);
|
||||
if (d <= 6) continue; // 안 바뀐 = 풀 없음
|
||||
if (q.g <= q.b + 4) continue; // 꽃(노랑)·자갈 제외
|
||||
sa += p.r; sb += q.r; sa2 += p.g; sb2 += q.g; sa3 += p.b; sb3 += q.b; n++;
|
||||
}
|
||||
cov = n;
|
||||
rr = n == 0 ? 1 : sb / sa; rg = n == 0 ? 1 : sb2 / sa2; rb = n == 0 ? 1 : sb3 / sa3;
|
||||
}
|
||||
|
||||
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_ITER.txt", sb.ToString());
|
||||
UnityEngine.Debug.Log("[816q iter]\n" + sb.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
// WL-816q — 「그라데이션(구름 띠)이 왜 안 보이나」 요소별 ON/OFF 실측.
|
||||
// 에셋 무변경(SO·머티리얼은 전부 런타임 복제본). Debug.LogError 금지.
|
||||
public static class WL816q_Play
|
||||
{
|
||||
public static void Start()
|
||||
{
|
||||
var go = UnityEngine.GameObject.Find("~WL816qPlay");
|
||||
if (go != null) UnityEngine.Object.DestroyImmediate(go);
|
||||
go = new UnityEngine.GameObject("~WL816qPlay");
|
||||
go.AddComponent<WL816q_PlayRunner>();
|
||||
}
|
||||
}
|
||||
|
||||
public class WL816q_PlayRunner : UnityEngine.MonoBehaviour
|
||||
{
|
||||
static System.Text.StringBuilder sb;
|
||||
const int W = 1080, H = 1920;
|
||||
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 < 7; i++) yield return new UnityEngine.WaitForSeconds(1f);
|
||||
|
||||
var g = UnityEngine.Object.FindFirstObjectByType<WL.Look.Farm.WLIslandGrass>(UnityEngine.FindObjectsInactive.Include);
|
||||
if (g == null) { L("WLIslandGrass 없음 — 중단"); Flush(); yield break; }
|
||||
|
||||
var src = WL.Look.Farm.WLIslandLookSettings.Instance;
|
||||
var rt = UnityEngine.Object.Instantiate(src);
|
||||
// scatter 정의를 새 인스턴스로 깊은 복사 — 에셋 쪽 객체를 절대 만지지 않는다
|
||||
if (rt.scatter != null)
|
||||
{
|
||||
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;
|
||||
|
||||
// ── 요소 진단 ────────────────────────────────────────────────
|
||||
L("=== 요소 진단 === t=" + UnityEngine.Time.timeSinceLevelLoad);
|
||||
L("ⓑ WLReferenceLook.IsApplied=" + WL.Look.Arena.WLReferenceLook.IsApplied
|
||||
+ " · Outlined=" + WL.Look.Arena.WLReferenceLook.OutlinedRenderers
|
||||
+ " · Contrast=" + WL.Look.Arena.WLReferenceLook.ContrastMaterials);
|
||||
int hull = 0, toonR = 0;
|
||||
var rends = UnityEngine.Object.FindObjectsByType<UnityEngine.Renderer>(UnityEngine.FindObjectsSortMode.None);
|
||||
for (int i = 0; i < rends.Length; i++)
|
||||
{
|
||||
var ms = rends[i].sharedMaterials;
|
||||
for (int m = 0; m < ms.Length; m++)
|
||||
{
|
||||
if (ms[m] == null || ms[m].shader == null) continue;
|
||||
if (ms[m].shader.name == "WL/HullOutline") hull++;
|
||||
if (ms[m].shader.name.Contains("Toon")) toonR++;
|
||||
}
|
||||
}
|
||||
L("ⓑ 씬 안 HullOutline 머티리얼 슬롯=" + hull + " · Toon 슬롯=" + toonR);
|
||||
var vols = UnityEngine.Object.FindObjectsByType<UnityEngine.Rendering.Volume>(UnityEngine.FindObjectsSortMode.None);
|
||||
L("ⓓ Volume 개수=" + vols.Length + (vols.Length > 0 ? " 첫 프로필=" + (vols[0].sharedProfile ? vols[0].sharedProfile.name : "-") : ""));
|
||||
var sun = UnityEngine.RenderSettings.sun;
|
||||
var lights = UnityEngine.Object.FindObjectsByType<UnityEngine.Light>(UnityEngine.FindObjectsSortMode.None);
|
||||
for (int i = 0; i < lights.Length; i++)
|
||||
if (lights[i].type == UnityEngine.LightType.Directional)
|
||||
L("ⓐ Directional " + lights[i].name + " shadows=" + lights[i].shadows + " int=" + lights[i].intensity + " rot=" + lights[i].transform.eulerAngles);
|
||||
|
||||
var top = rt.islandTopMaterial;
|
||||
var grassMat = (rt.scatter != null && rt.scatter.Length > 0) ? rt.scatter[0].material : null;
|
||||
L("ⓒⓔ 바닥=" + (top ? top.name : "-") + " sh=" + (top && top.shader ? top.shader.name : "-") + " " + MatDump(top));
|
||||
L("ⓒⓔ 풀 =" + (grassMat ? grassMat.name : "-") + " sh=" + (grassMat && grassMat.shader ? grassMat.shader.name : "-") + " " + MatDump(grassMat));
|
||||
L("ⓔ 그림자비(Shadow/Diffuse) 바닥=" + RatioStr(top) + " · 풀=" + RatioStr(grassMat));
|
||||
|
||||
var b = g.CalculateInstancesBounds();
|
||||
L("섬 바운즈 center=" + b.center + " size=" + b.size);
|
||||
|
||||
var camTop = MakeTop(b.center.x, b.center.z, 6f);
|
||||
var camPd = MakePd();
|
||||
L("탑다운 = ortho6 @(" + b.center.x.ToString("F2") + ",60," + b.center.z.ToString("F2") + ") · PD = 12m/45°/fov60");
|
||||
|
||||
// ── A: 풀 끔 (바닥만) ───────────────────────────────────────
|
||||
rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null;
|
||||
L("[A 바닥만] " + Shot(camTop, TS, TS, "A_top_groundonly") + " | " + Stat());
|
||||
Shot(camPd, W, H, "A_pd_groundonly");
|
||||
|
||||
// ── B: 현재 풀 (816n 상태) ──────────────────────────────────
|
||||
rt.grassEnabled = 1; g.Rebuild(); yield return null; yield return null;
|
||||
L("[B 현재풀] " + Shot(camTop, TS, TS, "B_top_before") + " | " + Stat());
|
||||
Shot(camPd, W, H, "B_pd_before");
|
||||
|
||||
// ── C: 풀 그림자색만 데모 비율로 (다른 값 전부 동일) ────────
|
||||
if (grassMat != null)
|
||||
{
|
||||
var clone = new UnityEngine.Material(grassMat);
|
||||
clone.name = grassMat.name + "_816qTest";
|
||||
var dif = clone.GetColor("_DiffuseColor");
|
||||
clone.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(dif.r * 0.3232f, dif.g * 0.3414f, dif.b * 0.3948f, dif.a));
|
||||
for (int i = 0; i < rt.scatter.Length; i++)
|
||||
if (rt.scatter[i].material == grassMat) rt.scatter[i].material = clone;
|
||||
g.Rebuild(); yield return null; yield return null;
|
||||
L("[C 풀그림자=데모비율] " + Shot(camTop, TS, TS, "C_top_shadowfix") + " | " + Stat());
|
||||
Shot(camPd, W, H, "C_pd_shadowfix");
|
||||
}
|
||||
|
||||
// ── D: 참고 — 데모 기준 캡처에 같은 지표 ────────────────────
|
||||
Ref("Screenshots_WL/WL816f/x_demo_ground.png", "D 데모참고(스케일 미상)");
|
||||
Ref("Screenshots_WL/WL816f/x_ours_ground.png", "D 우리참고(스케일 미상)");
|
||||
|
||||
Flush();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────── 지표
|
||||
// LF = 8×8 블록 평균 밝기의 표준편차(= 띠 세기) · HF = 블록 내 편차 평균(= 포기 잡음)
|
||||
// levels = 블록 밝기를 8단계로 양자화했을 때 면적 0.5% 이상인 단계 수(= 띠 단계)
|
||||
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 (!Ground(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 (!Ground(p)) continue;
|
||||
hf += System.Math.Abs(Lum(p) - mean); hfn++;
|
||||
}
|
||||
}
|
||||
double bs1 = 0, bs2 = 0; int bn = 0;
|
||||
var lv = new int[40];
|
||||
for (int i = 0; i < bm.Length; i++) { if (!bok[i]) continue; bs1 += bm[i]; bs2 += (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 = bs1 / bn;
|
||||
double lf = System.Math.Sqrt(System.Math.Max(0, bs2 / bn - mu * mu));
|
||||
int levels = 0; for (int i = 0; i < 40; i++) if (lv[i] >= bn * 0.005) levels++;
|
||||
// 에지% — 인접 픽셀 밝기차 8 이상
|
||||
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 (!Ground(p)) continue;
|
||||
var r = px[y * w + x + 1]; var d = px[(y + 1) * w + x];
|
||||
edn++;
|
||||
if ((Ground(r) && System.Math.Abs(Lum(p) - Lum(r)) > 8) || (Ground(d) && System.Math.Abs(Lum(p) - Lum(d)) > 8)) ed++;
|
||||
}
|
||||
return string.Format("지면 {0:F1}% · 띠세기LF={1:F2} · 포기잡음HF={2:F2} · LF/HF={3:F2} · 띠단계={4} · 에지%={5:F2} · 평균밝기={6:F1}",
|
||||
100.0 * edn / (w * h), lf, hf / hfn, lf / System.Math.Max(0.01, hf / hfn), levels, 100.0 * ed / System.Math.Max(1, edn), mu);
|
||||
}
|
||||
|
||||
static bool Ground(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 void Ref(string path, string tag)
|
||||
{
|
||||
if (!System.IO.File.Exists(path)) { L(tag + " : 파일 없음 " + path); return; }
|
||||
var tex = new UnityEngine.Texture2D(2, 2, UnityEngine.TextureFormat.RGB24, false);
|
||||
if (!UnityEngine.ImageConversion.LoadImage(tex, System.IO.File.ReadAllBytes(path))) { L(tag + " : 읽기 실패"); return; }
|
||||
L(tag + " " + tex.width + "x" + tex.height + " : " + Metric(tex.GetPixels32(), tex.width, tex.height));
|
||||
UnityEngine.Object.DestroyImmediate(tex);
|
||||
}
|
||||
|
||||
static string MatDump(UnityEngine.Material m)
|
||||
{
|
||||
if (m == null) return "";
|
||||
return "Shades=" + F(m, "_Shades") + " Bright=" + F(m, "_Brightness") + " MinDark=" + F(m, "_MinimumDarkness")
|
||||
+ " CloudStr=" + F(m, "_Cloud_Strength") + " CloudDens=" + F(m, "_Cloud_Density") + " Cover=" + F(m, "_Cloud_Cover")
|
||||
+ " Change=" + F(m, "_Cloud_Change") + " Move=" + (m.HasProperty("_Cloud_Movement") ? m.GetVector("_Cloud_Movement").ToString() : "-")
|
||||
+ " Dif=" + C(m, "_DiffuseColor") + " Shd=" + C(m, "_ShadowDiffuseColor")
|
||||
+ " kw=[" + string.Join(",", m.shaderKeywords) + "]";
|
||||
}
|
||||
static string F(UnityEngine.Material m, string n) { return m.HasProperty(n) ? m.GetFloat(n).ToString("F4") : "-"; }
|
||||
static string C(UnityEngine.Material m, string n) { if (!m.HasProperty(n)) return "-"; var c = m.GetColor(n); return "(" + c.r.ToString("F3") + "," + c.g.ToString("F3") + "," + c.b.ToString("F3") + ")"; }
|
||||
static string RatioStr(UnityEngine.Material m)
|
||||
{
|
||||
if (m == null || !m.HasProperty("_DiffuseColor") || !m.HasProperty("_ShadowDiffuseColor")) return "-";
|
||||
var d = m.GetColor("_DiffuseColor"); var s = m.GetColor("_ShadowDiffuseColor");
|
||||
return "(" + Safe(s.r, d.r) + "," + Safe(s.g, d.g) + "," + Safe(s.b, d.b) + ")";
|
||||
}
|
||||
static string Safe(float a, float b) { return b < 0.001f ? "-" : (a / b).ToString("F3"); }
|
||||
|
||||
static string Stat()
|
||||
{
|
||||
return "인스턴스=" + WL.Look.Farm.WLIslandGrass.Instances + " 삼각형=" + WL.Look.Farm.WLIslandGrass.Triangles
|
||||
+ " 드로우콜+" + WL.Look.Farm.WLIslandGrass.DrawnConfigs + " 타일=" + WL.Look.Farm.WLIslandGrass.Tiles;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────── 카메라·캡처
|
||||
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.Camera MakePd()
|
||||
{
|
||||
UnityEngine.Vector3 tgt = UnityEngine.Vector3.zero;
|
||||
var pc = UnityEngine.GameObject.FindGameObjectWithTag("Player");
|
||||
if (pc != null) tgt = pc.transform.position;
|
||||
var go = UnityEngine.GameObject.Find("~816qPd");
|
||||
if (go == null) go = new UnityEngine.GameObject("~816qPd");
|
||||
var c = go.GetComponent<UnityEngine.Camera>();
|
||||
if (c == null) c = go.AddComponent<UnityEngine.Camera>();
|
||||
c.orthographic = false; c.fieldOfView = 60f; c.nearClipPlane = 0.1f; c.farClipPlane = 300f;
|
||||
c.transform.rotation = UnityEngine.Quaternion.Euler(45f, 45f, 0f);
|
||||
c.transform.position = tgt - c.transform.forward * 12f;
|
||||
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 string Shot(UnityEngine.Camera cam, int w, int h, string name)
|
||||
{
|
||||
var tex = Grab(cam, w, h);
|
||||
System.IO.File.WriteAllBytes(Dir + "/" + name + ".png", UnityEngine.ImageConversion.EncodeToPNG(tex));
|
||||
string s = Metric(tex.GetPixels32(), w, h);
|
||||
UnityEngine.Object.DestroyImmediate(tex);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void L(string s) { sb.AppendLine(s); Flush(); }
|
||||
static void Flush()
|
||||
{
|
||||
System.IO.File.WriteAllText("AgentScripts/WL816q_PLAY.txt", sb.ToString());
|
||||
UnityEngine.Debug.Log("[816q play]\n" + sb.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
// WL-816q 스캔 — 풀이 바닥과 같은 톤 단계에 들어가는 「고원」의 폭과 중심을 찾는다.
|
||||
// 구름 위상 3곳에서 재확인해 특정 순간에만 맞는 값을 배제한다. 에셋 무변경.
|
||||
public static class WL816q_Scan
|
||||
{
|
||||
public static void Start()
|
||||
{
|
||||
var go = UnityEngine.GameObject.Find("~WL816qScan");
|
||||
if (go != null) UnityEngine.Object.DestroyImmediate(go);
|
||||
go = new UnityEngine.GameObject("~WL816qScan");
|
||||
go.AddComponent<WL816q_ScanRunner>();
|
||||
}
|
||||
}
|
||||
|
||||
public class WL816q_ScanRunner : UnityEngine.MonoBehaviour
|
||||
{
|
||||
static System.Text.StringBuilder sb;
|
||||
const int TS = 512;
|
||||
static string Dir = "Screenshots_WL/WL816q";
|
||||
static readonly float[] K0 = { 1.0737f, 1.1189f, 1.1365f };
|
||||
|
||||
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 < 5; 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;
|
||||
|
||||
var baseMat = arr[0].material;
|
||||
var dif0 = baseMat.GetColor("_DiffuseColor");
|
||||
var shd0 = baseMat.GetColor("_ShadowDiffuseColor");
|
||||
var clone = new UnityEngine.Material(baseMat); clone.name = baseMat.name + "_scan";
|
||||
for (int i = 0; i < arr.Length; i++) if (arr[i].material == baseMat) arr[i].material = clone;
|
||||
|
||||
var b = g.CalculateInstancesBounds();
|
||||
var cam = MakeTop(b.center.x, b.center.z, 6f);
|
||||
|
||||
float[] phases = { 0f, 3.5f, 7f }; // 구름이 흘러간 서로 다른 순간 3곳
|
||||
float[] ss = { 0.97f, 0.98f, 0.99f, 1.00f, 1.01f, 1.02f, 1.03f, 1.04f, 1.05f, 1.06f, 1.07f, 1.08f };
|
||||
var cov = new float[phases.Length, ss.Length];
|
||||
|
||||
for (int p = 0; p < phases.Length; p++)
|
||||
{
|
||||
if (p > 0) { UnityEngine.Time.timeScale = 1f; float t0 = UnityEngine.Time.realtimeSinceStartup; while (UnityEngine.Time.realtimeSinceStartup - t0 < 3.5f) yield return null; }
|
||||
UnityEngine.Time.timeScale = 0f; yield return null;
|
||||
|
||||
rt.grassEnabled = 0; g.Rebuild(); yield return null; yield return null;
|
||||
var A = Grab(cam, TS, TS); var apx = A.GetPixels32();
|
||||
rt.grassEnabled = 1;
|
||||
for (int i = 0; i < ss.Length; i++)
|
||||
{
|
||||
float s = ss[i];
|
||||
clone.SetColor("_DiffuseColor", new UnityEngine.Color(dif0.r * K0[0] * s, dif0.g * K0[1] * s, dif0.b * K0[2] * s, dif0.a));
|
||||
clone.SetColor("_ShadowDiffuseColor", new UnityEngine.Color(shd0.r * K0[0] * s, shd0.g * K0[1] * s, shd0.b * K0[2] * s, shd0.a));
|
||||
g.Rebuild(); yield return null; yield return null;
|
||||
var Bt = Grab(cam, TS, TS);
|
||||
cov[p, i] = Cover(apx, Bt.GetPixels32());
|
||||
UnityEngine.Object.DestroyImmediate(Bt);
|
||||
}
|
||||
UnityEngine.Object.DestroyImmediate(A);
|
||||
var line = "구름 위상 " + p + " (t=" + UnityEngine.Time.timeSinceLevelLoad.ToString("F1") + "s) 보이는 풀 % : ";
|
||||
for (int i = 0; i < ss.Length; i++) line += ss[i].ToString("F2") + "=" + cov[p, i].ToString("F2") + " ";
|
||||
L(line);
|
||||
}
|
||||
|
||||
// 세 위상 모두에서 최소인 구간의 중심
|
||||
int best = 0; float bestv = 999f;
|
||||
for (int i = 0; i < ss.Length; i++)
|
||||
{
|
||||
float m = 0f; for (int p = 0; p < phases.Length; p++) m = UnityEngine.Mathf.Max(m, cov[p, i]);
|
||||
if (m < bestv) { bestv = m; best = i; }
|
||||
}
|
||||
float sBest = ss[best];
|
||||
L("최적 s=" + sBest.ToString("F2") + " (세 위상 최대 보이는 풀 " + bestv.ToString("F2") + "%)");
|
||||
L(string.Format("채택 _DiffuseColor = {0:F4} {1:F4} {2:F4}", dif0.r * K0[0] * sBest, dif0.g * K0[1] * sBest, dif0.b * K0[2] * sBest));
|
||||
L(string.Format("채택 _ShadowDiffuseColor = {0:F4} {1:F4} {2:F4}", shd0.r * K0[0] * sBest, shd0.g * K0[1] * sBest, shd0.b * K0[2] * sBest));
|
||||
UnityEngine.Time.timeScale = 1f;
|
||||
Flush();
|
||||
}
|
||||
|
||||
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 (!(p.g > p.b + 8 && p.g > 45)) 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 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_SCAN.txt", sb.ToString());
|
||||
UnityEngine.Debug.Log("[816q scan]\n" + sb.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
public static class WL816q_Stat
|
||||
{
|
||||
public static void Go()
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
int n = UnityEngine.SceneManagement.SceneManager.sceneCount;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var s = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i);
|
||||
sb.Append(s.name + "(loaded=" + s.isLoaded + ") ");
|
||||
}
|
||||
var r = UnityEngine.GameObject.Find("~WL816qPlay");
|
||||
sb.Append(" | runner=" + (r != null) + " | t=" + UnityEngine.Time.timeSinceLevelLoad);
|
||||
var g = UnityEngine.Object.FindFirstObjectByType<WL.Look.Farm.WLIslandGrass>(UnityEngine.FindObjectsInactive.Include);
|
||||
sb.Append(" | grass=" + (g != null) + " inst=" + WL.Look.Farm.WLIslandGrass.Instances + " tiles=" + WL.Look.Farm.WLIslandGrass.Tiles);
|
||||
sb.Append(" | files=" + (System.IO.Directory.Exists("Screenshots_WL/WL816q") ? System.IO.Directory.GetFiles("Screenshots_WL/WL816q").Length : -1));
|
||||
System.IO.File.WriteAllText("AgentScripts/WL816q_STAT.txt", sb.ToString());
|
||||
UnityEngine.Debug.Log("[816q stat] " + sb.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ Material:
|
|||
- _CLOUDSENABLED: 1
|
||||
- _ClearCoatMask: 0
|
||||
- _ClearCoatSmoothness: 0
|
||||
- _Cloud_Change: 0.001
|
||||
- _Cloud_Change: 0.005
|
||||
- _Cloud_Cover: 0.5
|
||||
- _Cloud_Density: 0.01
|
||||
- _Cloud_Strength: 1
|
||||
|
|
@ -147,12 +147,12 @@ Material:
|
|||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _Cloud_Movement: {r: 0.2, g: 0.2, b: 0, a: 0}
|
||||
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
||||
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _DiffuseColor: {r: 0.60541177, g: 0.82788235, b: 0.5798824, a: 1}
|
||||
- _DiffuseColor: {r: 0.650031, g: 0.926315, b: 0.659036, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _ShadowDiffuseColor: {r: 0.5434118, g: 0.744, b: 0.5215294, a: 1}
|
||||
- _ShadowDiffuseColor: {r: 0.58346, g: 0.832462, b: 0.592718, a: 1}
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
- _WindMovement: {r: 6, g: 0, b: 0, a: 0}
|
||||
m_BuildTextureStacks: []
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Material:
|
|||
- _Blend: 0
|
||||
- _Brightness: 0.25
|
||||
- _CastShadows: 1
|
||||
- _Cloud_Change: 0.001
|
||||
- _Cloud_Change: 0.005
|
||||
- _Cloud_Cover: 0.5
|
||||
- _Cloud_Density: 0.01
|
||||
- _Cloud_Strength: 1
|
||||
|
|
@ -80,7 +80,7 @@ Material:
|
|||
- _ZWrite: 1
|
||||
- _ZWriteControl: 0
|
||||
m_Colors:
|
||||
- _Cloud_Movement: {r: 0.2, g: 0.2, b: 0, a: 0}
|
||||
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
|
||||
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
|
||||
- _DiffuseColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _NormalBias: {r: 1, g: 1, b: 1, a: 0}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,10 @@ MonoBehaviour:
|
|||
maxInstances: 40000
|
||||
rebuildPollSeconds: 1
|
||||
rebuildDelaySeconds: 1.4
|
||||
viewCullEnabled: 1
|
||||
viewMargin: 8
|
||||
viewRebuildStep: 2.5
|
||||
viewPollSeconds: 0.25
|
||||
tileMasks:
|
||||
- meshName: 01
|
||||
res: 64
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@ namespace WL.Look.Farm
|
|||
public static float UsedDensity;
|
||||
public static string LastLog = "";
|
||||
|
||||
// 816q — 가시 범위 배치 진단
|
||||
public static int ViewCulled, ViewRefreshes;
|
||||
public static bool ViewCullOn;
|
||||
public static Rect ViewRect;
|
||||
|
||||
// 816o — 구름 경계 배치 진단
|
||||
public static int Candidates, CloudRefreshes, CloudExcluded, EdgeCells, EdgeDilate, EdgeSamples;
|
||||
public static float EdgeFraction, EdgeCellSize, EdgeSampleCell, BakeMs, FilterMs;
|
||||
|
|
@ -74,6 +79,31 @@ namespace WL.Look.Farm
|
|||
Rebuilds++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 816q — 카메라가 움직인 만큼 **가시 범위만** 다시 고른다(타일 재수집 없음).
|
||||
/// 좌표는 월드 고정 격자라 이미 보이던 풀은 제자리 그대로다 — 늘고 주는 것은 여유 폭 안에서만.
|
||||
/// </summary>
|
||||
public void RefreshView()
|
||||
{
|
||||
if (!_cacheValid || !isActiveAndEnabled) return;
|
||||
if (cfg == null || cfg.viewCullEnabled == 0) return;
|
||||
_fastRefresh = true;
|
||||
enabled = false;
|
||||
enabled = true;
|
||||
_fastRefresh = false;
|
||||
ViewRefreshes++;
|
||||
}
|
||||
|
||||
/// <summary>바뀐 시야 사각형 중심이 직전 것과 이만큼 떨어졌는지(= 다시 골라야 하는지).</summary>
|
||||
public bool ViewMovedEnough()
|
||||
{
|
||||
if (cfg == null || cfg.viewCullEnabled == 0 || !_cacheValid) return false;
|
||||
Rect r;
|
||||
if (!TryViewRect(out r)) return false;
|
||||
return Mathf.Abs(r.center.x - _viewBuiltCenter.x) > Mathf.Max(0.05f, cfg.viewRebuildStep)
|
||||
|| Mathf.Abs(r.center.y - _viewBuiltCenter.y) > Mathf.Max(0.05f, cfg.viewRebuildStep);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 816o — 구름이 흐른 만큼 **띠만** 다시 고른다. 타일·제외 사각형은 다시 훑지 않는다
|
||||
/// (`FindObjectsByType` 를 타지 않으므로 전체 재생성보다 훨씬 싸다).
|
||||
|
|
@ -289,6 +319,62 @@ namespace WL.Look.Farm
|
|||
float _budgetScale = 1f;
|
||||
bool _edgeOn;
|
||||
|
||||
// ── 816q — 가시 범위 ────────────────────────────────────────────
|
||||
/// <summary>측정·검증용. 비우면 `Camera.main`(없으면 첫 활성 카메라)을 쓴다.</summary>
|
||||
public Camera viewCameraOverride;
|
||||
Rect _viewRect;
|
||||
Vector2 _viewBuiltCenter;
|
||||
bool _viewOn;
|
||||
|
||||
Camera PickCamera()
|
||||
{
|
||||
if (viewCameraOverride != null) return viewCameraOverride;
|
||||
var c = Camera.main;
|
||||
if (c != null) return c;
|
||||
var all = Object.FindObjectsByType<Camera>(FindObjectsSortMode.None);
|
||||
for (int i = 0; i < all.Length; i++) if (all[i].isActiveAndEnabled) return all[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>카메라가 바닥 평면(y = 윗면)에서 보는 사각형 + 여유 폭. 못 구하면 false.</summary>
|
||||
bool TryViewRect(out Rect rect)
|
||||
{
|
||||
rect = default(Rect);
|
||||
var cam = PickCamera();
|
||||
if (cam == null) return false;
|
||||
|
||||
float y = _cacheY;
|
||||
float far = cam.farClipPlane;
|
||||
float minX = float.MaxValue, maxX = float.MinValue, minZ = float.MaxValue, maxZ = float.MinValue;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
var vp = new Vector3((i & 1) == 0 ? 0f : 1f, (i & 2) == 0 ? 0f : 1f, 0f);
|
||||
var ray = cam.ViewportPointToRay(vp);
|
||||
Vector3 hit;
|
||||
// 평면을 향하지 않는 모서리(하늘로 나가는 방향)는 far 까지 뻗은 점으로 대신한다
|
||||
if (ray.direction.y < -1e-4f)
|
||||
{
|
||||
float t = (y - ray.origin.y) / ray.direction.y;
|
||||
hit = ray.origin + ray.direction * Mathf.Min(t, far);
|
||||
}
|
||||
else hit = ray.origin + ray.direction * far;
|
||||
|
||||
if (hit.x < minX) minX = hit.x; if (hit.x > maxX) maxX = hit.x;
|
||||
if (hit.z < minZ) minZ = hit.z; if (hit.z > maxZ) maxZ = hit.z;
|
||||
}
|
||||
float m = Mathf.Max(0f, cfg.viewMargin);
|
||||
minX -= m; maxX += m; minZ -= m; maxZ += m;
|
||||
|
||||
// 섬 밖은 어차피 후보가 없다 — 사각형을 섬 경계로 잘라 중심 판정이 튀지 않게 한다
|
||||
var bmin = _bounds.min; var bmax = _bounds.max;
|
||||
minX = Mathf.Max(minX, bmin.x); maxX = Mathf.Min(maxX, bmax.x);
|
||||
minZ = Mathf.Max(minZ, bmin.z); maxZ = Mathf.Min(maxZ, bmax.z);
|
||||
if (maxX <= minX || maxZ <= minZ) return false;
|
||||
|
||||
rect = Rect.MinMaxRect(minX, minZ, maxX, maxZ);
|
||||
return true;
|
||||
}
|
||||
|
||||
void BakeField()
|
||||
{
|
||||
float cell = Mathf.Max(0.05f, cfg.cloudEdgeCell);
|
||||
|
|
@ -315,7 +401,16 @@ namespace WL.Look.Farm
|
|||
BakeField();
|
||||
}
|
||||
|
||||
Instances = 0; Triangles = 0; DrawnConfigs = 0; CloudExcluded = 0;
|
||||
Instances = 0; Triangles = 0; DrawnConfigs = 0; CloudExcluded = 0; ViewCulled = 0;
|
||||
|
||||
// 816q — 카메라가 보는 범위(+여유 폭)만 남긴다. 좌표는 손대지 않는다(순간이동 0).
|
||||
_viewOn = false;
|
||||
if (cfg.viewCullEnabled != 0 && TryViewRect(out _viewRect))
|
||||
{
|
||||
_viewOn = true;
|
||||
_viewBuiltCenter = _viewRect.center;
|
||||
}
|
||||
ViewCullOn = _viewOn; ViewRect = _viewRect;
|
||||
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var buckets = new List<InstanceData>[_cdef.Length];
|
||||
|
|
@ -326,6 +421,7 @@ namespace WL.Look.Farm
|
|||
for (int i = 0; i < _candCount; i++)
|
||||
{
|
||||
var c = _cand[i];
|
||||
if (_viewOn && (c.x < _viewRect.xMin || c.x > _viewRect.xMax || c.z < _viewRect.yMin || c.z > _viewRect.yMax)) { ViewCulled++; continue; }
|
||||
if (edge && !_field.IsEdge(c.x, c.z)) { CloudExcluded++; continue; }
|
||||
|
||||
var rot = c.yaw != 0f ? Quaternion.Euler(0f, c.yaw, 0f) : Quaternion.identity;
|
||||
|
|
@ -352,6 +448,10 @@ namespace WL.Look.Farm
|
|||
+ " · 드로우콜 " + DrawnConfigs + " · 삼각형 " + Triangles + " · 제외점 " + Excluded
|
||||
+ " · 밀도 " + UsedDensity.ToString("F2") + "(=" + (UsedDensity * UsedDensity).ToString("F2") + "개/㎡)"
|
||||
+ (_budgetScale < 1f ? " · 예산으로 밀도 ×" + _budgetScale.ToString("F2") : "")
|
||||
+ (_viewOn
|
||||
? " · 가시범위 " + _viewRect.width.ToString("F1") + "×" + _viewRect.height.ToString("F1")
|
||||
+ "m(여유 " + cfg.viewMargin.ToString("F1") + "m) · 범위밖 제외 " + ViewCulled
|
||||
: " · 가시범위 off")
|
||||
+ (_edgeOn
|
||||
? " · 구름띠 " + (EdgeFraction * 100f).ToString("F1") + "%(칸 " + EdgeCellSize.ToString("F2")
|
||||
+ "m×" + EdgeCells + " · 넓힘 " + EdgeDilate + " · 노이즈 " + EdgeSampleCell.ToString("F2")
|
||||
|
|
|
|||
|
|
@ -167,10 +167,24 @@ namespace WL.Look.Farm
|
|||
{
|
||||
int last = CountUnlocked(scene);
|
||||
float nextCloud = Time.time + Mathf.Max(0f, cfg.cloudEdgeRefreshSeconds);
|
||||
float nextView = Time.time + Mathf.Max(0f, cfg.viewPollSeconds);
|
||||
float nextPoll = Time.time + cfg.rebuildPollSeconds;
|
||||
while (scene.isLoaded)
|
||||
{
|
||||
yield return new WaitForSeconds(cfg.rebuildPollSeconds);
|
||||
// 816q — 카메라 감시는 확장 감시(1 s)보다 촘촘해야 해서 따로 돈다
|
||||
float wait = cfg.viewCullEnabled != 0 && cfg.viewPollSeconds > 0f
|
||||
? Mathf.Min(cfg.rebuildPollSeconds, cfg.viewPollSeconds) : cfg.rebuildPollSeconds;
|
||||
yield return new WaitForSeconds(wait);
|
||||
if (!scene.isLoaded) yield break;
|
||||
|
||||
if (cfg.viewCullEnabled != 0 && cfg.viewPollSeconds > 0f && Time.time >= nextView)
|
||||
{
|
||||
nextView = Time.time + cfg.viewPollSeconds;
|
||||
var gv = Object.FindFirstObjectByType<WLIslandGrass>(FindObjectsInactive.Include);
|
||||
if (gv != null && gv.ViewMovedEnough()) gv.RefreshView();
|
||||
}
|
||||
if (Time.time < nextPoll) continue;
|
||||
nextPoll = Time.time + cfg.rebuildPollSeconds;
|
||||
HookIslands(cfg, scene);
|
||||
int now = CountUnlocked(scene);
|
||||
if (now != last)
|
||||
|
|
|
|||
|
|
@ -311,6 +311,25 @@ namespace WL.Look.Farm
|
|||
"바닥과 **같은 식**이어야 띠가 어긋나지 않는다.")]
|
||||
public Material cloudEdgeSourceMaterial;
|
||||
|
||||
// ───────────────────────────────── §3 가시 범위 배치 (816q)
|
||||
[Header("§3 — 카메라가 보는 범위에만 인스턴스를 둔다 (816q · 배치가 아니라 범위로 최적화)")]
|
||||
[Tooltip("🔴 되돌리기 스위치 — 0 이면 섬 전체에 인스턴스를 둔다(816n 상태). " +
|
||||
"1 이면 카메라 시야 사각형 + 여유 폭 안에만 둔다. " +
|
||||
"격자는 월드 고정이라 같은 자리의 풀은 항상 같은 자리다(순간이동 0) — " +
|
||||
"늘고 주는 것은 **화면 밖 여유 폭 안에서만** 일어난다.")]
|
||||
public int viewCullEnabled = 1;
|
||||
|
||||
[Tooltip("시야 사각형 바깥으로 더 두는 여유 폭(m). " +
|
||||
"🔴 `viewRebuildStep` 보다 커야 한다 — 차이만큼이 「화면 밖에서만 바뀐다」는 보장 폭이다.")]
|
||||
public float viewMargin = 8f;
|
||||
|
||||
[Tooltip("카메라(시야 사각형 중심)가 이만큼(m) 움직였을 때만 다시 고른다. " +
|
||||
"작을수록 자주 고르고, 클수록 여유 폭을 많이 먹는다.")]
|
||||
public float viewRebuildStep = 2.5f;
|
||||
|
||||
[Tooltip("시야 사각형을 다시 재는 주기(초). 0 이면 감시 안 함(한 번 고르고 고정).")]
|
||||
public float viewPollSeconds = 0.25f;
|
||||
|
||||
// ───────────────────────────────── 구운 마스크
|
||||
[Header("§1 — 구운 윗면 마스크 (에디터에서 생성 · 손대지 말 것)")]
|
||||
public WLTileMask[] tileMasks = new WLTileMask[0];
|
||||
|
|
|
|||
Loading…
Reference in New Issue