[WL-816ze/zg] 데모 동일 카메라 대조로 확정 — 풀 = 데모 규칙(0.35 · 6.25/㎡ · 균일 · 구름띠/경계띠 off · 밑동 어둡게 · 잎 윗면 diffuse ×1.167 로 타일과 동일 → 잎 윤곽 소멸 sd 11→2.8) · 해변 = 풀/모래 경계 등고선 절단(마칭 트라이앵글) + 굽이 노이즈 0.8 m + 모서리 필렛 R3(keep ≥ R) (#816)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-09-16 09:49:01 +09:00
parent 53bba8b5bd
commit c896b684b0
4 changed files with 100 additions and 20 deletions

View File

@ -136,9 +136,9 @@ Material:
- _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0} - _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0}
- _Cloud_Step: {r: 13, g: 17, b: 0, a: 0} - _Cloud_Step: {r: 13, g: 17, b: 0, a: 0}
- _Color: {r: 1, g: 1, b: 1, a: 1} - _Color: {r: 1, g: 1, b: 1, a: 1}
- _DiffuseColor: {r: 0.62, g: 0.82, b: 0.485, a: 0} - _DiffuseColor: {r: 0.72, g: 0.957, b: 0.566, a: 0}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1} - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _ShadowDiffuseColor: {r: 0.55, g: 0.75, b: 0.47, a: 0} - _ShadowDiffuseColor: {r: 0.2509804, g: 0.3254902, b: 0.23529413, a: 0}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
- _WindMovement: {r: 6, g: 0, b: 0, a: 0} - _WindMovement: {r: 6, g: 0, b: 0, a: 0}
m_BuildTextureStacks: [] m_BuildTextureStacks: []

View File

@ -85,35 +85,38 @@ MonoBehaviour:
mesh: {fileID: -4413095505993930501, guid: 1f298817fdd2a184480e8af5b21278bc, type: 3} mesh: {fileID: -4413095505993930501, guid: 1f298817fdd2a184480e8af5b21278bc, type: 3}
material: {fileID: 2100000, guid: 3b82f6a1cd074e2f9a15d8e73c40b592, type: 2} material: {fileID: 2100000, guid: 3b82f6a1cd074e2f9a15d8e73c40b592, type: 2}
probability: 100 probability: 100
scale: 0.1 scale: 0.35
normalOffset: 0.04 normalOffset: 0.04
density: 5 density: 2.5
edgeOnly_: 1 edgeOnly_: 0
- enabled_: 1 - enabled_: 1
label: "\uAF43" label: "\uAF43"
mesh: {fileID: -6327915695457451627, guid: dab2c84bf0b5ced4aaf0cffc4c491d55, type: 3} mesh: {fileID: -6327915695457451627, guid: dab2c84bf0b5ced4aaf0cffc4c491d55, type: 3}
material: {fileID: 2100000, guid: d50a40cb47ec86444b898d9121bbe0f7, type: 2} material: {fileID: 2100000, guid: d50a40cb47ec86444b898d9121bbe0f7, type: 2}
probability: 1 probability: 1
scale: 0.1 scale: 0.35
normalOffset: 0.04 normalOffset: 0.04
density: 5 density: 2.5
- enabled_: 1 - enabled_: 1
label: "\uC790\uAC08" label: "\uC790\uAC08"
mesh: {fileID: 2928966540353291585, guid: 7f1f11ac2c783374b876f8dc2bb7c7db, type: 3} mesh: {fileID: 2928966540353291585, guid: 7f1f11ac2c783374b876f8dc2bb7c7db, type: 3}
material: {fileID: 2100000, guid: 77d84437c100f274aad603219110357d, type: 2} material: {fileID: 2100000, guid: 77d84437c100f274aad603219110357d, type: 2}
probability: 1 probability: 1
scale: 0.35 scale: 0.7
normalOffset: 0.1 normalOffset: 0.1
density: 0.05 density: 0.05
positionVariance: 0.5 positionVariance: 0.5
scaleVariance: 0.2 scaleVariance: 0.2
grassEdgeBand: 0 grassEdgeBand: 0
cloudBandEnabled: 0
softDirtColor: {r: 0.71, g: 0.706, b: 0.549, a: 1} softDirtColor: {r: 0.71, g: 0.706, b: 0.549, a: 1}
seabedDepth: 0.6 seabedDepth: 0.6
seabedColor: {r: 0.86, g: 0.86, b: 0.72, a: 1} seabedColor: {r: 0.86, g: 0.86, b: 0.72, a: 1}
skirtGrassBand: 0.4 skirtGrassBand: 0.4
skirtInnerKeep: 0.5 skirtInnerKeep: 0.5
skirtTopDrop: 0.01 skirtTopDrop: 0.01
skirtCell: 0.5
skirtEdgeJitter: 0.15
randomYaw: 0 randomYaw: 0
edgeInset: 0.15 edgeInset: 0.15
roadMargin: 0.15 roadMargin: 0.15
@ -123,7 +126,7 @@ MonoBehaviour:
farmEdgeNoise: 0.4 farmEdgeNoise: 0.4
farmEdgeNoiseCell: 0.5 farmEdgeNoiseCell: 0.5
outlineRoundEnabled: 1 outlineRoundEnabled: 1
outlineRoundRadius: 3 outlineRoundRadius: 1.5
outlineOverhang: 0 outlineOverhang: 0
outlineRoundCell: 0.25 outlineRoundCell: 0.25
propMargin: 0.05 propMargin: 0.05

View File

@ -371,6 +371,12 @@ namespace WL.Look.Farm
"타일 윗면 위(+1 cm)에 모래를 얹는다. 풀 인스턴서 `outlineRoundRadius`(3)와 맞추면 풀잎도 같은 윤곽 안에만 깔린다. 0 = 각진 그대로(2026-09-16 PD 「각진 지형이 남는다」).")] "타일 윗면 위(+1 cm)에 모래를 얹는다. 풀 인스턴서 `outlineRoundRadius`(3)와 맞추면 풀잎도 같은 윤곽 안에만 깔린다. 0 = 각진 그대로(2026-09-16 PD 「각진 지형이 남는다」).")]
public float skirtGrassRound = 3f; public float skirtGrassRound = 3f;
[Tooltip("풀/모래 경계선을 굽이치게 하는 진폭(m). 직선 변을 파도처럼 들쭉날쭉하게(값 노이즈 · 결정적). 0 = 직선.")]
public float skirtWaveAmp = 0.8f;
[Tooltip("굽이의 크기(m) — 노이즈 격자 한 칸. 클수록 완만한 큰 굽이.")]
public float skirtWaveCell = 2.5f;
// ───────────────────────────────── §1 풀밭 // ───────────────────────────────── §1 풀밭
[Header("§1 — 섬 타일 위 풀밭 (데모와 같은 인스턴싱)")] [Header("§1 — 섬 타일 위 풀밭 (데모와 같은 인스턴싱)")]
[Tooltip("`edgeOnly_ 1` 정의를 경계에서 이만큼(m) 안쪽까지만 깐다. 0 = 띠 제한 없음. " + [Tooltip("`edgeOnly_ 1` 정의를 경계에서 이만큼(m) 안쪽까지만 깐다. 0 = 띠 제한 없음. " +

View File

@ -127,7 +127,8 @@ namespace WL.Look.Farm
+ cfg.skirtCell * 5.9f + cfg.skirtInnerKeep * 11.9f + cfg.skirtTopDrop * 17.7f + cfg.skirtUnderwaterMul * 31.7f + cfg.skirtCell * 5.9f + cfg.skirtInnerKeep * 11.9f + cfg.skirtTopDrop * 17.7f + cfg.skirtUnderwaterMul * 31.7f
+ cfg.skirtMaxGridNodes * 0.001f + topY * 37.3f + waterY * 41.1f + cfg.skirtMaxGridNodes * 0.001f + topY * 37.3f + waterY * 41.1f
+ grass.r * 101f + grass.g * 103f + grass.b * 107f + sand.r * 109f + sand.g * 113f + sand.b * 127f + grass.r * 101f + grass.g * 103f + grass.b * 107f + sand.r * 109f + sand.g * 113f + sand.b * 127f
+ cfg.skirtToonMode * 53f + cfg.skirtEdgeJitter * 59f + cfg.skirtSandShadowMul * 61f + cfg.skirtGrassRound * 67f; + cfg.skirtToonMode * 53f + cfg.skirtEdgeJitter * 59f + cfg.skirtSandShadowMul * 61f + cfg.skirtGrassRound * 67f
+ cfg.skirtWaveAmp * 71f + cfg.skirtWaveCell * 73f;
bool same = !_dirty && (_inst != null || _sandInst != null) && tileSig == _lastTileSig && Mathf.Abs(_lastValSig - valSig) < 0.0001f; bool same = !_dirty && (_inst != null || _sandInst != null) && tileSig == _lastTileSig && Mathf.Abs(_lastValSig - valSig) < 0.0001f;
if (same) { _mr.enabled = true; return; } if (same) { _mr.enabled = true; return; }
@ -204,6 +205,62 @@ namespace WL.Look.Farm
return cfg.shoreFoamFallbackY; return cfg.shoreFoamFallbackY;
} }
/// <summary>격자 정수 좌표 → 0~1 결정적 해시(굽이 노이즈용).</summary>
static float Hash01(int x, int z)
{
uint h = (uint)(x * 73856093) ^ (uint)(z * 19349663) ^ 0x9E3779B9u; h ^= h >> 13; h *= 0x5bd1e995u; h ^= h >> 15;
return (h & 0xffffu) / 65535f;
}
/// <summary>경계 굽이 — 값 노이즈(격자 skirtWaveCell · 겹선형 · 결정적) × 진폭. 직선 변을 파도처럼 굽이치게 한다.</summary>
float Wave(float x, float z)
{
if (cfg.skirtWaveAmp <= 0f || cfg.skirtWaveCell <= 0.1f) return 0f;
float qx = x / cfg.skirtWaveCell, qz = z / cfg.skirtWaveCell;
int gx = Mathf.FloorToInt(qx), gz = Mathf.FloorToInt(qz); float fx = qx - gx, fz = qz - gz;
fx = fx * fx * (3f - 2f * fx); fz = fz * fz * (3f - 2f * fz);
float v00 = Hash01(gx, gz), v10 = Hash01(gx + 1, gz), v01 = Hash01(gx, gz + 1), v11 = Hash01(gx + 1, gz + 1);
return (Mathf.Lerp(Mathf.Lerp(v00, v10, fx), Mathf.Lerp(v01, v11, fx), fz) - 0.5f) * 2f * cfg.skirtWaveAmp;
}
// ── 마칭 트라이앵글 — s 의 부호가 갈리는 삼각형을 등고선(s=0)으로 잘라 풀/모래 서브메시에 나눠 담는다 ──
float[] _s;
readonly Dictionary<long, int> _cut = new Dictionary<long, int>();
/// <summary>노드 na/nb 사이 s=0 지점의 정점(위치·색 보간 · 변마다 한 번만 생성).</summary>
int CrossVertex(int va, int na, int vb, int nb)
{
long key = na < nb ? ((long)na << 32) | (uint)nb : ((long)nb << 32) | (uint)na;
int idx;
if (_cut.TryGetValue(key, out idx)) return idx;
float sa = _s[na], sb = _s[nb];
float t = Mathf.Clamp01(sa / (sa - sb));
idx = _verts.Count;
_verts.Add(Vector3.LerpUnclamped(_verts[va], _verts[vb], t));
_cols.Add(Color32.Lerp(_cols[va], _cols[vb], t));
_cut[key] = idx;
return idx;
}
/// <summary>삼각형(정점 va,vb,vc · 노드 na,nb,nc · 감김 유지)을 s 부호로 가른다.</summary>
void CutTri(int va, int na, int vb, int nb, int vc, int nc)
{
bool ga = _s[na] < 0f, gb = _s[nb] < 0f, gc = _s[nc] < 0f;
if (ga == gb && gb == gc) { var l = ga ? _tris : _trisSand; l.Add(va); l.Add(vb); l.Add(vc); return; }
// 홀로 다른 꼭짓점 o 와 나머지 p,q (a→b→c 순환 순서 유지)
int vo, no, vp, np, vq, nq; bool go;
if (ga != gb && ga != gc) { vo = va; no = na; vp = vb; np = nb; vq = vc; nq = nc; go = ga; }
else if (gb != ga && gb != gc) { vo = vb; no = nb; vp = vc; np = nc; vq = va; nq = na; go = gb; }
else { vo = vc; no = nc; vp = va; np = na; vq = vb; nq = nb; go = gc; }
int m1 = CrossVertex(vo, no, vp, np);
int m2 = CrossVertex(vo, no, vq, nq);
var lo = go ? _tris : _trisSand; // o 쪽 조각(삼각형 1)
var lr = go ? _trisSand : _tris; // 나머지 조각(사각형 = 삼각형 2)
lo.Add(vo); lo.Add(m1); lo.Add(m2);
lr.Add(m1); lr.Add(vp); lr.Add(vq);
lr.Add(m1); lr.Add(vq); lr.Add(m2);
}
/// <summary>풀색 — 타일 윗면 머티리얼(Farm_IslandTop_Demo3)의 `_DiffuseColor`. 없으면 `_BaseColor`·`_Color` → 설정 대체값.</summary> /// <summary>풀색 — 타일 윗면 머티리얼(Farm_IslandTop_Demo3)의 `_DiffuseColor`. 없으면 `_BaseColor`·`_Color` → 설정 대체값.</summary>
Color GrassColor() Color GrassColor()
{ {
@ -383,11 +440,12 @@ namespace WL.Look.Farm
distR[n] = Mathf.Sqrt(best) - round; distR[n] = Mathf.Sqrt(best) - round;
} }
} }
keep = Mathf.Max(keep, round * 0.3f + cell); // 모서리가 파고드는 깊이 R(11/√2) ≈ 0.29R 까지 안쪽 노드 필요 // 🔴 실측(2026-09-16 09:5x): 모서리 필렛은 변을 따라 R 까지 이어지는 곡선 삼각형(깊이 R) — keep 이 R 보다 작으면 직사각형으로 잘린다.
keep = Mathf.Max(keep, round + cell);
} }
_verts.Clear(); _cols.Clear(); _tris.Clear(); _trisSand.Clear(); _verts.Clear(); _cols.Clear(); _tris.Clear(); _trisSand.Clear();
float jitter = Mathf.Max(0f, cfg.skirtEdgeJitter); float jitter = Mathf.Max(0f, cfg.skirtEdgeJitter); // 노드 단위 미세 흔들림(m) — 등고선을 살짝 거칠게
float outerKeep = width + cell * 2f; // 사각형 네 귀가 전부 있어야 하므로 바깥으로 두 칸 여유 float outerKeep = width + cell * 2f; // 사각형 네 귀가 전부 있어야 하므로 바깥으로 두 칸 여유
for (int j = 0; j < nz; j++) for (int j = 0; j < nz; j++)
{ {
@ -413,6 +471,20 @@ namespace WL.Look.Farm
} }
} }
// 2026-09-16 PD 「각진 경계 · 완만한 해변」 — 풀/모래 경계를 **격자 칸이 아니라 등고선으로 자른다**(칸 단위 계단 제거).
// 노드 값 s = 둥글린 거리 굽이(값 노이즈) 풀 띠 (+ 미세 흔들림). s<0 = 풀 · s≥0 = 모래. 삼각형이 두 쪽에 걸치면
// s=0 을 지나는 변 위에 정점을 보간해 만들고 세 조각으로 나눈다(마칭 트라이앵글).
if (_s == null || _s.Length < nx * nz) _s = new float[nx * nz];
_cut.Clear();
for (int j = 0; j < nz; j++)
for (int i = 0; i < nx; i++)
{
int n = j * nx + i;
if (index[n] < 0) { _s[n] = 0f; continue; }
float x = x0 + i * cell, z = z0 + j * cell;
_s[n] = distR[n] - Wave(x, z) - band + (Hash01(i * 3 + 7, j * 5 + 11) - 0.5f) * jitter;
}
for (int j = 0; j < nz - 1; j++) for (int j = 0; j < nz - 1; j++)
{ {
for (int i = 0; i < nx - 1; i++) for (int i = 0; i < nx - 1; i++)
@ -424,17 +496,16 @@ namespace WL.Look.Farm
float dm = Mathf.Min(Mathf.Min(dist[n], dist[n + 1]), Mathf.Min(dist[n + nx], dist[n + nx + 1])); float dm = Mathf.Min(Mathf.Min(dist[n], dist[n + 1]), Mathf.Min(dist[n + nx], dist[n + nx + 1]));
if (dm >= width) continue; if (dm >= width) continue;
// 바닥판(SpawnSeabed)과 같은 감김 — (x0,z0)→(x0,z1)→(x1,z1) 이 위를 향한다 // 바닥판(SpawnSeabed)과 같은 감김 — (x0,z0)→(x0,z1)→(x1,z1) 이 위를 향한다
var list = _tris;
if (_toon) if (_toon)
{ {
// Toon 모드 — 풀/모래는 서브메시로 가른다. 문턱 = 풀 띠 + 칸별 흔들림(들쭉날쭉한 물가 · 결정적) CutTri(ia, n, ic, n + nx, id, n + nx + 1);
uint h = (uint)(i * 73856093) ^ (uint)(j * 19349663); h ^= h >> 13; h *= 0x5bd1e995u; h ^= h >> 15; CutTri(ia, n, id, n + nx + 1, ib, n + 1);
float jit = ((h & 0xffffu) / 65535f - 0.5f) * jitter; }
float dq = (distR[n] + distR[n + 1] + distR[n + nx] + distR[n + nx + 1]) * 0.25f; // 둥글린 거리로 가른다 else
if (dq >= band + jit) list = _trisSand; {
_tris.Add(ia); _tris.Add(ic); _tris.Add(id);
_tris.Add(ia); _tris.Add(id); _tris.Add(ib);
} }
list.Add(ia); list.Add(ic); list.Add(id);
list.Add(ia); list.Add(id); list.Add(ib);
} }
} }