Merge branch 'wl/gameplay/WL-816zh-cloud-band-grass'
This commit is contained in:
commit
1362c01540
|
|
@ -138,7 +138,7 @@ Material:
|
|||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _DiffuseColor: {r: 0.62, g: 0.82, b: 0.485, a: 0}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _ShadowDiffuseColor: {r: 0.62, g: 0.82, b: 0.485, a: 0}
|
||||
- _ShadowDiffuseColor: {r: 0.55, g: 0.75, b: 0.47, a: 0}
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
- _WindMovement: {r: 6, g: 0, b: 0, a: 0}
|
||||
m_BuildTextureStacks: []
|
||||
|
|
|
|||
|
|
@ -134,6 +134,12 @@ namespace WL.Look.Farm
|
|||
/// <summary>시간만 다시 읽는다(주기 갱신용 — 머티리얼·태양은 그대로).</summary>
|
||||
public void Tick() { ResolveSun(); ResolveTime(); }
|
||||
|
||||
/// <summary>시간만 다시 읽는다(816zh 띠 갱신용 — 태양·머티리얼은 그대로 · FindObjectsByType 0회).</summary>
|
||||
public void TickTime() { ResolveTime(); }
|
||||
|
||||
/// <summary>머티리얼 `_Cloud_Cover` — 816zh 띠의 중심선(구름 높이가 이 값을 지나는 곳 = 그림자 얼룩의 가장자리).</summary>
|
||||
public float Cover { get { return _cover; } }
|
||||
|
||||
/// <summary>셰이더가 쓰는 `_Time.y`. 진단·검증에서 직접 넣어 볼 수 있다.</summary>
|
||||
public float TimeValue { get { return _time; } set { _time = value; } }
|
||||
|
||||
|
|
@ -321,6 +327,72 @@ namespace WL.Look.Farm
|
|||
return _edge[cz * _nx + cx] != 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// ③-b 816zh — 구름 「높이」 표본 격자 (강도를 걷어 낸 값 · 경계 = cover 를 지나는 선)
|
||||
//
|
||||
// Cloudiness = (cover + 0.5·noise) × strength. 그림자 얼룩의 「가장자리」는 노이즈가 0 을 지나는 선
|
||||
// = 강도를 걷어 낸 값이 cover 와 같아지는 선이다. 🔴 강도(0.5)를 곱한 값에 |c − cover| 를 재면
|
||||
// c 가 cover 에 닿지 못해(최대 ≈ 0.43) 띠가 아예 없어진다 — 그래서 판정은 강도를 걷어 낸 값으로 한다.
|
||||
// 비싼 6 옥타브 노이즈는 성긴 격자에서만 계산하고(816o 와 같은 원칙) 사이는 겹선형 보간.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
float[] _lv;
|
||||
int _lx, _lz;
|
||||
float _l0x, _l0z, _linv;
|
||||
public int LevelSamples; // 노이즈를 실제로 계산한 점 수(비용은 여기서만 난다)
|
||||
public float LevelCell;
|
||||
|
||||
/// <summary>범위의 XZ 평면(높이 y)에 구름 높이를 성긴 격자로 굽는다. 칸 수가 상한을 넘으면 간격을 1.25 배씩 키운다.</summary>
|
||||
public void BakeLevels(Bounds b, float y, float sampleCell, int maxSamples)
|
||||
{
|
||||
LevelSamples = 0;
|
||||
if (!Ready) { _lv = null; return; }
|
||||
if (sampleCell < 0.05f) sampleCell = 0.05f;
|
||||
float w = Mathf.Max(b.size.x, 0.1f), h = Mathf.Max(b.size.z, 0.1f);
|
||||
int sx = 2, sz = 2;
|
||||
for (int guard = 0; guard < 24; guard++)
|
||||
{
|
||||
sx = Mathf.CeilToInt(w / sampleCell) + 3; // 한 칸 바깥에서 시작 + 끝 보간 여유
|
||||
sz = Mathf.CeilToInt(h / sampleCell) + 3;
|
||||
if (maxSamples <= 0 || (long)sx * sz <= maxSamples) break;
|
||||
sampleCell *= 1.25f;
|
||||
}
|
||||
_lx = sx; _lz = sz; _linv = 1f / sampleCell; LevelCell = sampleCell;
|
||||
_l0x = b.min.x - sampleCell; _l0z = b.min.z - sampleCell;
|
||||
if (_lv == null || _lv.Length < sx * sz) _lv = new float[sx * sz];
|
||||
float inv = _strength > 1e-6f ? 1f / _strength : 1f;
|
||||
for (int j = 0; j < sz; j++)
|
||||
{
|
||||
float wz = _l0z + j * sampleCell;
|
||||
int row = j * sx;
|
||||
for (int i = 0; i < sx; i++) _lv[row + i] = Cloudiness(_l0x + i * sampleCell, y, wz) * inv;
|
||||
}
|
||||
LevelSamples = sx * sz;
|
||||
}
|
||||
|
||||
/// <summary>표본 격자를 겹선형으로 읽은 구름 높이(= cover + 0.5·noise). 격자 밖은 가장자리 값으로 고정.</summary>
|
||||
public float LevelAt(float wx, float wz)
|
||||
{
|
||||
if (_lv == null || LevelSamples == 0) return _cover;
|
||||
float fx = (wx - _l0x) * _linv, fz = (wz - _l0z) * _linv;
|
||||
int jx = (int)fx; if (jx < 0) jx = 0; else if (jx > _lx - 2) jx = _lx - 2;
|
||||
int jz = (int)fz; if (jz < 0) jz = 0; else if (jz > _lz - 2) jz = _lz - 2;
|
||||
float tx = fx - jx; if (tx < 0f) tx = 0f; else if (tx > 1f) tx = 1f;
|
||||
float tz = fz - jz; if (tz < 0f) tz = 0f; else if (tz > 1f) tz = 1f;
|
||||
int r0 = jz * _lx + jx, r1 = r0 + _lx;
|
||||
float a = _lv[r0] + (_lv[r0 + 1] - _lv[r0]) * tx;
|
||||
float c = _lv[r1] + (_lv[r1 + 1] - _lv[r1]) * tx;
|
||||
return a + (c - a) * tz;
|
||||
}
|
||||
|
||||
/// <summary>띠 가중치 w = smoothstep(clamp01(1 − |높이 − cover| / width)) — 그림자 경계에서 1, 안쪽·바깥쪽은 0.</summary>
|
||||
public float BandWeight(float wx, float wz, float width)
|
||||
{
|
||||
float t = 1f - Mathf.Abs(LevelAt(wx, wz) - _cover) / (width > 1e-4f ? width : 1e-4f);
|
||||
if (t <= 0f) return 0f;
|
||||
if (t >= 1f) return 1f;
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// ④ SimplexNoise3D.hlsl `snoise` 이식 (Ashima Arts · MIT · 원본 파일 0줄 수정)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ namespace WL.Look.Farm
|
|||
public static float OutlineRadius, OutlineOverhang, OutlineBakeMs;
|
||||
public static string OutlineWhy = "";
|
||||
|
||||
// 816zh — 구름 그림자 경계 띠(부드러운 성장) 진단
|
||||
public static bool CloudBandOn;
|
||||
public static int BandCandidates, BandCompacted, BandRefreshes, BandSamples;
|
||||
public static float BandMs, BandSampleCell;
|
||||
public static string CloudBandWhy = "";
|
||||
|
||||
public WLIslandLookSettings cfg;
|
||||
|
||||
Bounds _bounds = new Bounds(Vector3.zero, Vector3.one * 16f);
|
||||
|
|
@ -66,7 +72,8 @@ namespace WL.Look.Farm
|
|||
bool _softDirt;
|
||||
|
||||
// ── 816o 캐시 — 한 번 뽑은 후보를 들고 있다가, 구름이 흐르면 「고르기」만 다시 한다 ──
|
||||
struct Cand { public float x, z, y; public int def; public float sc; public float yaw; }
|
||||
// 816zh — band: 1 = 물리 경계 띠 안(항상 배율 1) · 0 = 들판(구름 경계 가중치 w 가 배율을 정한다 · 0~1)
|
||||
struct Cand { public float x, z, y; public int def; public float sc; public float yaw; public byte band; public float w; }
|
||||
Cand[] _cand;
|
||||
int _candCount;
|
||||
InstancingSettings[] _cset;
|
||||
|
|
@ -77,6 +84,13 @@ namespace WL.Look.Farm
|
|||
bool _fastRefresh;
|
||||
readonly WLCloudShadowField _field = new WLCloudShadowField();
|
||||
|
||||
// ── 816zh — 구름 그림자 경계 띠(부드러운 성장) ──
|
||||
bool _bandOn; // 이번 캐시가 띠 가중치를 쓰는가(바닥 재질에서 구름을 읽었을 때만)
|
||||
bool _bandRefresh; // 지금 토글이 「띠 목표 재계산」 갱신인가(816o 굽기·로그는 건너뛴다)
|
||||
float _bandNext; // 다음 갱신 시각(unscaled)
|
||||
int _bandPendingFrame = -1; // 프레임 끝 토글을 예약한 프레임(같은 프레임에 두 번 예약 방지 · 코루틴이 죽어도 다음 프레임에 복구)
|
||||
static readonly WaitForEndOfFrame s_eof = new WaitForEndOfFrame();
|
||||
|
||||
public override Bounds CalculateInstancesBounds() { return _bounds; }
|
||||
|
||||
/// <summary>다시 깐다(섬이 확장됐을 때). 멱등 — 몇 번 불러도 안전.</summary>
|
||||
|
|
@ -132,6 +146,43 @@ namespace WL.Look.Farm
|
|||
CloudRefreshes++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 816zh — 구름이 흐른 만큼 풀잎 **배율 목표만** 다시 계산해 버퍼를 다시 채운다(좌표·후보 불변 · 팝핑 0).
|
||||
/// 갱신 주기 = `cloudBandRefreshSeconds`(LateUpdate 가 스스로 돈다). 프로브가 직접 불러도 안전.
|
||||
/// </summary>
|
||||
public void RefreshCloudBand()
|
||||
{
|
||||
if (!_cacheValid || !isActiveAndEnabled || !_bandOn) return;
|
||||
_fastRefresh = true; _bandRefresh = true;
|
||||
enabled = false;
|
||||
enabled = true;
|
||||
_fastRefresh = false; _bandRefresh = false;
|
||||
BandRefreshes++;
|
||||
}
|
||||
|
||||
// 🔴 기반 클래스(InstancesBehaviour)에는 Update·OnEnable·OnDisable 만 있다(private) — LateUpdate 는 여기 정의해도 가려지는 것이 없다.
|
||||
// OnEnable/OnDisable/Update 를 이 클래스에 정의하면 기반 것이 가려져 버퍼가 안 만들어진다 — 절대 정의하지 말 것.
|
||||
void LateUpdate()
|
||||
{
|
||||
if (!_bandOn || !_cacheValid || cfg == null) return;
|
||||
if (_bandPendingFrame == Time.frameCount) return;
|
||||
if (Time.unscaledTime < _bandNext) return;
|
||||
_bandPendingFrame = Time.frameCount;
|
||||
StartCoroutine(RefreshCloudBandAtEndOfFrame());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 토글(OnDisable → 버퍼 해제 → OnEnable → 재생성)은 **프레임 끝**에 한다 — 기반 Update 가 이번 프레임 드로우에 이미 넘긴
|
||||
/// 버퍼를 렌더링 전에 해제하면 그 프레임의 풀이 통째로 빠져 5 Hz 깜빡임이 된다.
|
||||
/// </summary>
|
||||
System.Collections.IEnumerator RefreshCloudBandAtEndOfFrame()
|
||||
{
|
||||
yield return s_eof;
|
||||
if (cfg == null) yield break;
|
||||
_bandNext = Time.unscaledTime + Mathf.Max(0.02f, cfg.cloudBandRefreshSeconds);
|
||||
RefreshCloudBand();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 점 뽑기 — 데모 `TerrainInstancesBehaviour.GetInstanceData` 와 같은 절차
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
|
@ -145,6 +196,8 @@ namespace WL.Look.Farm
|
|||
BakeMs = 0f; FilterMs = 0f; CloudEdgeOn = false; CloudWhy = ""; _edgeOn = false;
|
||||
OutlineRoundOn = false; OutlineCells = 0; OutlineRadius = 0f; OutlineOverhang = 0f;
|
||||
OutlineBakeMs = 0f; OutlineWhy = ""; _outOn = false;
|
||||
CloudBandOn = false; CloudBandWhy = ""; _bandOn = false;
|
||||
BandCandidates = 0; BandCompacted = 0; BandSamples = 0; BandMs = 0f; BandSampleCell = 0f;
|
||||
|
||||
if (cfg == null) cfg = WLIslandLookSettings.Instance;
|
||||
if (cfg == null || cfg.enabled_ == 0 || cfg.grassEnabled == 0) { LastLog = "꺼짐"; return null; }
|
||||
|
|
@ -185,9 +238,20 @@ namespace WL.Look.Farm
|
|||
_bounds = new Bounds((bmin + bmax) * 0.5f, bmax - bmin);
|
||||
_cacheY = tiles[0].center.y;
|
||||
|
||||
// ── 816zh — 구름 그림자 경계 띠(부드러운 성장): 바닥 재질의 구름 식을 읽는다(816o 와 같은 필드 · 같은 재질) ──
|
||||
// 못 읽으면(구름 꺼짐 등) 물리 경계 띠만 = 816ze 그대로.
|
||||
if (cfg.cloudBandEnabled != 0)
|
||||
{
|
||||
var bsrc = cfg.cloudEdgeSourceMaterial != null ? cfg.cloudEdgeSourceMaterial : cfg.islandTopMaterial;
|
||||
if (_field.Configure(bsrc)) { _bandOn = true; CloudBandOn = true; }
|
||||
else CloudBandWhy = _field.Why;
|
||||
}
|
||||
|
||||
// ── 816o — 바닥과 **같은 식**으로 구름 그림자 색 띠의 경계를 굽는다 ──
|
||||
// 816zh 띠가 켜져 있으면 816o(하드 컷 = 순간이동)는 쓰지 않는다 — 둘을 겹치면 팝핑이 되살아난다.
|
||||
float coverage = 1f;
|
||||
if (cfg.cloudEdgeEnabled != 0)
|
||||
if (cfg.cloudEdgeEnabled != 0 && _bandOn) CloudWhy = "816zh 구름 띠가 대신한다";
|
||||
else if (cfg.cloudEdgeEnabled != 0)
|
||||
{
|
||||
var src = cfg.cloudEdgeSourceMaterial != null ? cfg.cloudEdgeSourceMaterial : cfg.islandTopMaterial;
|
||||
if (_field.Configure(src))
|
||||
|
|
@ -314,13 +378,25 @@ namespace WL.Look.Farm
|
|||
if (r <= acc) { pick = d; break; }
|
||||
}
|
||||
|
||||
// 2026-09-15 PD 「그라데이션과 무관하게 풀이 얼룩덜룩」 — 경계 전용 정의는 경계 띠 밖(들판 한가운데)에는 깔지 않는다.
|
||||
if (cfg.grassEdgeBand > 0f && defs[pick].edgeOnly_ != 0 && !NearBoundary(tiles, wx, wz, cfg.grassEdgeBand)) { Excluded++; continue; }
|
||||
// 2026-09-15 PD 「그라데이션과 무관하게 풀이 얼룩덜룩」 — 경계 전용 정의는 물리 경계 띠 밖(들판 한가운데)에는 깔지 않는다.
|
||||
// 816zh(PD 「그림자를 기준으로」) — 구름 띠가 켜져 있으면 들판에도 후보를 두되 band = 0 으로 표시한다:
|
||||
// 배율 = 구름 경계 가중치 w(주기마다 목표 재계산 · lerp 로 자라고 줄어듦). 물리 띠 안(band = 1)은 w 와 무관하게 항상 1.
|
||||
byte band = 1;
|
||||
if (defs[pick].edgeOnly_ != 0)
|
||||
{
|
||||
bool physical = cfg.grassEdgeBand > 0f;
|
||||
bool near = physical && NearBoundary(tiles, wx, wz, cfg.grassEdgeBand);
|
||||
if (!near)
|
||||
{
|
||||
if (_bandOn) { band = 0; BandCandidates++; }
|
||||
else if (physical) { Excluded++; continue; }
|
||||
}
|
||||
}
|
||||
|
||||
float sc = 1f + (Frac(h, 3) - 0.5f) * 2f * sv;
|
||||
float yaw = cfg.randomYaw != 0 ? Frac(h, 4) * 360f : 0f;
|
||||
|
||||
cands.Add(new Cand { x = wx, z = wz, y = t.center.y, def = baseIdx + pick, sc = sc, yaw = yaw });
|
||||
cands.Add(new Cand { x = wx, z = wz, y = t.center.y, def = baseIdx + pick, sc = sc, yaw = yaw, band = band, w = 1f });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -442,15 +518,42 @@ namespace WL.Look.Farm
|
|||
EdgeFraction = _field.EdgeFraction; EdgeDilate = dil;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 816zh — 구름 「높이」(강도를 걷어 낸 cover + 0.5·noise)를 성긴 격자에서 계산해 두고(6 옥타브 노이즈 비용은 여기서만 ·
|
||||
/// 간격·상한은 816o 의 `cloudEdgeSampleCell`/`cloudEdgeMaxSamples` 재사용), 들판 후보마다 목표 배율
|
||||
/// w = smoothstep(1 − |높이 − cover| / cloudBandWidth) 을 구한다 — 그림자 경계에서 1, 안쪽·바깥쪽은 0.
|
||||
/// follow = true 면 현재값이 목표로 `cloudBandLerp` 만큼 다가간다(자라고 줄어듦) · false 면 목표값을 그대로 쓴다(첫 굽기).
|
||||
/// </summary>
|
||||
void BakeBand(bool follow)
|
||||
{
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
_field.TickTime();
|
||||
_field.BakeLevels(_bounds, _cacheY, cfg.cloudEdgeSampleCell, cfg.cloudEdgeMaxSamples);
|
||||
float width = Mathf.Max(1e-4f, cfg.cloudBandWidth);
|
||||
float k = follow ? Mathf.Clamp01(cfg.cloudBandLerp) : 1f;
|
||||
for (int i = 0; i < _candCount; i++)
|
||||
{
|
||||
if (_cand[i].band != 0) continue;
|
||||
float target = _field.BandWeight(_cand[i].x, _cand[i].z, width);
|
||||
_cand[i].w += (target - _cand[i].w) * k;
|
||||
}
|
||||
sw.Stop();
|
||||
BandMs = (float)sw.Elapsed.TotalMilliseconds;
|
||||
BandSamples = _field.LevelSamples; BandSampleCell = _field.LevelCell;
|
||||
_bandNext = Time.unscaledTime + Mathf.Max(0.02f, cfg.cloudBandRefreshSeconds);
|
||||
}
|
||||
|
||||
Dictionary<InstancingSettings, List<InstanceData>> BuildFromCache(bool refresh)
|
||||
{
|
||||
if (refresh)
|
||||
if (refresh && !_bandRefresh)
|
||||
{
|
||||
_field.Tick();
|
||||
BakeField();
|
||||
}
|
||||
// 816zh — 첫 굽기는 목표값 그대로(로드 때 자라나지 않는다) · 띠 갱신은 현재값이 목표로 lerp(자라고 줄어듦) · 뷰 갱신은 손대지 않는다
|
||||
if (_bandOn && (!refresh || _bandRefresh)) BakeBand(refresh);
|
||||
|
||||
Instances = 0; Triangles = 0; DrawnConfigs = 0; CloudExcluded = 0; ViewCulled = 0;
|
||||
Instances = 0; Triangles = 0; DrawnConfigs = 0; CloudExcluded = 0; ViewCulled = 0; BandCompacted = 0;
|
||||
|
||||
// 816q — 카메라가 보는 범위(+여유 폭)만 남긴다. 좌표는 손대지 않는다(순간이동 0).
|
||||
_viewOn = false;
|
||||
|
|
@ -467,6 +570,7 @@ namespace WL.Look.Farm
|
|||
|
||||
Vector3 bc = _bounds.center;
|
||||
bool edge = _edgeOn;
|
||||
float minW = Mathf.Max(0f, cfg.cloudBandMinScale);
|
||||
ulong key = 0UL;
|
||||
for (int i = 0; i < _candCount; i++)
|
||||
{
|
||||
|
|
@ -474,8 +578,15 @@ namespace WL.Look.Farm
|
|||
if (_viewOn && !InView(c.x, c.z)) { ViewCulled++; continue; }
|
||||
if (edge && !_field.IsEdge(c.x, c.z)) { CloudExcluded++; continue; }
|
||||
|
||||
float sc = c.sc;
|
||||
if (c.band == 0)
|
||||
{
|
||||
// 816zh — 들판 풀잎은 구름 경계 가중치가 배율. 거의 0 이면 버퍼에서 뺀다(정점 0 · 압축).
|
||||
if (c.w < minW) { BandCompacted++; continue; }
|
||||
sc *= c.w;
|
||||
}
|
||||
var rot = c.yaw != 0f ? Quaternion.Euler(0f, c.yaw, 0f) : Quaternion.identity;
|
||||
var trs = Matrix4x4.TRS(new Vector3(c.x, c.y, c.z) - bc, rot, Vector3.one * c.sc);
|
||||
var trs = Matrix4x4.TRS(new Vector3(c.x, c.y, c.z) - bc, rot, Vector3.one * sc);
|
||||
var def = _cdef[c.def];
|
||||
trs *= Matrix4x4.TRS(def.normalOffset * Vector3.up, Quaternion.identity,
|
||||
new Vector3(def.scale, def.scale, def.scale));
|
||||
|
|
@ -516,8 +627,13 @@ namespace WL.Look.Farm
|
|||
+ "m×" + EdgeCells + " · 넓힘 " + EdgeDilate + " · 노이즈 " + EdgeSampleCell.ToString("F2")
|
||||
+ "m×" + EdgeSamples + ") · 띠밖 제외 " + CloudExcluded
|
||||
+ " · 굽기 " + BakeMs.ToString("F1") + "ms · 고르기 " + FilterMs.ToString("F1") + "ms"
|
||||
: " · 구름띠 off" + (CloudWhy.Length > 0 ? "(" + CloudWhy + ")" : ""));
|
||||
cfg.Log(LastLog);
|
||||
: " · 구름띠 off" + (CloudWhy.Length > 0 ? "(" + CloudWhy + ")" : ""))
|
||||
+ (_bandOn
|
||||
? " · 구름경계띠(816zh) 폭 " + cfg.cloudBandWidth.ToString("F2") + " · 들판후보 " + BandCandidates
|
||||
+ " · 압축 " + BandCompacted + " · 표본 " + BandSampleCell.ToString("F2") + "m×" + BandSamples
|
||||
+ " · 굽기 " + BandMs.ToString("F1") + "ms · 갱신 " + BandRefreshes
|
||||
: " · 구름경계띠(816zh) off" + (CloudBandWhy.Length > 0 ? "(" + CloudBandWhy + ")" : ""));
|
||||
if (!_bandRefresh) cfg.Log(LastLog); // 816zh — 주기 갱신마다 로그를 찍지 않는다(프로브는 LastLog 를 읽는다)
|
||||
return result.Count == 0 ? null : result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -370,8 +370,31 @@ namespace WL.Look.Farm
|
|||
// ───────────────────────────────── §1 풀밭
|
||||
[Header("§1 — 섬 타일 위 풀밭 (데모와 같은 인스턴싱)")]
|
||||
[Tooltip("`edgeOnly_ 1` 정의를 경계에서 이만큼(m) 안쪽까지만 깐다. 0 = 띠 제한 없음. " +
|
||||
"2026-09-15 PD 「그라데이션과 무관하게 풀이 얼룩덜룩」 — 들판 안쪽 풀잎은 같은 색이라도 미세한 음영 차로 얼룩이 되므로 경계 실루엣만 남긴다.")]
|
||||
public float grassEdgeBand = 1.2f;
|
||||
"2026-09-15 PD 「그라데이션과 무관하게 풀이 얼룩덜룩」 — 들판 안쪽 풀잎은 같은 색이라도 미세한 음영 차로 얼룩이 되므로 경계 실루엣만 남긴다. " +
|
||||
"816zh — 구름 경계 띠(`cloudBandEnabled 1`)가 켜지면 들판은 구름 그림자 경계가 맡으므로 이 물리 띠는 0.6 으로 낮춰 병행한다(띠 안은 항상 배율 1).")]
|
||||
public float grassEdgeBand = 0.6f;
|
||||
|
||||
// ── 816zh: 풀잎을 「구름 그림자(그라데이션) 경계」에 — 크기로 부드럽게 자라고 줄어든다 (PD 2026-09-16 「그림자를 기준으로」)
|
||||
[Header("§1-816zh — 풀잎 = 구름 그림자 경계 띠 (부드러운 성장 · 팝핑 0)")]
|
||||
[Tooltip("🔴 되돌리기 — 0 = 지금처럼 `edgeOnly_` 풀잎을 물리 경계 띠(grassEdgeBand)에만. " +
|
||||
"1 = 들판 전체에 후보를 두고, 구름 그림자 경계(구름 높이가 바닥 재질 `_Cloud_Cover` 를 지나는 선) 근처만 배율을 키워 그린다. " +
|
||||
"816o(하드 컷 = 구름이 흐르면 순간이동)와 달리 껐다 켜는 게 아니라 **자라고 줄어든다**. 켜지면 816o `cloudEdgeEnabled` 는 무시된다. " +
|
||||
"구름 식은 `cloudEdgeSourceMaterial`(비우면 `islandTopMaterial`)에서 읽는다 — 바닥과 같은 식.")]
|
||||
public int cloudBandEnabled = 1;
|
||||
|
||||
[Tooltip("띠 반폭(구름 높이 단위 · m 아님) — 강도를 걷어 낸 구름 높이(cover + 0.5·noise)가 cover 에서 이만큼 벗어나면 배율 0, 경계선 위는 1(smoothstep). " +
|
||||
"🔴 현 바닥 재질(_Shades 7 · _Brightness 0.25 · 강도 0.5)의 보이는 색 계단은 cover ± 0.143 에 있다 — 0.12 는 그 사이 그라데이션 중심 띠. 계단선까지 덮으려면 0.15~0.18.")]
|
||||
public float cloudBandWidth = 0.12f;
|
||||
|
||||
[Tooltip("띠 목표 배율을 다시 계산하고 인스턴스 버퍼를 다시 채우는 주기(초). 구름은 초당 약 1.4 m 흐른다(_Cloud_Movement 1,1). " +
|
||||
"비용 = 노이즈 표본(cloudEdgeSampleCell 간격 · 6 옥타브) + 후보 전수 겹선형 읽기 + 버퍼 재생성, 이 주기마다 1회.")]
|
||||
public float cloudBandRefreshSeconds = 0.2f;
|
||||
|
||||
[Tooltip("갱신 때마다 현재 배율이 목표로 다가가는 비율(0~1). 작을수록 천천히 자라고 줄어든다 — 0.15 × 0.2 s ≈ 시간상수 1.2 초(구름 이동 약 1.7 m 뒤따름). 1 = 즉시(=팝핑).")]
|
||||
[Range(0.01f, 1f)] public float cloudBandLerp = 0.15f;
|
||||
|
||||
[Tooltip("이 배율 미만인 들판 풀잎은 버퍼에서 뺀다(정점 0 · 압축). 너무 크면 사라질 때 톡 끊긴다.")]
|
||||
public float cloudBandMinScale = 0.02f;
|
||||
|
||||
[Tooltip("0 이면 풀을 깔지 않는다.")]
|
||||
public int grassEnabled = 1;
|
||||
|
|
@ -460,10 +483,10 @@ namespace WL.Look.Farm
|
|||
|
||||
[Tooltip("🔴 비용이 나는 유일한 곳 — 구름 노이즈(6 옥타브)를 실제로 계산하는 간격(m). " +
|
||||
"구름의 가장 작은 무늬가 1/(32×_Cloud_Density) ≈ 3 m 라 1 m 면 충분하다. " +
|
||||
"사이 값은 겹선형 보간으로 채운다.")]
|
||||
"사이 값은 겹선형 보간으로 채운다. 816zh 구름 경계 띠도 이 간격으로 표본을 뜬다(0.2 s 마다 · 키우면 비용이 제곱으로 준다).")]
|
||||
public float cloudEdgeSampleCell = 1f;
|
||||
|
||||
[Tooltip("노이즈를 계산하는 점의 개수 상한. 섬이 커지면 간격을 자동으로 늘려 비용을 묶어 둔다.")]
|
||||
[Tooltip("노이즈를 계산하는 점의 개수 상한. 섬이 커지면 간격을 자동으로 늘려 비용을 묶어 둔다(816zh 띠도 같은 상한).")]
|
||||
public int cloudEdgeMaxSamples = 4096;
|
||||
|
||||
[Tooltip("경계에서 몇 칸 더 넓힐 것인가. 띠 폭 ≈ (1 + 2 × 이 값) × 칸(m). " +
|
||||
|
|
|
|||
Loading…
Reference in New Issue