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

159 lines
7.3 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// 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
}