159 lines
7.3 KiB
GLSL
159 lines
7.3 KiB
GLSL
// ─────────────────────────────────────────────────────────────────────────────
|
||
// 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
|
||
}
|