[WL] 섬 가장자리 각짐·경계 완화 — 거품 띠 v스크롤 제거(계단·사선 원인) · 물가선 유클리드 거리 띠 · 펄린 노이즈(값 노이즈 격자 무늬 제거) · 페이드 2 m 안쪽 30 % + 잔굽이 · SSAO 스코프 세션 표식 (#816)
- WLShoreFoam: 물가선 모드 = 높이 차 → 수평 유클리드 거리(물가선 교점 414) · 런타임 텍스처 알파 제곱 감쇠·최대 0.75 · 격자 0.25 · **v 방향 스크롤 0**(이분 실측: 이것이 「각진 부분 사이의 사선」) - WLDemoTerrain: GradNoise(펄린 · 16방향 표) 로 굽이·경사 폭·굴곡·둔덕 전부 교체 · 물가선 ±0.05~0.4 m 굴곡 0(notch) · 바다 바닥 굴곡 0 · 페이드 안쪽 비율 · 잔굽이 옥타브 · 픽셀 오차 값(0.05 허용) - WLSkyScope: SSAO 끔 표식을 SessionState 에 두어 Play 중 컴파일(도메인 리로드) 뒤에도 복원 · EnteredEditMode 복원 - 값: terrainBlend 2 · terrainBlendInward 0.3 · terrainEdgeNoise 0.35/1 · terrainSlopeVar 0.25/7 · terrainBumpAmp 0.18/3 · terrainSeabedBumpAmp 0 · shoreFoamUp 0.3 · shoreFoamDown 2 · shoreFoamCell 0.25 · softDirtNoise 0.6 · softDirtFeather 2 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e66bb838f6
commit
da6854461d
|
|
@ -75,12 +75,12 @@ MonoBehaviour:
|
||||||
waterFrom: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2}
|
waterFrom: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2}
|
||||||
waterTo: {fileID: 2100000, guid: 7c41d0e5b2a94f6d8e3517c0aa96b41d, type: 2}
|
waterTo: {fileID: 2100000, guid: 7c41d0e5b2a94f6d8e3517c0aa96b41d, type: 2}
|
||||||
shoreFoamEnabled: 1
|
shoreFoamEnabled: 1
|
||||||
shoreFoamUp: 0.08
|
shoreFoamUp: 0.3
|
||||||
shoreFoamDown: 0.25
|
shoreFoamDown: 2
|
||||||
shoreFoamMaterial: {fileID: 2100000, guid: d24a4c5ce16646c409631c112b57f2cf, type: 2}
|
shoreFoamMaterial: {fileID: 2100000, guid: d24a4c5ce16646c409631c112b57f2cf, type: 2}
|
||||||
shoreFoamWidth: 1.6
|
shoreFoamWidth: 1.6
|
||||||
shoreFoamInner: 0.5
|
shoreFoamInner: 0.5
|
||||||
shoreFoamCell: 0.5
|
shoreFoamCell: 0.25
|
||||||
shoreFoamY: 0.02
|
shoreFoamY: 0.02
|
||||||
shoreFoamFallbackY: -1
|
shoreFoamFallbackY: -1
|
||||||
shoreFoamTileLength: 6
|
shoreFoamTileLength: 6
|
||||||
|
|
@ -118,6 +118,8 @@ MonoBehaviour:
|
||||||
grassEdgeBand: 0
|
grassEdgeBand: 0
|
||||||
cloudBandEnabled: 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}
|
||||||
|
softDirtNoise: 0.6
|
||||||
|
softDirtFeather: 2
|
||||||
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
|
||||||
|
|
@ -408,14 +410,19 @@ MonoBehaviour:
|
||||||
terrainAlphaRes: 256
|
terrainAlphaRes: 256
|
||||||
terrainMargin: 8
|
terrainMargin: 8
|
||||||
terrainMaxSize: 128
|
terrainMaxSize: 128
|
||||||
terrainBlend: 1
|
terrainBlend: 2
|
||||||
|
terrainBlendInward: 0.3
|
||||||
|
terrainEdgeNoiseAmp: 0.35
|
||||||
|
terrainEdgeNoiseCell: 1
|
||||||
|
terrainPixelError: 1
|
||||||
|
terrainBumpWaterNotch: 0.4
|
||||||
terrainBottomY: -1.8
|
terrainBottomY: -1.8
|
||||||
terrainSlope: 5
|
terrainSlope: 5
|
||||||
terrainSlopeVar: 0.35
|
terrainSlopeVar: 0.25
|
||||||
terrainSlopeVarCell: 5
|
terrainSlopeVarCell: 7
|
||||||
terrainBumpAmp: 0.25
|
terrainBumpAmp: 0.18
|
||||||
terrainBumpCell: 2.5
|
terrainBumpCell: 3
|
||||||
terrainSeabedBumpAmp: 0.12
|
terrainSeabedBumpAmp: 0
|
||||||
terrainSeabedBumpCell: 3.5
|
terrainSeabedBumpCell: 3.5
|
||||||
terrainReliefAmp: 0.5
|
terrainReliefAmp: 0.5
|
||||||
terrainReliefCell: 4.5
|
terrainReliefCell: 4.5
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,8 @@ namespace WL.Look.Farm
|
||||||
+ cfg.terrainSlopeVar * 127f + cfg.terrainSlopeVarCell * 131f + cfg.terrainBumpAmp * 137f + cfg.terrainBumpCell * 139f
|
+ cfg.terrainSlopeVar * 127f + cfg.terrainSlopeVarCell * 131f + cfg.terrainBumpAmp * 137f + cfg.terrainBumpCell * 139f
|
||||||
+ cfg.terrainSeabedBumpAmp * 149f + cfg.terrainSeabedBumpCell * 151f
|
+ cfg.terrainSeabedBumpAmp * 149f + cfg.terrainSeabedBumpCell * 151f
|
||||||
+ cfg.terrainReliefAmp * 157f + cfg.terrainReliefCell * 163f + cfg.terrainReliefEdgeFade * 167f + cfg.terrainReliefFlatRadius * 173f
|
+ cfg.terrainReliefAmp * 157f + cfg.terrainReliefCell * 163f + cfg.terrainReliefEdgeFade * 167f + cfg.terrainReliefFlatRadius * 173f
|
||||||
+ cfg.terrainReliefFeather * 179f + cfg.terrainReliefCollider * 181f + CollectFlatSpots(cfg) * 191f;
|
+ cfg.terrainReliefFeather * 179f + cfg.terrainReliefCollider * 181f + CollectFlatSpots(cfg) * 191f
|
||||||
|
+ cfg.terrainBlendInward * 193f + cfg.terrainEdgeNoiseAmp * 197f + cfg.terrainEdgeNoiseCell * 199f + cfg.terrainBumpWaterNotch * 211f + WaterY() * 223f;
|
||||||
|
|
||||||
bool same = !_dirty && _data != null && _child != null && tileSig == _lastTileSig && Mathf.Abs(_lastValSig - valSig) < 0.0001f;
|
bool same = !_dirty && _data != null && _child != null && tileSig == _lastTileSig && Mathf.Abs(_lastValSig - valSig) < 0.0001f;
|
||||||
|
|
||||||
|
|
@ -154,7 +155,8 @@ namespace WL.Look.Farm
|
||||||
+ " · 윗면 y " + topY.ToString("F2") + (fixedTop ? "(고정)" : "(실측)") + " → 바닥 " + cfg.terrainBottomY.ToString("F2")
|
+ " · 윗면 y " + topY.ToString("F2") + (fixedTop ? "(고정)" : "(실측)") + " → 바닥 " + cfg.terrainBottomY.ToString("F2")
|
||||||
+ " · 경사 " + cfg.terrainSlope.ToString("F1") + " m(폭 ±" + (cfg.terrainSlopeVar * 100f).ToString("F0") + " % · 굴곡 ±" + cfg.terrainBumpAmp.ToString("F2") + " m · 바닥 굴곡 ±" + cfg.terrainSeabedBumpAmp.ToString("F2") + " m)"
|
+ " · 경사 " + cfg.terrainSlope.ToString("F1") + " m(폭 ±" + (cfg.terrainSlopeVar * 100f).ToString("F0") + " % · 굴곡 ±" + cfg.terrainBumpAmp.ToString("F2") + " m · 바닥 굴곡 ±" + cfg.terrainSeabedBumpAmp.ToString("F2") + " m)"
|
||||||
+ " · 둔덕 0~" + cfg.terrainReliefAmp.ToString("F2") + " m(격자 " + cfg.terrainReliefCell.ToString("F1") + " · 평평 자리 " + _flatX.Count + " · 콜라이더 " + (LastReliefTris > 0 ? LastReliefTris + "△" : "없음") + ")"
|
+ " · 둔덕 0~" + cfg.terrainReliefAmp.ToString("F2") + " m(격자 " + cfg.terrainReliefCell.ToString("F1") + " · 평평 자리 " + _flatX.Count + " · 콜라이더 " + (LastReliefTris > 0 ? LastReliefTris + "△" : "없음") + ")"
|
||||||
+ " · 잔디 경계 R " + cfg.skirtGrassRound.ToString("F1") + " 굽이 ±" + cfg.skirtWaveAmp.ToString("F1")
|
+ " · 잔디 경계 R " + cfg.skirtGrassRound.ToString("F1") + " 굽이 ±" + cfg.skirtWaveAmp.ToString("F1") + "+잔굽이 ±" + cfg.terrainEdgeNoiseAmp.ToString("F2")
|
||||||
|
+ " · 페이드 " + cfg.terrainBlend.ToString("F1") + " m(안쪽 " + (cfg.terrainBlendInward * 100f).ToString("F0") + " %) · 픽셀 오차 " + cfg.terrainPixelError.ToString("F0")
|
||||||
+ " · 밭 " + (zoneValid ? (zone.size.x).ToString("F1") + "×" + (zone.size.z).ToString("F1") + " m" : "미확정")
|
+ " · 밭 " + (zoneValid ? (zone.size.x).ToString("F1") + "×" + (zone.size.z).ToString("F1") + " m" : "미확정")
|
||||||
+ " · 굽기 " + LastBakeMs.ToString("F1") + " ms · 인스턴서 " + LastInstancerMs.ToString("F1") + " ms(" + instLog + ")"
|
+ " · 굽기 " + LastBakeMs.ToString("F1") + " ms · 인스턴서 " + LastInstancerMs.ToString("F1") + " ms(" + instLog + ")"
|
||||||
+ " · 드로우콜 터레인 1(+그림자 패스) + 인다이렉트 3 · 타일 윗면 숨김 " + _hiddenTops.Count;
|
+ " · 드로우콜 터레인 1(+그림자 패스) + 인다이렉트 3 · 타일 윗면 숨김 " + _hiddenTops.Count;
|
||||||
|
|
@ -421,22 +423,58 @@ namespace WL.Look.Farm
|
||||||
return (h & 0xffffu) / 65535f;
|
return (h & 0xffffu) / 65535f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>값 노이즈 −1~+1(격자 cell · 겹선형 · 결정적). seed 0 = 경계 굽이(WLShoreSkirt 와 같은 식) · 다른 seed 는 격자를 옮겨 독립된 무늬.</summary>
|
/// <summary>
|
||||||
static float ValueNoise(float x, float z, float cell, int seed)
|
/// 그라디언트(펄린) 노이즈 −1~+1(격자 cell · 5차 보간 · 결정적). seed 마다 격자를 옮겨 독립된 무늬.
|
||||||
|
/// 🔴 2026-09-16 PD 「가장자리가 각졌다」 실측: 값 노이즈(격자 값 겹선형)는 등고선이 **격자에 맞춘 둥근 네모**라 토온 단계·물 깊이 색이
|
||||||
|
/// 그 등고선을 그대로 드러내 계단·다각형이 됐다(거품·그림자·굴곡 끔 A/B 로 배제). 펄린은 등고선이 유기적 곡선.
|
||||||
|
/// </summary>
|
||||||
|
static float GradNoise(float x, float z, float cell, int seed)
|
||||||
{
|
{
|
||||||
if (cell <= 0.1f) return 0f;
|
if (cell <= 0.1f) return 0f;
|
||||||
float qx = x / cell, qz = z / cell;
|
float qx = x / cell, qz = z / cell;
|
||||||
int gx = Mathf.FloorToInt(qx) + seed * 1013, gz = Mathf.FloorToInt(qz) + seed * 2027; float fx = qx - Mathf.Floor(qx), fz = qz - Mathf.Floor(qz);
|
int gx = Mathf.FloorToInt(qx), gz = Mathf.FloorToInt(qz);
|
||||||
fx = fx * fx * (3f - 2f * fx); fz = fz * fz * (3f - 2f * fz);
|
float fx = qx - gx, fz = qz - gz;
|
||||||
float v00 = Hash01(gx, gz), v10 = Hash01(gx + 1, gz), v01 = Hash01(gx, gz + 1), v11 = Hash01(gx + 1, gz + 1);
|
int ox = seed * 1013, oz = seed * 2027;
|
||||||
return (Mathf.Lerp(Mathf.Lerp(v00, v10, fx), Mathf.Lerp(v01, v11, fx), fz) - 0.5f) * 2f;
|
float n00 = Grad(gx + ox, gz + oz, fx, fz), n10 = Grad(gx + 1 + ox, gz + oz, fx - 1f, fz);
|
||||||
|
float n01 = Grad(gx + ox, gz + 1 + oz, fx, fz - 1f), n11 = Grad(gx + 1 + ox, gz + 1 + oz, fx - 1f, fz - 1f);
|
||||||
|
float ux = fx * fx * fx * (fx * (fx * 6f - 15f) + 10f), uz = fz * fz * fz * (fz * (fz * 6f - 15f) + 10f);
|
||||||
|
float v = Mathf.Lerp(Mathf.Lerp(n00, n10, ux), Mathf.Lerp(n01, n11, ux), uz);
|
||||||
|
return Mathf.Clamp(v * 1.6f, -1f, 1f); // 2D 펄린 범위 ≈ ±0.7 → ±1 근처로
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>경계 굽이 — 값 노이즈(격자 skirtWaveCell) × 진폭. 직선 변을 파도처럼 굽이치게 한다(WLShoreSkirt 와 같은 식).</summary>
|
// 16방향 단위 벡터 표(해시로 고른다 · cos/sin 호출 없음 — 굽기 133 → ≈100 ms)
|
||||||
|
static readonly float[] s_gx = { 1f, 0.9239f, 0.7071f, 0.3827f, 0f, -0.3827f, -0.7071f, -0.9239f, -1f, -0.9239f, -0.7071f, -0.3827f, 0f, 0.3827f, 0.7071f, 0.9239f };
|
||||||
|
static readonly float[] s_gz = { 0f, 0.3827f, 0.7071f, 0.9239f, 1f, 0.9239f, 0.7071f, 0.3827f, 0f, -0.3827f, -0.7071f, -0.9239f, -1f, -0.9239f, -0.7071f, -0.3827f };
|
||||||
|
static float Grad(int ix, int iz, float dx, float dz)
|
||||||
|
{
|
||||||
|
int k = (int)(Hash01(ix, iz) * 15.999f);
|
||||||
|
return s_gx[k] * dx + s_gz[k] * dz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>경계 굽이 — 큰 굽이(격자 skirtWaveCell × skirtWaveAmp · WLShoreSkirt 와 같은 식) + 잔 굽이(terrainEdgeNoiseCell × Amp · 붓 자국). 직선 변을 굽이치게 한다.</summary>
|
||||||
float Wave(float x, float z)
|
float Wave(float x, float z)
|
||||||
{
|
{
|
||||||
if (cfg.skirtWaveAmp <= 0f || cfg.skirtWaveCell <= 0.1f) return 0f;
|
float w = 0f;
|
||||||
return ValueNoise(x, z, cfg.skirtWaveCell, 0) * cfg.skirtWaveAmp;
|
if (cfg.skirtWaveAmp > 0f && cfg.skirtWaveCell > 0.1f) w += GradNoise(x, z, cfg.skirtWaveCell, 0) * cfg.skirtWaveAmp;
|
||||||
|
if (cfg.terrainEdgeNoiseAmp > 0f && cfg.terrainEdgeNoiseCell > 0.1f) w += GradNoise(x, z, cfg.terrainEdgeNoiseCell, 5) * cfg.terrainEdgeNoiseAmp;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>물 평면 높이 — 섬 씬의 `Water`(루트/자식) · 없으면 shoreFoamFallbackY.</summary>
|
||||||
|
float WaterY()
|
||||||
|
{
|
||||||
|
var scene = gameObject.scene;
|
||||||
|
if (scene.IsValid())
|
||||||
|
{
|
||||||
|
var roots = scene.GetRootGameObjects();
|
||||||
|
for (int i = 0; i < roots.Length; i++)
|
||||||
|
{
|
||||||
|
if (roots[i].name == "Water") return roots[i].transform.position.y;
|
||||||
|
var t = roots[i].transform.Find("Water");
|
||||||
|
if (t != null) return t.position.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cfg.shoreFoamFallbackY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -539,7 +577,9 @@ namespace WL.Look.Farm
|
||||||
BuildEdges(size, half);
|
BuildEdges(size, half);
|
||||||
float round = Mathf.Max(0f, cfg.skirtGrassRound);
|
float round = Mathf.Max(0f, cfg.skirtGrassRound);
|
||||||
float cellA = Mathf.Max(w / aRes, l / aRes);
|
float cellA = Mathf.Max(w / aRes, l / aRes);
|
||||||
float far = slope * (1f + slopeVar) + seaFade + blend + Mathf.Max(0f, cfg.skirtWaveAmp) + cellA * 2f; // 이보다 먼 바깥은 둥글림이 높이·스플랫에 영향 없음
|
float far = slope * (1f + slopeVar) + seaFade + blend + Mathf.Max(0f, cfg.skirtWaveAmp) + Mathf.Max(0f, cfg.terrainEdgeNoiseAmp) + cellA * 2f; // 이보다 먼 바깥은 둥글림이 높이·스플랫에 영향 없음
|
||||||
|
float blendIn = blend * Mathf.Clamp(cfg.terrainBlendInward, 0f, 0.5f); // 페이드가 잔디 경계 안쪽으로 들어오는 길이
|
||||||
|
float waterY = WaterY(), notch = Mathf.Max(0f, cfg.terrainBumpWaterNotch);
|
||||||
// 🔴 2026-09-16 Lead 실측: 링 0.125 m 간격(632점) × 스플랫 65k 텍셀 = 굽기 851 ms → 상인 설치 때 화면이 멎어 「지형이 사라졌다 보임」.
|
// 🔴 2026-09-16 Lead 실측: 링 0.125 m 간격(632점) × 스플랫 65k 텍셀 = 굽기 851 ms → 상인 설치 때 화면이 멎어 「지형이 사라졌다 보임」.
|
||||||
// 링은 0.5 m 간격(≈1/4)으로, 경계 거리 s 는 높이맵 격자(0.25 m)에서 한 번만 계산해 스플랫은 겹선형으로 읽는다(blend 1 m 에 충분).
|
// 링은 0.5 m 간격(≈1/4)으로, 경계 거리 s 는 높이맵 격자(0.25 m)에서 한 번만 계산해 스플랫은 겹선형으로 읽는다(blend 1 m 에 충분).
|
||||||
GatherRing(size, half, minX, minZ, maxX, maxZ, Mathf.Max(0.5f, Mathf.Min(w / aRes, l / aRes)), round);
|
GatherRing(size, half, minX, minZ, maxX, maxZ, Mathf.Max(0.5f, Mathf.Min(w / aRes, l / aRes)), round);
|
||||||
|
|
@ -585,26 +625,32 @@ namespace WL.Look.Farm
|
||||||
if (reliefAmp > 0f)
|
if (reliefAmp > 0f)
|
||||||
{
|
{
|
||||||
float env = ReliefEnv(x, z, s, reliefFade, reliefFeather, zoneValid, zcx, zcz, zhx, zhz, zrad, znoise, zcell);
|
float env = ReliefEnv(x, z, s, reliefFade, reliefFeather, zoneValid, zcx, zcz, zhx, zhz, zrad, znoise, zcell);
|
||||||
if (env > 0f) y += env * reliefAmp * (ValueNoise(x, z, reliefCell, 4) * 0.5f + 0.5f);
|
if (env > 0f) y += env * reliefAmp * (GradNoise(x, z, reliefCell, 4) * 0.5f + 0.5f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 경사 폭을 자리마다 ±slopeVar 로 흔든다 → 해안이 넓어졌다 좁아졌다 한다(굽이와 별개의 무늬).
|
// 경사 폭을 자리마다 ±slopeVar 로 흔든다 → 해안이 넓어졌다 좁아졌다 한다(굽이와 별개의 무늬).
|
||||||
float slopeL = slope * (1f + slopeVar * ValueNoise(x, z, varCell, 1));
|
float slopeL = slope * (1f + slopeVar * GradNoise(x, z, varCell, 1));
|
||||||
if (slopeL < 0.5f) slopeL = 0.5f;
|
if (slopeL < 0.5f) slopeL = 0.5f;
|
||||||
float t = s / slopeL;
|
float t = s / slopeL;
|
||||||
if (t < 1f)
|
if (t < 1f)
|
||||||
{
|
{
|
||||||
y = Mathf.Lerp(topY, bottom, SmoothStep01(t));
|
y = Mathf.Lerp(topY, bottom, SmoothStep01(t));
|
||||||
// 능선·골 — sin(πt) 로 양 끝(윗면·바닥)에서 0 → 걷는 면 위로 솟지 않고 바닥과도 이어진다.
|
// 능선·골 — sin(πt) 로 양 끝(윗면·바닥)에서 0 → 걷는 면 위로 솟지 않고 바닥과도 이어진다.
|
||||||
if (bumpAmp > 0f) y += Mathf.Sin(t * Mathf.PI) * bumpAmp * ValueNoise(x, z, bumpCell, 2);
|
// 🔴 물가선(y = 물 높이) 근처에서는 0 — 굴곡이 물가선에 걸치면 선이 지그재그·계단이 된다(2026-09-16 PD 「각졌다」 실측).
|
||||||
|
if (bumpAmp > 0f)
|
||||||
|
{
|
||||||
|
float env = Mathf.Sin(t * Mathf.PI);
|
||||||
|
if (notch > 0f) env *= SmoothStep01((Mathf.Abs(y - waterY) - 0.05f) / notch);
|
||||||
|
y += env * bumpAmp * GradNoise(x, z, bumpCell, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
y = bottom;
|
y = bottom;
|
||||||
// 바다 바닥 — 경사 끝에서 seaFade 에 걸쳐 굴곡이 자란다(끝점 연속).
|
// 바다 바닥 — 경사 끝에서 seaFade 에 걸쳐 굴곡이 자란다(끝점 연속).
|
||||||
if (seaAmp > 0f) y += SmoothStep01((s - slopeL) / seaFade) * seaAmp * ValueNoise(x, z, seaCell, 3);
|
if (seaAmp > 0f) y += SmoothStep01((s - slopeL) / seaFade) * seaAmp * GradNoise(x, z, seaCell, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
heights[j, i] = Mathf.Clamp01((y - baseY) / span);
|
heights[j, i] = Mathf.Clamp01((y - baseY) / span);
|
||||||
|
|
@ -624,7 +670,7 @@ namespace WL.Look.Farm
|
||||||
int gi = (int)gx, gj = (int)gz; float tx = gx - gi, tz = gz - gj;
|
int gi = (int)gx, gj = (int)gz; float tx = gx - gi, tz = gz - gj;
|
||||||
float s00 = sGrid[gj * hRes + gi], s10 = sGrid[gj * hRes + gi + 1], s01 = sGrid[(gj + 1) * hRes + gi], s11 = sGrid[(gj + 1) * hRes + gi + 1];
|
float s00 = sGrid[gj * hRes + gi], s10 = sGrid[gj * hRes + gi + 1], s01 = sGrid[(gj + 1) * hRes + gi], s11 = sGrid[(gj + 1) * hRes + gi + 1];
|
||||||
float s = Mathf.Lerp(Mathf.Lerp(s00, s10, tx), Mathf.Lerp(s01, s11, tx), tz);
|
float s = Mathf.Lerp(Mathf.Lerp(s00, s10, tx), Mathf.Lerp(s01, s11, tx), tz);
|
||||||
float grass = 1f - SmoothStep01(s / blend); // s ≤ 0 잔디 1 → s ≥ blend 잔디 0(모래)
|
float grass = 1f - SmoothStep01((s + blendIn) / blend); // s ≤ −blendIn 잔디 1 → s ≥ blend − blendIn 잔디 0(모래) · 풀잎 선(50 %) = blend/2 − blendIn
|
||||||
float dirt = 0f;
|
float dirt = 0f;
|
||||||
if (zoneValid && Mathf.Abs(x - zcx) <= zhx + znoise + feather && Mathf.Abs(z - zcz) <= zhz + znoise + feather)
|
if (zoneValid && Mathf.Abs(x - zcx) <= zhx + znoise + feather && Mathf.Abs(z - zcz) <= zhz + znoise + feather)
|
||||||
{
|
{
|
||||||
|
|
@ -687,6 +733,9 @@ namespace WL.Look.Farm
|
||||||
if (_terrain.materialTemplate != tmat) _terrain.materialTemplate = tmat;
|
if (_terrain.materialTemplate != tmat) _terrain.materialTemplate = tmat;
|
||||||
if (_terrain.drawInstanced) _terrain.drawInstanced = false; // Demo.unity 1228 m_DrawInstanced 0 · 그 외 Terrain 값은 데모 = 컴포넌트 기본값
|
if (_terrain.drawInstanced) _terrain.drawInstanced = false; // Demo.unity 1228 m_DrawInstanced 0 · 그 외 Terrain 값은 데모 = 컴포넌트 기본값
|
||||||
if (_terrain.allowAutoConnect) _terrain.allowAutoConnect = false;
|
if (_terrain.allowAutoConnect) _terrain.allowAutoConnect = false;
|
||||||
|
// 🔴 2026-09-16 PD 「가장자리가 각졌다」 — 데모(5)와 같은 픽셀 오차는 200 m 언덕용. 32 m 섬을 12 m 에서 보면 경사가 큰 삼각형으로 뭉개져 토온 음영이 각진 면이 된다 → 1.
|
||||||
|
float pe = Mathf.Clamp(cfg.terrainPixelError, 0.05f, 200f); // API 는 1 미만도 받는다(실측 0.05 반영) — 인스펙터 슬라이더 하한과 다름
|
||||||
|
if (!Mathf.Approximately(_terrain.heightmapPixelError, pe)) _terrain.heightmapPixelError = pe;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -257,8 +257,8 @@ namespace WL.Look.Farm
|
||||||
[Tooltip("섬 안쪽으로 겹쳐 들어가는 길이(m) — 물가에 틈이 생기지 않게. 섬 벽이 가려 준다.")]
|
[Tooltip("섬 안쪽으로 겹쳐 들어가는 길이(m) — 물가에 틈이 생기지 않게. 섬 벽이 가려 준다.")]
|
||||||
public float shoreFoamInner = 0.5f;
|
public float shoreFoamInner = 0.5f;
|
||||||
|
|
||||||
[Tooltip("띠 메시의 격자 간격(m). 크면 싸고 거칠다. 비용이 문제면 이것부터 키운다.")]
|
[Tooltip("띠 메시의 격자 간격(m). 크면 싸고 거칠다. 비용이 문제면 이것부터 키운다. 2026-09-16 물가선 모드: 0.5 → 0.25(경계 계단 완화 · 삼각형 ≈ 3천).")]
|
||||||
public float shoreFoamCell = 0.5f;
|
public float shoreFoamCell = 0.25f;
|
||||||
|
|
||||||
[Tooltip("물 표면보다 얼마나 위에 깔 것인가(m). 너무 작으면 z-fighting.")]
|
[Tooltip("물 표면보다 얼마나 위에 깔 것인가(m). 너무 작으면 z-fighting.")]
|
||||||
public float shoreFoamY = 0.02f;
|
public float shoreFoamY = 0.02f;
|
||||||
|
|
@ -273,11 +273,13 @@ namespace WL.Look.Farm
|
||||||
public float shoreFoamScroll = 0.06f;
|
public float shoreFoamScroll = 0.06f;
|
||||||
|
|
||||||
// ── 2026-09-16 PD 「물이 섬 경계에 닿을 때 자연스러운 물보라가 안 보인다」 — 데모 터레인 모드에서는 타일 발자국 대신 **터레인 물가선**(지형 높이 = 물 높이)을 따른다
|
// ── 2026-09-16 PD 「물이 섬 경계에 닿을 때 자연스러운 물보라가 안 보인다」 — 데모 터레인 모드에서는 타일 발자국 대신 **터레인 물가선**(지형 높이 = 물 높이)을 따른다
|
||||||
[Tooltip("데모 터레인 모드의 물가 띠 — 물가선에서 **모래 쪽**으로 지형이 물보다 이만큼(m · 높이) 높은 곳까지 거품이 올라온다(완만한 곳은 넓게 · 가파른 곳은 좁게 = 자연스러운 물보라).")]
|
[Tooltip("데모 터레인 모드의 물가 띠 — 물가선에서 **모래 쪽**으로 이만큼(m · 수평 거리)까지 거품이 올라온다. " +
|
||||||
public float shoreFoamUp = 0.08f;
|
"2026-09-16 PD 「가장자리가 각졌다」: 높이 차로 정하던 띠가 평평한 곳에서 흰 계단 덩어리가 됐다 → 수평 거리(어디서나 같은 폭)로 바꿈.")]
|
||||||
|
public float shoreFoamUp = 0.3f;
|
||||||
|
|
||||||
[Tooltip("데모 터레인 모드의 물가 띠 — 물가선에서 **바다 쪽**으로 수심 이만큼(m)까지 거품이 퍼진다.")]
|
[Tooltip("데모 터레인 모드의 물가 띠 — 물가선에서 **바다 쪽**으로 이만큼(m · 수평 거리)까지 메시가 뻗는다. 보이는 거품은 그 60 % 안(알파 제곱 감쇠)이고 끝은 투명 → 메시 경계가 안 보인다. " +
|
||||||
public float shoreFoamDown = 0.25f;
|
"2026-09-16 실측: 1.2 m 에 알파가 끝까지 남아 격자 칸 경계가 네모 계단으로 보였다 → 2 m.")]
|
||||||
|
public float shoreFoamDown = 2f;
|
||||||
|
|
||||||
// ───────────────────────────────── §816zf 부드러운 흙 데칼
|
// ───────────────────────────────── §816zf 부드러운 흙 데칼
|
||||||
[Header("§816zf — 흙 자리를 데모처럼 「둥글게 번지게」 (PD 2026-09-15)")]
|
[Header("§816zf — 흙 자리를 데모처럼 「둥글게 번지게」 (PD 2026-09-15)")]
|
||||||
|
|
@ -440,8 +442,26 @@ namespace WL.Look.Farm
|
||||||
[Tooltip("터레인 한 변 상한(m). 발자국 + 여백이 이보다 크면 발자국 중심 기준으로 자른다.")]
|
[Tooltip("터레인 한 변 상한(m). 발자국 + 여백이 이보다 크면 발자국 중심 기준으로 자른다.")]
|
||||||
public float terrainMaxSize = 128f;
|
public float terrainMaxSize = 128f;
|
||||||
|
|
||||||
[Tooltip("스플랫 잔디→모래 페이드 폭(m). 잔디 경계(둥글림+굽이)에서 바깥으로 이만큼에 걸쳐 1→0.")]
|
[Tooltip("스플랫 잔디→모래 페이드 폭(m). 잔디 경계(둥글림+굽이)에서 이만큼에 걸쳐 1→0. " +
|
||||||
public float terrainBlend = 1.0f;
|
"2026-09-16 PD 「흙·풀 경계가 너무 드로잉된 느낌」 → 1 → 2(데모의 붓으로 칠한 듯한 넓은 경계).")]
|
||||||
|
public float terrainBlend = 2.0f;
|
||||||
|
|
||||||
|
[Tooltip("페이드 폭 중 잔디 경계 **안쪽**으로 들어오는 비율(0~0.5). 0.3 = 안쪽 0.6 m 부터 모래색이 스미기 시작 · 풀잎이 멈추는 선(잔디 50 %)은 경계 바깥 0.4 m 로 거의 그대로. " +
|
||||||
|
"0 = 예전처럼 경계 바깥으로만 페이드(풀잎 선이 폭/2 만큼 밖으로 밀린다).")]
|
||||||
|
[Range(0f, 0.5f)] public float terrainBlendInward = 0.3f;
|
||||||
|
|
||||||
|
[Tooltip("잔디 경계에 얹는 **잔 굽이**(m · 값 노이즈 2번째 옥타브). 큰 굽이(skirtWaveAmp/Cell)만 있으면 매끈한 곡선이라 「그려 넣은 선」처럼 보인다 → 붓 자국처럼 잘게 흔든다. 0 = 없음.")]
|
||||||
|
public float terrainEdgeNoiseAmp = 0.35f;
|
||||||
|
|
||||||
|
[Tooltip("잔 굽이 노이즈 격자(m).")]
|
||||||
|
public float terrainEdgeNoiseCell = 1.0f;
|
||||||
|
|
||||||
|
[Tooltip("지형 렌더 LOD 허용 오차(픽셀 · Unity Terrain heightmapPixelError · 기본 5). 2026-09-16 PD 「가장자리가 각졌다」: 5 면 12 m 카메라에서 해변 경사가 큰 삼각형으로 뭉개져 토온 음영이 각진 면으로 보였다 → 1.")]
|
||||||
|
public float terrainPixelError = 1f;
|
||||||
|
|
||||||
|
[Tooltip("경사면 굴곡(terrainBumpAmp)을 **물가선 근처에서는 0** 으로 죽이는 높이 폭(m). 물 높이 ±0.05 m 에서 0 → 이 값에서 100 %. " +
|
||||||
|
"굴곡이 물가선에 걸치면 물가선이 지그재그·계단이 되고 거품 띠가 덩어리진다(2026-09-16 실측). 0 = 안 죽임.")]
|
||||||
|
public float terrainBumpWaterNotch = 0.4f;
|
||||||
|
|
||||||
[Tooltip("경사 바깥 끝 = 바다 바닥 높이(월드 y · m). 물(−1.0) 아래.")]
|
[Tooltip("경사 바깥 끝 = 바다 바닥 높이(월드 y · m). 물(−1.0) 아래.")]
|
||||||
public float terrainBottomY = -1.8f;
|
public float terrainBottomY = -1.8f;
|
||||||
|
|
@ -452,19 +472,20 @@ namespace WL.Look.Farm
|
||||||
|
|
||||||
// ── 2026-09-16 PD 「섬 가장자리와 바다로 이어지는 부분을 자연스러운 경사면으로 · 굴곡이 보이게」
|
// ── 2026-09-16 PD 「섬 가장자리와 바다로 이어지는 부분을 자연스러운 경사면으로 · 굴곡이 보이게」
|
||||||
[Tooltip("경사 폭을 자리마다 이 비율(±)로 흔든다(값 노이즈 · 격자 terrainSlopeVarCell). 0.35 = 폭 ×0.65~1.35 → 해안이 넓어졌다 좁아졌다 한다. 0 = 균일.")]
|
[Tooltip("경사 폭을 자리마다 이 비율(±)로 흔든다(값 노이즈 · 격자 terrainSlopeVarCell). 0.35 = 폭 ×0.65~1.35 → 해안이 넓어졌다 좁아졌다 한다. 0 = 균일.")]
|
||||||
[Range(0f, 0.8f)] public float terrainSlopeVar = 0.35f;
|
[Range(0f, 0.8f)] public float terrainSlopeVar = 0.25f; // 2026-09-16 「각짐」 완화: 0.35/5 m → 0.25/7 m(옆으로 꺾이는 주름이 완만)
|
||||||
|
|
||||||
[Tooltip("경사 폭 흔들기 노이즈 격자(m).")]
|
[Tooltip("경사 폭 흔들기 노이즈 격자(m).")]
|
||||||
public float terrainSlopeVarCell = 5f;
|
public float terrainSlopeVarCell = 7f;
|
||||||
|
|
||||||
[Tooltip("경사면 위에 얹는 능선·골 굴곡 진폭(m · 경사 양 끝에서 0 이라 윗면·바닥과 이어진다). 데모 지형처럼 법선 음영이 굴곡을 드러낸다. 0 = 매끈한 smoothstep.")]
|
[Tooltip("경사면 위에 얹는 능선·골 굴곡 진폭(m · 경사 양 끝·물가선에서 0). 데모 지형처럼 법선 음영이 굴곡을 드러낸다. 0 = 매끈한 smoothstep.")]
|
||||||
public float terrainBumpAmp = 0.25f;
|
public float terrainBumpAmp = 0.18f;
|
||||||
|
|
||||||
[Tooltip("굴곡 노이즈 격자(m).")]
|
[Tooltip("굴곡 노이즈 격자(m).")]
|
||||||
public float terrainBumpCell = 2.5f;
|
public float terrainBumpCell = 3f;
|
||||||
|
|
||||||
[Tooltip("경사 밖 바다 바닥의 완만한 굴곡 진폭(m · 경사 끝에서 2 m 에 걸쳐 서서히 커진다). 물이 깊이로 색을 정하므로 얕은 곳/깊은 곳 얼룩으로 보인다. 0 = 평평.")]
|
[Tooltip("경사 밖 바다 바닥의 완만한 굴곡 진폭(m · 경사 끝에서 2 m 에 걸쳐 서서히 커진다). " +
|
||||||
public float terrainSeabedBumpAmp = 0.12f;
|
"🔴 2026-09-16 실측: 물 셰이더가 깊이 색을 계단으로 양자화해 바닥 굴곡의 등고선이 흰 계단 덩어리로 보였다 → 0(평평). 켜려면 0.05 이하 권장.")]
|
||||||
|
public float terrainSeabedBumpAmp = 0f;
|
||||||
|
|
||||||
[Tooltip("바다 바닥 굴곡 격자(m).")]
|
[Tooltip("바다 바닥 굴곡 격자(m).")]
|
||||||
public float terrainSeabedBumpCell = 3.5f;
|
public float terrainSeabedBumpCell = 3.5f;
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,10 @@ namespace WL.Look.Farm
|
||||||
if (sp == 0f) return;
|
if (sp == 0f) return;
|
||||||
_scroll += Time.deltaTime * sp;
|
_scroll += Time.deltaTime * sp;
|
||||||
if (_scroll > 1f) _scroll -= 1f;
|
if (_scroll > 1f) _scroll -= 1f;
|
||||||
_inst.SetTextureOffset(IdBase, new Vector2(_scroll, _scroll * 0.37f));
|
// 🔴 2026-09-16 PD 「각진 부분 사이의 사선」 이분 실측: v(띠 가로) 방향 스크롤이 물가선 알파 프로파일을 띠 밖으로 밀어 모래 위에 계단·사선
|
||||||
|
// 모양의 흰 덩어리를 만들었다(스크롤 0 이면 즉시 매끈). 물가선 모드에서는 **해안 방향(u)만** 흐른다. 네모 띠(816j2)는 예전 그대로.
|
||||||
|
bool waterline = cfg.demoTerrainEnabled != 0;
|
||||||
|
_inst.SetTextureOffset(IdBase, new Vector2(_scroll, waterline ? 0f : _scroll * 0.37f));
|
||||||
}
|
}
|
||||||
Material _inst;
|
Material _inst;
|
||||||
static readonly int IdBase = Shader.PropertyToID("_BaseMap");
|
static readonly int IdBase = Shader.PropertyToID("_BaseMap");
|
||||||
|
|
@ -202,7 +205,7 @@ namespace WL.Look.Farm
|
||||||
// 띠 = f ∈ [−shoreFoamUp, +shoreFoamDown] 에 걸치는 칸. 정점 높이 = max(지형, 물) + shoreFoamY → 모래 위에서는 모래를 덮고 물 위에서는 물을 덮는다.
|
// 띠 = f ∈ [−shoreFoamUp, +shoreFoamDown] 에 걸치는 칸. 정점 높이 = max(지형, 물) + shoreFoamY → 모래 위에서는 모래를 덮고 물 위에서는 물을 덮는다.
|
||||||
// UV v = 0(모래 끝) → 1(바다 끝) — 기존 띠 텍스처와 같은 방향. u = 해안 방향 반복(shoreFoamTileLength).
|
// UV v = 0(모래 끝) → 1(바다 끝) — 기존 띠 텍스처와 같은 방향. u = 해안 방향 반복(shoreFoamTileLength).
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
public static int TerrainNodes;
|
public static int TerrainNodes, WaterlinePoints;
|
||||||
|
|
||||||
void BuildFromTerrain(Terrain terrain)
|
void BuildFromTerrain(Terrain terrain)
|
||||||
{
|
{
|
||||||
|
|
@ -222,7 +225,7 @@ namespace WL.Look.Farm
|
||||||
float stepX = sz.x / (nx - 1), stepZ = sz.z / (nz - 1);
|
float stepX = sz.x / (nx - 1), stepZ = sz.z / (nz - 1);
|
||||||
|
|
||||||
var hgt = new float[nx * nz];
|
var hgt = new float[nx * nz];
|
||||||
var f = new float[nx * nz];
|
var under = new bool[nx * nz];
|
||||||
var probe = new Vector3();
|
var probe = new Vector3();
|
||||||
for (int j = 0; j < nz; j++)
|
for (int j = 0; j < nz; j++)
|
||||||
{
|
{
|
||||||
|
|
@ -232,10 +235,46 @@ namespace WL.Look.Farm
|
||||||
probe.x = tp.x + i * stepX;
|
probe.x = tp.x + i * stepX;
|
||||||
float h = terrain.SampleHeight(probe) + tp.y;
|
float h = terrain.SampleHeight(probe) + tp.y;
|
||||||
int k = j * nx + i;
|
int k = j * nx + i;
|
||||||
hgt[k] = h; f[k] = waterY - h;
|
hgt[k] = h; under[k] = h < waterY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TerrainNodes = nx * nz;
|
TerrainNodes = nx * nz;
|
||||||
|
// 🔴 2026-09-16 PD 「섬 가장자리가 각졌다」 실측: 높이 차(f)로 띠를 정하면 물 높이 근처가 평평한 곳에서 띠가 통째로 넓어져
|
||||||
|
// 흰 계단 덩어리가 됐다. → 물가선까지의 **수평 거리**(격자 챔퍼 · + 물 쪽 / − 모래 쪽)로 띠를 정한다 = 어디서나 같은 폭.
|
||||||
|
var dToWater = Chamfer(under, nx, nz, cell, false); // 모래 노드 → 가장 가까운 물속 노드까지(m · 거친 선별용)
|
||||||
|
var dToSand = Chamfer(under, nx, nz, cell, true); // 물속 노드 → 가장 가까운 모래 노드까지(m · 거친 선별용)
|
||||||
|
// 🔴 2026-09-16 PD 「각진 부분 사이의 사선」 실측: 챔퍼 거리의 등고선은 축 방향 + 45° 사선의 **팔각형**이라 띠 가장자리가 그 모양 그대로
|
||||||
|
// 계단·사선으로 보였다(물 셰이더 교체·높이맵 257 로도 불변 → 거품 띠가 원인). 물가선 교점(격자 변에서 물 높이를 가로지르는 점)을 모아
|
||||||
|
// 띠 근처 노드만 **진짜 유클리드 거리**로 다시 잰다 → 등고선이 매끈한 곡선.
|
||||||
|
var wlx = new List<float>(1024); var wlz = new List<float>(1024);
|
||||||
|
for (int j = 0; j < nz; j++)
|
||||||
|
for (int i = 0; i < nx; i++)
|
||||||
|
{
|
||||||
|
int k = j * nx + i;
|
||||||
|
float x = tp.x + i * stepX, z = tp.z + j * stepZ;
|
||||||
|
if (i + 1 < nx && under[k] != under[k + 1])
|
||||||
|
{ float t = Mathf.Clamp01((waterY - hgt[k]) / (hgt[k + 1] - hgt[k])); wlx.Add(x + t * stepX); wlz.Add(z); }
|
||||||
|
if (j + 1 < nz && under[k] != under[k + nx])
|
||||||
|
{ float t = Mathf.Clamp01((waterY - hgt[k]) / (hgt[k + nx] - hgt[k])); wlx.Add(x); wlz.Add(z + t * stepZ); }
|
||||||
|
}
|
||||||
|
WaterlinePoints = wlx.Count;
|
||||||
|
float reach = Mathf.Max(up, down) + cell * 2f;
|
||||||
|
var f = new float[nx * nz];
|
||||||
|
for (int j = 0; j < nz; j++)
|
||||||
|
for (int i = 0; i < nx; i++)
|
||||||
|
{
|
||||||
|
int k = j * nx + i;
|
||||||
|
float dc = under[k] ? dToSand[k] : dToWater[k];
|
||||||
|
if (dc > reach || wlx.Count == 0) { f[k] = under[k] ? reach + 1f : -(reach + 1f); continue; }
|
||||||
|
float x = tp.x + i * stepX, z = tp.z + j * stepZ, best = float.MaxValue;
|
||||||
|
for (int q = 0; q < wlx.Count; q++)
|
||||||
|
{
|
||||||
|
float dx = x - wlx[q], dz = z - wlz[q]; float d2 = dx * dx + dz * dz;
|
||||||
|
if (d2 < best) best = d2;
|
||||||
|
}
|
||||||
|
float d = Mathf.Sqrt(best);
|
||||||
|
f[k] = under[k] ? d : -d;
|
||||||
|
}
|
||||||
|
|
||||||
var vmap = new int[nx * nz];
|
var vmap = new int[nx * nz];
|
||||||
for (int k = 0; k < vmap.Length; k++) vmap[k] = -1;
|
for (int k = 0; k < vmap.Length; k++) vmap[k] = -1;
|
||||||
|
|
@ -278,8 +317,8 @@ namespace WL.Look.Farm
|
||||||
_mr.enabled = true;
|
_mr.enabled = true;
|
||||||
|
|
||||||
Rebuilds++; Verts = verts.Count; Tris = tris.Count / 3; Tiles = 0;
|
Rebuilds++; Verts = verts.Count; Tris = tris.Count / 3; Tiles = 0;
|
||||||
LastLog = "터레인 물가선 띠 — 삼각형 " + Tris + " · 정점 " + Verts + " · 물 y " + waterY.ToString("F2")
|
LastLog = "터레인 물가선 띠 — 삼각형 " + Tris + " · 정점 " + Verts + " · 물 y " + waterY.ToString("F2") + " · 물가선 점 " + WaterlinePoints
|
||||||
+ " · 모래 쪽 " + up.ToString("F2") + " m ↑ · 바다 쪽 수심 " + down.ToString("F2") + " m · 격자 " + cell.ToString("F2") + " m · 드로우콜 +1";
|
+ " · 모래 쪽 " + up.ToString("F2") + " m · 바다 쪽 " + down.ToString("F2") + " m(유클리드 수평 거리) · 격자 " + cell.ToString("F2") + " m · 드로우콜 +1";
|
||||||
cfg.Log("둘레 거품 띠 — " + LastLog);
|
cfg.Log("둘레 거품 띠 — " + LastLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,16 +337,18 @@ namespace WL.Look.Farm
|
||||||
s_foamTex = new Texture2D(W, H, TextureFormat.RGBA32, false)
|
s_foamTex = new Texture2D(W, H, TextureFormat.RGBA32, false)
|
||||||
{ name = "WL_ShoreFoam(runtime)", hideFlags = HideFlags.DontSave, wrapMode = TextureWrapMode.Repeat, filterMode = FilterMode.Bilinear };
|
{ name = "WL_ShoreFoam(runtime)", hideFlags = HideFlags.DontSave, wrapMode = TextureWrapMode.Repeat, filterMode = FilterMode.Bilinear };
|
||||||
var px = new Color32[W * H];
|
var px = new Color32[W * H];
|
||||||
|
// 🔴 2026-09-16 실측(남서 물가): 바다 쪽 끝까지 알파가 남아 메시 경계(격자 칸)가 네모 계단으로 보였다 → 바다 쪽은 제곱으로 빨리 꺼지고
|
||||||
|
// (띠 폭의 60 % 지점에서 ≈ 5 %) 물가선 최대 알파도 0.75 로 낮춘다(데모의 옅은 물보라). 메시 끝(v=1)은 0.
|
||||||
for (int y = 0; y < H; y++)
|
for (int y = 0; y < H; y++)
|
||||||
{
|
{
|
||||||
float v = (y + 0.5f) / H;
|
float v = (y + 0.5f) / H;
|
||||||
float a = v < vWl ? Mathf.Pow(Mathf.Clamp01(v / vWl), 2f) : 1f - SmoothStep01((v - vWl) / (1f - vWl));
|
float a = v < vWl ? Mathf.Pow(Mathf.Clamp01(v / vWl), 2f) : Mathf.Pow(1f - SmoothStep01((v - vWl) / (1f - vWl)), 2.2f);
|
||||||
for (int x = 0; x < W; x++)
|
for (int x = 0; x < W; x++)
|
||||||
{
|
{
|
||||||
float u = (float)x / W;
|
float u = (float)x / W;
|
||||||
float n = 0.5f + 0.5f * Mathf.Sin(u * 6.2832f * 3f + Mathf.Sin(u * 6.2832f * 7f + v * 3f) * 1.3f);
|
float n = 0.5f + 0.5f * Mathf.Sin(u * 6.2832f * 3f + Mathf.Sin(u * 6.2832f * 7f + v * 3f) * 1.3f);
|
||||||
float edge = v > vWl ? Mathf.Clamp01(1f - (v - vWl) / (1f - vWl) * (0.6f + 0.8f * n)) : 1f;
|
float edge = v > vWl ? Mathf.Clamp01(1f - (v - vWl) / (1f - vWl) * (0.8f + 0.9f * n)) : 1f;
|
||||||
float alpha = Mathf.Clamp01(a * edge * (0.75f + 0.25f * n));
|
float alpha = Mathf.Clamp01(a * edge * (0.7f + 0.3f * n)) * 0.75f;
|
||||||
px[y * W + x] = new Color32(255, 255, 255, (byte)(alpha * 255f));
|
px[y * W + x] = new Color32(255, 255, 255, (byte)(alpha * 255f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,13 @@ namespace WL.Look.Farm
|
||||||
SceneManager.sceneUnloaded += OnSceneUnloaded;
|
SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||||
SceneManager.activeSceneChanged += OnActiveSceneChanged;
|
SceneManager.activeSceneChanged += OnActiveSceneChanged;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
// 🔴 SSAO 는 렌더러 **에셋**의 값이라 섬에서 Play 를 끄면 「꺼진 채」 메모리에 남는다 → Play 를 나갈 때 반드시 되돌린다.
|
// 🔴 SSAO 는 렌더러 **에셋**의 값이라 섬에서 Play 를 끄면 「꺼진 채」 메모리에 남는다 → Play 를 나갈 때·편집 모드로 돌아올 때 반드시 되돌린다.
|
||||||
UnityEditor.EditorApplication.playModeStateChanged += st => { if (st == UnityEditor.PlayModeStateChange.ExitingPlayMode) RestoreSsao(); };
|
UnityEditor.EditorApplication.playModeStateChanged += st =>
|
||||||
|
{
|
||||||
|
if (st == UnityEditor.PlayModeStateChange.ExitingPlayMode || st == UnityEditor.PlayModeStateChange.EnteredEditMode) RestoreSsao();
|
||||||
|
};
|
||||||
|
// 도메인 리로드(Play 중 컴파일) 뒤 — 이전 도메인이 끈 채 남긴 SSAO 를 먼저 되돌리고, 아직 섬이면 아래에서 다시 끈다.
|
||||||
|
RestoreSsao();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 🔴 아직 아무 맵도 안 붙은 지금이 「활성 씬의 원래 하늘」이다 — 여기서 한 번만 뜬다.
|
// 🔴 아직 아무 맵도 안 붙은 지금이 「활성 씬의 원래 하늘」이다 — 여기서 한 번만 뜬다.
|
||||||
|
|
@ -169,6 +174,13 @@ namespace WL.Look.Farm
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const string SsaoSessionKey = "WL_SSAO_OFF_BY_ISLAND"; // 에디터 도메인 리로드(Play 중 컴파일)에도 살아남는 표식 — 정적 목록은 리로드에 지워진다(2026-09-16 실측: 섬에서 리컴파일 → SSAO 가 꺼진 채 남음)
|
||||||
|
|
||||||
|
static bool IsSsaoFeature(UnityEngine.Rendering.Universal.ScriptableRendererFeature f)
|
||||||
|
{
|
||||||
|
return f != null && (f.GetType().Name == "ScreenSpaceAmbientOcclusion" || f.name == "SSAO");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>섬에 들어갈 때 — 켜져 있는 SSAO 피처(이름 · 형식명으로 판별 · URP 클래스는 internal)를 끄고 기억한다.</summary>
|
/// <summary>섬에 들어갈 때 — 켜져 있는 SSAO 피처(이름 · 형식명으로 판별 · URP 클래스는 internal)를 끄고 기억한다.</summary>
|
||||||
public static void AcquireSsao(WLIslandLookSettings cfg, string sceneName)
|
public static void AcquireSsao(WLIslandLookSettings cfg, string sceneName)
|
||||||
{
|
{
|
||||||
|
|
@ -177,25 +189,46 @@ namespace WL.Look.Farm
|
||||||
for (int i = 0; i < feats.Length; i++)
|
for (int i = 0; i < feats.Length; i++)
|
||||||
{
|
{
|
||||||
var f = feats[i];
|
var f = feats[i];
|
||||||
if (f == null || !f.isActive) continue;
|
if (!IsSsaoFeature(f) || !f.isActive) continue;
|
||||||
if (f.GetType().Name != "ScreenSpaceAmbientOcclusion" && f.name != "SSAO") continue;
|
|
||||||
if (s_ssaoOff.Contains(f)) continue;
|
if (s_ssaoOff.Contains(f)) continue;
|
||||||
f.SetActive(false);
|
f.SetActive(false);
|
||||||
s_ssaoOff.Add(f);
|
s_ssaoOff.Add(f);
|
||||||
SsaoOffCount++;
|
SsaoOffCount++;
|
||||||
}
|
}
|
||||||
SsaoHeld = s_ssaoOff.Count > 0;
|
SsaoHeld = s_ssaoOff.Count > 0;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
if (SsaoHeld) UnityEditor.SessionState.SetBool(SsaoSessionKey, true);
|
||||||
|
#endif
|
||||||
if (SsaoHeld) cfg.Log("SSAO 끔 — " + sceneName + " 동안 · 피처 " + s_ssaoOff.Count + "개(나갈 때 복원)");
|
if (SsaoHeld) cfg.Log("SSAO 끔 — " + sceneName + " 동안 · 피처 " + s_ssaoOff.Count + "개(나갈 때 복원)");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>섬을 나갈 때(또는 에디터 Play 종료) — 우리가 끈 피처만 다시 켠다.</summary>
|
/// <summary>
|
||||||
|
/// 섬을 나갈 때(또는 에디터 Play 종료·편집 모드 복귀) — 우리가 끈 피처를 다시 켠다.
|
||||||
|
/// 정적 목록이 비어 있어도 세션 표식이 남아 있으면(도메인 리로드로 목록만 잃은 경우) SSAO 형식 피처를 전부 켠다(에셋 원래 값 = 켜짐).
|
||||||
|
/// </summary>
|
||||||
public static void RestoreSsao()
|
public static void RestoreSsao()
|
||||||
{
|
{
|
||||||
if (s_ssaoOff.Count == 0) { SsaoHeld = false; return; }
|
bool flagged = false;
|
||||||
for (int i = 0; i < s_ssaoOff.Count; i++) if (s_ssaoOff[i] != null) s_ssaoOff[i].SetActive(true);
|
#if UNITY_EDITOR
|
||||||
SsaoRestores += s_ssaoOff.Count;
|
flagged = UnityEditor.SessionState.GetBool(SsaoSessionKey, false);
|
||||||
|
#endif
|
||||||
|
if (s_ssaoOff.Count == 0 && !flagged) { SsaoHeld = false; return; }
|
||||||
|
int n = 0;
|
||||||
|
if (s_ssaoOff.Count > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < s_ssaoOff.Count; i++) if (s_ssaoOff[i] != null && !s_ssaoOff[i].isActive) { s_ssaoOff[i].SetActive(true); n++; }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var feats = Resources.FindObjectsOfTypeAll<UnityEngine.Rendering.Universal.ScriptableRendererFeature>();
|
||||||
|
for (int i = 0; i < feats.Length; i++) if (IsSsaoFeature(feats[i]) && !feats[i].isActive) { feats[i].SetActive(true); n++; }
|
||||||
|
}
|
||||||
|
SsaoRestores += n;
|
||||||
s_ssaoOff.Clear();
|
s_ssaoOff.Clear();
|
||||||
SsaoHeld = false;
|
SsaoHeld = false;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.SessionState.EraseBool(SsaoSessionKey);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue