2026-09-15 10:06:15 +00:00
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
// WLSoftDirt.cs — 밭(흙) 자리를 데모처럼 **둥글게 번지는 한 장**으로 (WL-816zf · #816)
|
|
|
|
|
|
//
|
|
|
|
|
|
// PD 지시(2026-09-15) 「데모씬을 보면 자연스럽게 흙과 잔디가 라운딩 된 것처럼 배치되어 있어.
|
|
|
|
|
|
// 데모씬과 유사하게 맞출 방법을 찾아서 최대한 맞춰.」
|
|
|
|
|
|
//
|
|
|
|
|
|
// ■ 왜 각져 보이는가
|
|
|
|
|
|
// 데모는 **터레인 스플랫**이라 흙이 잔디 속으로 부드럽게 번진다.
|
|
|
|
|
|
// 우리 섬은 FI 의 **1×1 흙 타일(Soil)** 묶음이라 경계가 계단처럼 각진다.
|
|
|
|
|
|
// 타일을 아무리 골라 숨겨도 최소 단위가 1 m 라 「둥글게」가 안 나온다.
|
|
|
|
|
|
//
|
|
|
|
|
|
// ■ 어떻게 바꾸는가
|
|
|
|
|
|
// 밭 구역(`WLIslandGateFlow.ZoneBounds`)을 덮는 **평면 메시 1장**(4정점 · 2삼각형)에
|
|
|
|
|
|
// 런타임에 구운 **마스크 텍스처**(모서리를 깎은 사각형 + 경계 노이즈 + 가장자리 페이드)를 씌운다.
|
|
|
|
|
|
// 모양은 전부 텍스처의 알파가 만들므로 메시는 사각형 하나면 된다 = **드로우콜 +1**.
|
|
|
|
|
|
// 켜지면 각진 흙 타일 렌더러는 숨긴다(`WLIslandGateFlow.IsSoilRenderer` 가 배타 처리).
|
|
|
|
|
|
//
|
|
|
|
|
|
// ■ 풀과 흙이 같은 선을 쓴다
|
|
|
|
|
|
// 816za 의 풀 제외(밭 자리)는 이 파일의 `SignedDist` 를 그대로 쓴다.
|
|
|
|
|
|
// 풀은 데칼 가장자리보다 `softDirtGrassBite`(m) **안쪽까지** 들어와, 흙 가장자리에
|
|
|
|
|
|
// 풀 톱니가 파고든 것처럼 읽힌다 — 경계를 두 군데서 따로 계산하지 않는다.
|
|
|
|
|
|
//
|
|
|
|
|
|
// ■ 되돌리기 — `softDirtEnabled = 0` 이면 흙 타일이 그대로 보이고 풀 제외도 816za 방식으로 돌아간다.
|
|
|
|
|
|
//
|
|
|
|
|
|
// 🔴 FI 코드 0줄 · 씬 파일 0줄 · 에셋 0줄. 메시·텍스처·머티리얼 전부 런타임 생성이다.
|
|
|
|
|
|
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 팝업).
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using FISoil = CryingSnow.FarmingIsland.Soil;
|
|
|
|
|
|
|
|
|
|
|
|
namespace WL.Look.Farm
|
|
|
|
|
|
{
|
|
|
|
|
|
[DisallowMultipleComponent]
|
|
|
|
|
|
public sealed class WLSoftDirt : MonoBehaviour
|
|
|
|
|
|
{
|
|
|
|
|
|
public const string ObjectName = "~WL_SoftDirt";
|
|
|
|
|
|
|
|
|
|
|
|
// ── 진단(프로브·보고가 읽는다)
|
|
|
|
|
|
public static int Rebuilds;
|
|
|
|
|
|
public static string LastLog = "꺼짐";
|
|
|
|
|
|
|
|
|
|
|
|
public WLIslandLookSettings cfg;
|
|
|
|
|
|
|
|
|
|
|
|
MeshFilter _mf;
|
|
|
|
|
|
MeshRenderer _mr;
|
|
|
|
|
|
Mesh _mesh;
|
|
|
|
|
|
Texture2D _tex;
|
|
|
|
|
|
Material _inst;
|
|
|
|
|
|
|
|
|
|
|
|
static readonly int IdBaseMap = Shader.PropertyToID("_BaseMap");
|
|
|
|
|
|
static readonly int IdMainTex = Shader.PropertyToID("_MainTex");
|
|
|
|
|
|
static readonly int IdBaseColor = Shader.PropertyToID("_BaseColor");
|
|
|
|
|
|
static readonly int IdColor = Shader.PropertyToID("_Color");
|
|
|
|
|
|
static readonly int IdSurface = Shader.PropertyToID("_Surface");
|
|
|
|
|
|
static readonly int IdBlend = Shader.PropertyToID("_Blend");
|
|
|
|
|
|
static readonly int IdSrcBlend = Shader.PropertyToID("_SrcBlend");
|
|
|
|
|
|
static readonly int IdDstBlend = Shader.PropertyToID("_DstBlend");
|
|
|
|
|
|
static readonly int IdZWrite = Shader.PropertyToID("_ZWrite");
|
|
|
|
|
|
static readonly int IdCull = Shader.PropertyToID("_Cull");
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
// 마스크 — 흙 경계의 **단일 정의**. 데칼 텍스처와 풀 제외가 같은 값을 읽는다.
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
static bool s_ready;
|
|
|
|
|
|
static float s_cx, s_cz, s_hx, s_hz, s_rad, s_noise, s_cell, s_bite;
|
|
|
|
|
|
|
2026-09-16 05:45:47 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 설정이 데칼을 쓰기로 했는가 — 흙 타일 숨김(배타)을 가르는 값. 구역을 몰라도 참이다.
|
|
|
|
|
|
/// 816zj — 데모 터레인 모드(`demoTerrainEnabled 1`)에서는 데칼 대신 터레인 스플랫이 밭 자리를 흙으로 칠하므로
|
|
|
|
|
|
/// 각진 흙 타일은 마찬가지로 숨겨야 한다 → 그 모드도 참으로 친다(`softDirtEnabled` 값과 무관).
|
|
|
|
|
|
/// </summary>
|
2026-09-15 10:06:15 +00:00
|
|
|
|
public static bool Active
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
var c = WLIslandLookSettings.Instance;
|
2026-09-16 05:45:47 +00:00
|
|
|
|
return c != null && c.enabled_ != 0 && (c.softDirtEnabled != 0 || c.demoTerrainEnabled != 0);
|
2026-09-15 10:06:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>마스크를 실제로 계산해 두었는가(밭 구역을 알아낸 뒤). 풀 제외는 이것이 참일 때만 바뀐다.</summary>
|
|
|
|
|
|
public static bool Ready { get { return s_ready && Active; } }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 흙 경계까지의 부호 있는 거리(m). **음수 = 흙 안쪽 · 양수 = 바깥(풀)**.
|
|
|
|
|
|
/// 모서리를 `softDirtCornerRadius` 만큼 깎은 사각형에 셀 노이즈로 경계를 흔든 모양이다.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static float SignedDist(float x, float z)
|
|
|
|
|
|
{
|
2026-09-16 05:45:47 +00:00
|
|
|
|
return ShapeDist(x, z, s_cx, s_cz, s_hx, s_hz, s_rad, s_noise, s_cell);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 흙 모양의 **단일 식** — 중심(cx,cz)·반폭(hx,hz)·모서리 반지름 rad 의 둥근 사각형에 셀 노이즈(진폭 noise · 셀 cell)로 경계를 흔든 부호 거리(m).
|
|
|
|
|
|
/// 816zj 데모 터레인 스플랫이 같은 식으로 밭 자리를 칠한다(정적 상태 없이 값만 받는다).
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static float ShapeDist(float x, float z, float cx, float cz, float hx, float hz, float rad, float noise, float cell)
|
|
|
|
|
|
{
|
|
|
|
|
|
float dx = Mathf.Abs(x - cx) - (hx - rad);
|
|
|
|
|
|
float dz = Mathf.Abs(z - cz) - (hz - rad);
|
2026-09-15 10:06:15 +00:00
|
|
|
|
float ox = Mathf.Max(dx, 0f), oz = Mathf.Max(dz, 0f);
|
2026-09-16 05:45:47 +00:00
|
|
|
|
float d = Mathf.Min(Mathf.Max(dx, dz), 0f) + Mathf.Sqrt(ox * ox + oz * oz) - rad;
|
|
|
|
|
|
if (noise > 0f && cell > 0.0001f)
|
|
|
|
|
|
d -= (CellNoise(x / cell, z / cell) * 2f - 1f) * noise;
|
2026-09-15 10:06:15 +00:00
|
|
|
|
return d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>여기에 풀을 깔지 말 것인가 — 데칼 가장자리보다 `softDirtGrassBite` 만큼 **더 안쪽**만 막는다.</summary>
|
|
|
|
|
|
public static bool Blocks(float x, float z)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!s_ready) return false;
|
|
|
|
|
|
// 싸게 먼저 거른다 — 구역 밖이면 노이즈를 계산하지 않는다.
|
|
|
|
|
|
if (Mathf.Abs(x - s_cx) > s_hx + s_noise || Mathf.Abs(z - s_cz) > s_hz + s_noise) return false;
|
|
|
|
|
|
return SignedDist(x, z) < -s_bite;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
void OnEnable() { Ensure(); _dirty = true; Rebuild(); }
|
|
|
|
|
|
void OnDisable() { if (_mr != null) _mr.enabled = false; s_ready = false; }
|
|
|
|
|
|
|
|
|
|
|
|
void OnDestroy()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_inst != null) Destroy(_inst);
|
|
|
|
|
|
if (_tex != null) Destroy(_tex);
|
|
|
|
|
|
if (_mesh != null) Destroy(_mesh);
|
|
|
|
|
|
s_ready = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Ensure()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_mf == null) _mf = GetComponent<MeshFilter>();
|
|
|
|
|
|
if (_mf == null) _mf = gameObject.AddComponent<MeshFilter>();
|
|
|
|
|
|
if (_mr == null) _mr = GetComponent<MeshRenderer>();
|
|
|
|
|
|
if (_mr == null) _mr = gameObject.AddComponent<MeshRenderer>();
|
|
|
|
|
|
_mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
|
|
|
|
|
_mr.receiveShadows = false;
|
|
|
|
|
|
_mr.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
|
|
|
|
|
|
_mr.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
|
|
|
|
|
|
_mr.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
|
|
|
|
|
if (_mesh == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_mesh = new Mesh { name = "WL_SoftDirtMesh" };
|
|
|
|
|
|
_mesh.MarkDynamic();
|
|
|
|
|
|
_mesh.hideFlags = HideFlags.DontSave;
|
|
|
|
|
|
}
|
|
|
|
|
|
_mf.sharedMesh = _mesh;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 마지막으로 구운 조건 — 같으면 다시 굽지 않는다(1초 폴링에서 공짜로 돌게).
|
|
|
|
|
|
bool _dirty = true;
|
|
|
|
|
|
float _lastCx, _lastCz, _lastHx, _lastHz, _lastY, _lastSig = float.NaN;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>다시 만든다(섬 확장·밭 재구성). 멱등 — 조건이 그대로면 아무것도 하지 않는다.</summary>
|
|
|
|
|
|
public void Rebuild()
|
|
|
|
|
|
{
|
|
|
|
|
|
Ensure();
|
|
|
|
|
|
if (cfg == null) cfg = WLIslandLookSettings.Instance;
|
|
|
|
|
|
if (cfg == null || cfg.enabled_ == 0 || cfg.softDirtEnabled == 0)
|
|
|
|
|
|
{ _mr.enabled = false; s_ready = false; LastLog = "꺼짐"; return; }
|
|
|
|
|
|
|
|
|
|
|
|
if (!global::WL.Island.WLIslandGateFlow.ZoneBoundsValid)
|
|
|
|
|
|
{ _mr.enabled = false; s_ready = false; LastLog = "밭 구역 미확정 — 대기"; return; }
|
|
|
|
|
|
|
|
|
|
|
|
var zone = global::WL.Island.WLIslandGateFlow.ZoneBounds;
|
|
|
|
|
|
float hx = zone.extents.x + cfg.softDirtExpand;
|
|
|
|
|
|
float hz = zone.extents.z + cfg.softDirtExpand;
|
|
|
|
|
|
if (hx <= 0.01f || hz <= 0.01f)
|
|
|
|
|
|
{ _mr.enabled = false; s_ready = false; LastLog = "밭 구역 넓이 0"; return; }
|
|
|
|
|
|
|
|
|
|
|
|
float y = SoilTopY(zone) + cfg.softDirtY;
|
|
|
|
|
|
|
|
|
|
|
|
// 값이 바뀌면 다시 굽는다(인스펙터에서 만져도 다음 폴링에 반영된다).
|
|
|
|
|
|
float sig = cfg.softDirtCornerRadius * 7.1f + cfg.softDirtNoise * 13.3f + cfg.softDirtNoiseCell * 3.7f
|
|
|
|
|
|
+ cfg.softDirtFeather * 29.3f + cfg.softDirtGrassBite * 5.9f + cfg.softDirtPad * 11.9f
|
|
|
|
|
|
+ cfg.softDirtTexRes * 0.017f + cfg.softDirtTexNoise * 23.1f + cfg.softDirtOpacity * 17.7f
|
|
|
|
|
|
+ cfg.softDirtColor.r * 101f + cfg.softDirtColor.g * 103f + cfg.softDirtColor.b * 107f;
|
|
|
|
|
|
|
|
|
|
|
|
bool same = !_dirty && _tex != null && _inst != null
|
|
|
|
|
|
&& Mathf.Abs(_lastCx - zone.center.x) < 0.001f && Mathf.Abs(_lastCz - zone.center.z) < 0.001f
|
|
|
|
|
|
&& Mathf.Abs(_lastHx - hx) < 0.001f && Mathf.Abs(_lastHz - hz) < 0.001f
|
|
|
|
|
|
&& Mathf.Abs(_lastY - y) < 0.001f && Mathf.Abs(_lastSig - sig) < 0.0001f;
|
|
|
|
|
|
|
|
|
|
|
|
// 마스크 값은 매번 갱신한다 — 풀(816za)이 읽는 단일 출처다.
|
|
|
|
|
|
s_cx = zone.center.x; s_cz = zone.center.z;
|
|
|
|
|
|
s_hx = hx; s_hz = hz;
|
|
|
|
|
|
s_rad = Mathf.Clamp(cfg.softDirtCornerRadius, 0f, Mathf.Min(hx, hz));
|
|
|
|
|
|
s_noise = Mathf.Max(0f, cfg.softDirtNoise);
|
|
|
|
|
|
s_cell = Mathf.Max(0.05f, cfg.softDirtNoiseCell);
|
|
|
|
|
|
s_bite = Mathf.Max(0f, cfg.softDirtGrassBite);
|
|
|
|
|
|
s_ready = true;
|
|
|
|
|
|
|
|
|
|
|
|
if (same) { _mr.enabled = true; return; }
|
|
|
|
|
|
|
|
|
|
|
|
if (!EnsureMaterial()) { _mr.enabled = false; s_ready = false; LastLog = "🔴 URP Unlit 셰이더를 못 찾음"; return; }
|
|
|
|
|
|
|
|
|
|
|
|
float feather = Mathf.Max(0.05f, cfg.softDirtFeather);
|
|
|
|
|
|
float pad = Mathf.Max(cfg.softDirtPad, feather + s_noise + 0.25f);
|
|
|
|
|
|
int res = Mathf.Clamp(cfg.softDirtTexRes, 32, 1024);
|
|
|
|
|
|
|
|
|
|
|
|
BakeMask(hx + pad, hz + pad, res, feather);
|
|
|
|
|
|
BuildQuad(hx + pad, hz + pad, y);
|
|
|
|
|
|
|
|
|
|
|
|
_inst.SetTexture(IdBaseMap, _tex);
|
|
|
|
|
|
if (_inst.HasProperty(IdMainTex)) _inst.SetTexture(IdMainTex, _tex);
|
|
|
|
|
|
var col = cfg.softDirtColor; col.a = Mathf.Clamp01(cfg.softDirtOpacity);
|
|
|
|
|
|
_inst.SetColor(IdBaseColor, col);
|
|
|
|
|
|
if (_inst.HasProperty(IdColor)) _inst.SetColor(IdColor, col);
|
|
|
|
|
|
_mr.sharedMaterial = _inst;
|
|
|
|
|
|
_mr.enabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
_lastCx = s_cx; _lastCz = s_cz; _lastHx = hx; _lastHz = hz; _lastY = y; _lastSig = sig;
|
|
|
|
|
|
_dirty = false;
|
|
|
|
|
|
Rebuilds++;
|
|
|
|
|
|
|
|
|
|
|
|
LastLog = "흙 데칼 — 구역 " + (hx * 2f).ToString("F1") + "×" + (hz * 2f).ToString("F1")
|
|
|
|
|
|
+ " m · 모서리 R " + s_rad.ToString("F2") + " m · 노이즈 ±" + s_noise.ToString("F2")
|
|
|
|
|
|
+ " m(셀 " + s_cell.ToString("F2") + ") · 페이드 " + feather.ToString("F2")
|
|
|
|
|
|
+ " m · 풀 파고듦 " + s_bite.ToString("F2") + " m · 텍스처 " + res + "² · 드로우콜 +1";
|
|
|
|
|
|
cfg.Log(LastLog);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 흙칸(Soil) 윗면 높이 — 데칼을 여기 바로 위에 깐다(밭 바닥이 곧 걸어다니는 면).
|
|
|
|
|
|
/// 렌더러를 꺼 두어도 `bounds` 는 유효하다. 구역 안에 흙칸이 하나도 없으면 구역 바닥을 쓴다.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
static float SoilTopY(Bounds zone)
|
|
|
|
|
|
{
|
|
|
|
|
|
float y = float.MinValue;
|
|
|
|
|
|
var soils = Object.FindObjectsByType<FISoil>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
|
|
|
|
for (int i = 0; i < soils.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var s = soils[i];
|
|
|
|
|
|
if (s == null) continue;
|
|
|
|
|
|
var p = s.transform.position;
|
|
|
|
|
|
if (Mathf.Abs(p.x - zone.center.x) > zone.extents.x + 1f) continue; // 다른 섬의 밭은 뺀다
|
|
|
|
|
|
if (Mathf.Abs(p.z - zone.center.z) > zone.extents.z + 1f) continue;
|
|
|
|
|
|
var r = s.GetComponentInChildren<Renderer>(true);
|
|
|
|
|
|
float t = r != null ? r.bounds.max.y : p.y;
|
|
|
|
|
|
if (t > y) y = t;
|
|
|
|
|
|
}
|
|
|
|
|
|
return y > -1e8f ? y : zone.min.y;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>URP Unlit 반투명 런타임 머티리얼(816j2 `Farm_ShoreFoam.mat` 과 같은 구성).</summary>
|
|
|
|
|
|
bool EnsureMaterial()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (cfg.softDirtMaterial != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_inst == null || _inst.shader != cfg.softDirtMaterial.shader)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_inst != null) Destroy(_inst);
|
|
|
|
|
|
_inst = new Material(cfg.softDirtMaterial) { name = "WL_SoftDirt(runtime)", hideFlags = HideFlags.DontSave };
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_inst != null) return true;
|
|
|
|
|
|
|
|
|
|
|
|
// 에셋을 새로 만들지 않는다 — 816j2 거품 띠가 쓰는 URP Unlit 셰이더를 그대로 빌려 쓴다.
|
|
|
|
|
|
Shader sh = cfg.shoreFoamMaterial != null ? cfg.shoreFoamMaterial.shader : null;
|
|
|
|
|
|
if (sh == null) sh = Shader.Find("Universal Render Pipeline/Unlit");
|
|
|
|
|
|
if (sh == null) sh = Shader.Find("Unlit/Transparent");
|
|
|
|
|
|
if (sh == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
_inst = new Material(sh) { name = "WL_SoftDirt(runtime)", hideFlags = HideFlags.DontSave };
|
|
|
|
|
|
// URP Unlit 을 반투명으로 — Farm_ShoreFoam.mat 실측값과 같다(_Surface 1 · SrcAlpha/OneMinusSrcAlpha · ZWrite 0).
|
|
|
|
|
|
if (_inst.HasProperty(IdSurface)) _inst.SetFloat(IdSurface, 1f);
|
|
|
|
|
|
if (_inst.HasProperty(IdBlend)) _inst.SetFloat(IdBlend, 0f);
|
|
|
|
|
|
if (_inst.HasProperty(IdSrcBlend)) _inst.SetFloat(IdSrcBlend, (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
|
|
|
|
|
|
if (_inst.HasProperty(IdDstBlend)) _inst.SetFloat(IdDstBlend, (float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
|
|
|
|
|
if (_inst.HasProperty(IdZWrite)) _inst.SetFloat(IdZWrite, 0f);
|
|
|
|
|
|
if (_inst.HasProperty(IdCull)) _inst.SetFloat(IdCull, (float)UnityEngine.Rendering.CullMode.Off);
|
|
|
|
|
|
_inst.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
|
|
|
|
|
|
_inst.SetShaderPassEnabled("ShadowCaster", false);
|
|
|
|
|
|
_inst.SetShaderPassEnabled("DepthOnly", false);
|
|
|
|
|
|
_inst.SetShaderPassEnabled("MOTIONVECTORS", false);
|
|
|
|
|
|
_inst.SetOverrideTag("RenderType", "Transparent");
|
|
|
|
|
|
// 거품 띠(3050)보다 먼저 — 땅에 붙는 데칼이 먼저 깔리고 그 위로 물가 거품이 온다.
|
|
|
|
|
|
_inst.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>마스크를 굽는다 — 알파 = 흙 덮임(1) → 풀(0) 으로 `feather`(m) 폭에서 부드럽게 넘어간다.</summary>
|
|
|
|
|
|
void BakeMask(float halfX, float halfZ, int res, float feather)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 구운 뒤 CPU 사본을 버리므로(아래 `Apply(false, true)`) 다시 구울 때는 새로 만든다.
|
|
|
|
|
|
if (_tex != null) Destroy(_tex);
|
|
|
|
|
|
// linear:true — RGB 값을 그대로 곱하게 한다(sRGB 변환이 끼면 얼룩 세기가 뜻대로 안 나온다).
|
|
|
|
|
|
_tex = new Texture2D(res, res, TextureFormat.RGBA32, false, true)
|
|
|
|
|
|
{ name = "WL_SoftDirtMask", hideFlags = HideFlags.DontSave, wrapMode = TextureWrapMode.Clamp, filterMode = FilterMode.Bilinear };
|
|
|
|
|
|
|
|
|
|
|
|
float texNoise = Mathf.Clamp01(cfg.softDirtTexNoise);
|
|
|
|
|
|
float half = feather * 0.5f;
|
|
|
|
|
|
var px = new Color32[res * res];
|
|
|
|
|
|
float stepX = halfX * 2f / res, stepZ = halfZ * 2f / res;
|
|
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < res; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
float wz = s_cz - halfZ + (j + 0.5f) * stepZ;
|
|
|
|
|
|
for (int i = 0; i < res; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
float wx = s_cx - halfX + (i + 0.5f) * stepX;
|
|
|
|
|
|
float d = SignedDist(wx, wz);
|
|
|
|
|
|
// 1 = 흙 · 0 = 풀. 가장자리에서 feather(m) 폭으로 부드럽게 사라진다.
|
|
|
|
|
|
float a = Mathf.Clamp01((half - d) / Mathf.Max(0.0001f, feather));
|
|
|
|
|
|
a = a * a * (3f - 2f * a); // smoothstep — 띠가 선처럼 보이지 않게
|
|
|
|
|
|
|
|
|
|
|
|
float v = 1f;
|
|
|
|
|
|
if (texNoise > 0f) // 흙 얼룩 — 한 덩어리 단색으로 보이지 않게
|
|
|
|
|
|
v = 1f - texNoise * CellNoise(wx * 1.7f, wz * 1.7f);
|
|
|
|
|
|
|
|
|
|
|
|
byte c = (byte)Mathf.RoundToInt(Mathf.Clamp01(v) * 255f);
|
|
|
|
|
|
px[j * res + i] = new Color32(c, c, c, (byte)Mathf.RoundToInt(a * 255f));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_tex.SetPixels32(px);
|
|
|
|
|
|
_tex.Apply(false, true); // 밉 없음 + CPU 사본 해제
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>구역을 덮는 사각형 한 장(4정점 · 2삼각형). 모양은 전부 마스크가 만든다.</summary>
|
|
|
|
|
|
void BuildQuad(float halfX, float halfZ, float y)
|
|
|
|
|
|
{
|
|
|
|
|
|
float x0 = s_cx - halfX, x1 = s_cx + halfX;
|
|
|
|
|
|
float z0 = s_cz - halfZ, z1 = s_cz + halfZ;
|
|
|
|
|
|
|
|
|
|
|
|
_mesh.Clear();
|
|
|
|
|
|
_mesh.SetVertices(new System.Collections.Generic.List<Vector3>(4)
|
|
|
|
|
|
{
|
|
|
|
|
|
new Vector3(x0, y, z0), new Vector3(x1, y, z0),
|
|
|
|
|
|
new Vector3(x0, y, z1), new Vector3(x1, y, z1)
|
|
|
|
|
|
});
|
|
|
|
|
|
_mesh.SetUVs(0, new System.Collections.Generic.List<Vector2>(4)
|
|
|
|
|
|
{
|
|
|
|
|
|
new Vector2(0f, 0f), new Vector2(1f, 0f),
|
|
|
|
|
|
new Vector2(0f, 1f), new Vector2(1f, 1f)
|
|
|
|
|
|
});
|
|
|
|
|
|
_mesh.SetTriangles(new int[] { 0, 2, 1, 1, 2, 3 }, 0);
|
|
|
|
|
|
_mesh.RecalculateBounds();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
// 816za 와 **같은 식**의 셀 격자 값 노이즈 [0,1] — 경계를 들쭉날쭉하게 만든다.
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
static float CellNoise(float fx, float fz)
|
|
|
|
|
|
{
|
|
|
|
|
|
int x0 = Mathf.FloorToInt(fx), z0 = Mathf.FloorToInt(fz);
|
|
|
|
|
|
float tx = fx - x0, tz = fz - z0;
|
|
|
|
|
|
tx = tx * tx * (3f - 2f * tx);
|
|
|
|
|
|
tz = tz * tz * (3f - 2f * tz);
|
|
|
|
|
|
float a = CellRand(x0, z0), b = CellRand(x0 + 1, z0);
|
|
|
|
|
|
float c = CellRand(x0, z0 + 1), d = CellRand(x0 + 1, z0 + 1);
|
|
|
|
|
|
return Mathf.Lerp(Mathf.Lerp(a, b, tx), Mathf.Lerp(c, d, tx), tz);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static float CellRand(int x, int z)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint h = Hash(unchecked((uint)x * 73856093U) ^ unchecked((uint)z * 19349663U) ^ 0x9E3779B9U);
|
|
|
|
|
|
return (h & 0xFFFFFF) / 16777215f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static uint Hash(uint x)
|
|
|
|
|
|
{
|
|
|
|
|
|
x ^= x >> 16; x *= 0x7feb352dU;
|
|
|
|
|
|
x ^= x >> 15; x *= 0x846ca68bU;
|
|
|
|
|
|
x ^= x >> 16;
|
|
|
|
|
|
return x;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|