[WL-816zg] 스커트 Toon 2재질(풀=타일 재질 · 모래=복사본 · 구름·조명 동일 → 이음새 0) · 풀/모래 경계 칸별 흔들림 0.6 · 풀 띠 0.4 (#816)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-09-16 00:19:47 +09:00
parent b8d839fbb1
commit efa7a066ee
3 changed files with 55 additions and 11 deletions

View File

@ -110,7 +110,7 @@ MonoBehaviour:
softDirtColor: {r: 0.71, g: 0.706, b: 0.549, a: 1}
seabedDepth: 0.6
seabedColor: {r: 0.86, g: 0.86, b: 0.72, a: 1}
skirtGrassBand: 0
skirtGrassBand: 0.4
skirtInnerKeep: 0.5
skirtTopDrop: 0.01
randomYaw: 0

View File

@ -356,6 +356,17 @@ namespace WL.Look.Farm
[Tooltip("거리장·메시 격자 정점 수 상한. 넘으면 칸을 두 배씩 키운다(섬이 아무리 커져도 비용이 묶인다).")]
public int skirtMaxGridNodes = 400000;
[Tooltip("1 = 스커트를 타일과 **같은 Toon 재질**로 그린다(풀 = islandTopMaterial 그대로 · 모래 = 그 복사본에 모래색) → " +
"구름 그림자·조명이 타일과 똑같이 떨어져 이음새가 뜨지 않는다(2026-09-16 PD 「가장자리 경계가 붕 뜬다」 실측: 언릿 스커트가 구름 아래 타일보다 최대 15 % 밝았다). " +
"0 = 전용 언릿 정점색 셰이더(그라데이션은 부드럽지만 구름을 못 받는다).")]
public int skirtToonMode = 1;
[Tooltip("Toon 모드 모래 재질의 그늘색 배율(모래색 × 이 값) — 경사의 그늘진 면.")]
public float skirtSandShadowMul = 0.72f;
[Tooltip("Toon 모드에서 풀/모래 경계선을 칸마다 흔드는 폭(m) — 직선이 아니라 들쭉날쭉한 물가. 0 = 직선.")]
public float skirtEdgeJitter = 0.6f;
// ───────────────────────────────── §1 풀밭
[Header("§1 — 섬 타일 위 풀밭 (데모와 같은 인스턴싱)")]
[Tooltip("`edgeOnly_ 1` 정의를 경계에서 이만큼(m) 안쪽까지만 깐다. 0 = 띠 제한 없음. " +

View File

@ -59,6 +59,10 @@ namespace WL.Look.Farm
readonly List<Vector3> _verts = new List<Vector3>();
readonly List<Color32> _cols = new List<Color32>();
readonly List<int> _tris = new List<int>();
readonly List<int> _trisSand = new List<int>(); // Toon 모드 — 모래 서브메시
static readonly int IdShadowDiffuseColor = Shader.PropertyToID("_ShadowDiffuseColor");
Material _sandInst; // Toon 모드 — 타일 재질 복사본(모래색)
bool _toon;
// 마지막으로 만든 조건 — 같으면 다시 만들지 않는다(1초 폴링·재훑기에서 공짜로 돌게).
bool _dirty = true;
@ -71,6 +75,7 @@ namespace WL.Look.Farm
void OnDestroy()
{
if (_inst != null) Destroy(_inst);
if (_sandInst != null) Destroy(_sandInst);
if (_mesh != null) Destroy(_mesh);
}
@ -121,9 +126,10 @@ namespace WL.Look.Farm
float valSig = cfg.skirtWidth * 7.1f + cfg.skirtBottomY * 13.3f + cfg.skirtGrassBand * 3.7f + cfg.skirtSandBlend * 29.3f
+ 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
+ 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;
bool same = !_dirty && _inst != 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 (!EnsureMaterial()) { _mr.enabled = false; LastLog = "🔴 셰이더를 못 찾음(WL/Shore Skirt · URP Unlit 둘 다)"; return; }
@ -131,7 +137,8 @@ namespace WL.Look.Farm
BuildEdges(size, half);
float cell = BuildMesh(half, topY, waterY, grass, sand);
_mr.sharedMaterial = _inst;
if (_toon) _mr.sharedMaterials = new[] { cfg.islandTopMaterial, _sandInst }; // 풀 = 타일 재질 그대로 · 모래 = 복사본
else _mr.sharedMaterial = _inst;
_mr.enabled = true;
_lastTileSig = tileSig; _lastValSig = valSig; _dirty = false;
Rebuilds++;
@ -142,8 +149,8 @@ namespace WL.Look.Farm
+ (fixedTop ? "(고정 · 실측 " + (float.IsNaN(topMeasured) ? "없음" : topMeasured.ToString("F2"))
: "(실측 · 고정값 " + cfg.skirtTopY.ToString("F2")) + ")"
+ " → 바닥 " + cfg.skirtBottomY.ToString("F2") + " · 물 " + waterY.ToString("F2") + (waterFound ? "" : "(미발견·대체값)")
+ " · 격자 " + cell.ToString("F2") + " m · 셰이더 " + (_vertexColor ? ShaderName : "대체 URP Unlit(정점색 없음 · 모래 단색)")
+ " · 드로우콜 +1";
+ " · 격자 " + cell.ToString("F2") + " m · 셰이더 " + (_toon ? "Toon 2재질(풀=타일 · 모래=복사본 · 구름 동일)" : _vertexColor ? ShaderName : "대체 URP Unlit(정점색 없음 · 모래 단색)")
+ " · 드로우콜 +" + (_toon ? "2" : "1");
cfg.Log(LastLog);
}
@ -222,6 +229,20 @@ namespace WL.Look.Farm
/// <summary>전용 셰이더(정점색) → 없으면 URP Unlit 모래 단색 대체. 에셋은 만들지 않는다.</summary>
bool EnsureMaterial()
{
// 2026-09-16 Lead — Toon 모드: 타일과 같은 재질 계열이라 구름 그림자·조명이 똑같이 떨어진다(이음새 0).
if (cfg.skirtToonMode != 0 && cfg.islandTopMaterial != null)
{
_toon = true; _vertexColor = false;
if (_sandInst == null)
_sandInst = new Material(cfg.islandTopMaterial) { name = "WL_ShoreSkirt_Sand(runtime)", hideFlags = HideFlags.DontSave };
var sc = cfg.softDirtColor; sc.a = 1f;
float sm = Mathf.Clamp01(cfg.skirtSandShadowMul);
if (_sandInst.HasProperty(IdDiffuseColor)) _sandInst.SetColor(IdDiffuseColor, sc);
if (_sandInst.HasProperty(IdShadowDiffuseColor)) _sandInst.SetColor(IdShadowDiffuseColor, new Color(sc.r * sm, sc.g * sm, sc.b * sm, 1f));
if (_sandInst.HasProperty(IdBaseColor)) _sandInst.SetColor(IdBaseColor, sc);
return true;
}
_toon = false;
if (_inst != null) return true;
Shader sh = cfg.shoreSkirtMaterial != null ? cfg.shoreSkirtMaterial.shader : null;
@ -338,7 +359,8 @@ namespace WL.Look.Farm
for (int i = 0; i < nx; i++) dist[j * nx + i] = SignedDist(x0 + i * cell, z, half);
}
_verts.Clear(); _cols.Clear(); _tris.Clear();
_verts.Clear(); _cols.Clear(); _tris.Clear(); _trisSand.Clear();
float jitter = Mathf.Max(0f, cfg.skirtEdgeJitter);
float outerKeep = width + cell * 2f; // 사각형 네 귀가 전부 있어야 하므로 바깥으로 두 칸 여유
for (int j = 0; j < nz; j++)
{
@ -374,8 +396,17 @@ namespace WL.Look.Farm
float dm = Mathf.Min(Mathf.Min(dist[n], dist[n + 1]), Mathf.Min(dist[n + nx], dist[n + nx + 1]));
if (dm >= width) continue;
// 바닥판(SpawnSeabed)과 같은 감김 — (x0,z0)→(x0,z1)→(x1,z1) 이 위를 향한다
_tris.Add(ia); _tris.Add(ic); _tris.Add(id);
_tris.Add(ia); _tris.Add(id); _tris.Add(ib);
var list = _tris;
if (_toon)
{
// Toon 모드 — 풀/모래는 서브메시로 가른다. 문턱 = 풀 띠 + 칸별 흔들림(들쭉날쭉한 물가 · 결정적)
uint h = (uint)(i * 73856093) ^ (uint)(j * 19349663); h ^= h >> 13; h *= 0x5bd1e995u; h ^= h >> 15;
float jit = ((h & 0xffffu) / 65535f - 0.5f) * jitter;
float dq = (dist[n] + dist[n + 1] + dist[n + nx] + dist[n + nx + 1]) * 0.25f;
if (dq >= band + jit) list = _trisSand;
}
list.Add(ia); list.Add(ic); list.Add(id);
list.Add(ia); list.Add(id); list.Add(ib);
}
}
@ -383,12 +414,14 @@ namespace WL.Look.Farm
_mesh.indexFormat = _verts.Count > 65000 ? IndexFormat.UInt32 : IndexFormat.UInt16;
_mesh.SetVertices(_verts);
_mesh.SetColors(_cols);
_mesh.subMeshCount = _toon ? 2 : 1;
_mesh.SetTriangles(_tris, 0);
_mesh.RecalculateNormals(); // DepthNormals 패스용
if (_toon) _mesh.SetTriangles(_trisSand, 1);
_mesh.RecalculateNormals(); // DepthNormals 패스용 · Toon 조명용
_mesh.RecalculateBounds();
LastVertices = _verts.Count;
LastTriangles = _tris.Count / 3;
LastTriangles = (_tris.Count + _trisSand.Count) / 3;
return cell;
}
}