Project_WL/Assets/WL/Look/Farm/Shaders/WLShoreSpray.shader

159 lines
7.3 KiB
Plaintext
Raw Permalink Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLShoreSpray.shader — 섬 물가의 물보라(절차적 거품) · 언릿 · 알파 블렌드
//
// 2026-09-17 PD 「섬 가장자리의 물거품이 안개처럼 보이고 부자연스럽다 → 좀 더 자연스러운 물보라처럼」.
// 예전 띠 = 옅은 알파 그라데이션 텍스처(폭 2 m) → 흰 안개처럼 번져 보였다.
// 이 셰이더 = 경계가 **또렷한 조각**만 그린다(그라데이션 0):
// ① 물가 거품 띠: 물가선에 붙어 있고, 바다 쪽 가장자리가 노이즈로 끊긴다. 해안 구간마다 다른 박자로 밀려왔다(_EdgeMax) 빠진다(_EdgeMin).
// ② 포말 줄: 그 바깥에서 해안으로 밀려오는 가는 줄 2~3가닥. 멀수록 가늘고 듬성듬성(노이즈 임계가 올라가 조각으로 흩어진다).
// 입력: uv2.x = 물가선까지의 수평 거리(m · + 바다 / − 모래) — `WLShoreFoam.BuildFromTerrain` 이 채운다. 무늬 좌표 = 월드 xz(이음매 없음).
// 모바일: 텍스처 0장 · 그라디언트 노이즈 3회(좁은 띠 위에서만) · 조명 없음.
// ─────────────────────────────────────────────────────────────────────────────
Shader "WL/ShoreSpray"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
_Opacity ("Opacity", Range(0, 1)) = 0.92
_NoiseScale ("Noise Scale (1/m)", Float) = 2.4
_EdgeMin ("Contact Foam Width — 빠졌을 때 (m)", Float) = 0.10
_EdgeMax ("Contact Foam Width — 밀려왔을 때 (m)", Float) = 0.42
_SandReach ("Sand Reach (m)", Float) = 0.16
_WaveSpeed ("Wave Speed (rad/s)", Float) = 0.9
_LineSpacing ("Wash Line Spacing (m)", Float) = 0.62
_LineSpeed ("Wash Line Speed (spacing/s)", Float) = 0.22
_LineWidth ("Wash Line Width (0~1)", Range(0.02, 0.6)) = 0.2
_LineReach ("Wash Line Reach (m)", Float) = 1.7
_LineBreak ("Wash Line Break (0~1)", Range(0, 1)) = 0.45
}
SubShader
{
Tags { "RenderPipeline" = "UniversalPipeline" "RenderType" = "Transparent" "Queue" = "Transparent+50" "IgnoreProjector" = "True" }
Pass
{
Name "WLShoreSpray"
Tags { "LightMode" = "SRPDefaultUnlit" }
Cull Off
ZWrite Off
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float _Opacity;
float _NoiseScale;
float _EdgeMin;
float _EdgeMax;
float _SandReach;
float _WaveSpeed;
float _LineSpacing;
float _LineSpeed;
float _LineWidth;
float _LineReach;
float _LineBreak;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
float2 uv2 : TEXCOORD1;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD0;
float dist : TEXCOORD1; // 물가선까지 수평 거리(m · + 바다 / − 모래)
};
Varyings vert(Attributes input)
{
Varyings o = (Varyings)0;
o.positionWS = TransformObjectToWorld(input.positionOS.xyz);
o.positionCS = TransformWorldToHClip(o.positionWS);
o.dist = input.uv2.x;
return o;
}
float hash21(float2 p)
{
p = frac(p * float2(123.34, 456.21));
p += dot(p, p + 45.32);
return frac(p.x * p.y);
}
float2 grad2(float2 i)
{
float a = hash21(i) * 6.2831853;
return float2(cos(a), sin(a));
}
// 그라디언트(펄린) 노이즈 0..1 — 값 노이즈는 격자 모양(둥근 네모)이 임계 경계에 그대로 드러난다(2026-09-16 지형 실측과 같은 이유)
float gnoise(float2 p)
{
float2 i = floor(p), f = frac(p);
float2 u = f * f * (3.0 - 2.0 * f);
float a = dot(grad2(i), f);
float b = dot(grad2(i + float2(1, 0)), f - float2(1, 0));
float c = dot(grad2(i + float2(0, 1)), f - float2(0, 1));
float d = dot(grad2(i + float2(1, 1)), f - float2(1, 1));
return saturate(0.5 + 0.72 * lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y));
}
// 화면 미분으로 1 px 만 부드럽게 한 계단(또렷하되 계단 현상 없음)
float aastep(float x, float edge)
{
float fw = max(fwidth(x), 1e-4);
return smoothstep(edge - fw, edge + fw, x);
}
half4 frag(Varyings input) : SV_Target
{
float d = input.dist;
float2 w = input.positionWS.xz;
float t = _Time.y;
float n1 = gnoise(w * _NoiseScale + float2(t * 0.13, t * 0.09));
float n2 = gnoise(w * _NoiseScale * 2.6 + float2(-t * 0.21, t * 0.05));
float n = n1 * 0.62 + n2 * 0.38;
// 해안 구간마다 다른 박자로 밀려왔다 빠진다
float ph = gnoise(w * 0.11) * 6.2831853;
float breath = 0.5 + 0.5 * sin(t * _WaveSpeed + ph);
float edgeW = lerp(_EdgeMin, _EdgeMax, breath);
// ① 물가 거품 띠 — 모래 쪽 끝(−) ~ 바다 쪽 끝(+ · 노이즈로 끊김)
float seaEdge = edgeW * (0.45 + 1.1 * n);
float sandEdge = -_SandReach * (0.35 + 0.65 * n2) * (0.4 + 0.6 * breath);
float contact = aastep(d, sandEdge) * (1.0 - aastep(d, seaEdge));
// ② 포말 줄 — 해안으로 밀려온다(시간이 흐르면 같은 줄의 d 가 줄어든다)
float k = d / max(_LineSpacing, 0.05) + t * _LineSpeed;
float core = 1.0 - abs(frac(k) - 0.5) * 2.0; // 줄 중심 1
float distT = saturate(d / max(_LineReach, 0.05));
float width = _LineWidth * (1.0 - 0.6 * distT); // 멀수록 가늘다
float washLine = aastep(core, 1.0 - width);
float keepTh = lerp(_LineBreak * 0.6, 0.98, distT * distT); // 멀수록 듬성듬성
washLine *= aastep(n, keepTh);
washLine *= step(seaEdge, d) * step(d, _LineReach);
float a = saturate(max(contact, washLine)) * _Opacity * _Color.a;
return half4(_Color.rgb, a);
}
ENDHLSL
}
}
Fallback Off
}