|
#include <metal_stdlib> |
|
using namespace metal; |
|
|
|
struct VertexOut { |
|
float4 position [[position]]; |
|
float2 uv; |
|
}; |
|
|
|
struct UnderwaterUniforms { |
|
float2 viewportSize; |
|
float time; |
|
float strength; |
|
float caustics; |
|
float absorption; |
|
float turbidity; |
|
float waterLevel; |
|
float edgeFeather; |
|
float edgeWaves; |
|
float2 _pad; |
|
}; |
|
|
|
vertex VertexOut fullscreenVertex( |
|
const device float* vtx [[buffer(0)]], |
|
uint vid [[vertex_id]] |
|
) { |
|
VertexOut out; |
|
uint i = vid * 4; |
|
out.position = float4(vtx[i + 0], vtx[i + 1], 0.0, 1.0); |
|
out.uv = float2(vtx[i + 2], vtx[i + 3]); |
|
return out; |
|
} |
|
|
|
inline float hash21(float2 p) { |
|
p = fract(p * float2(123.34, 345.45)); |
|
p += dot(p, p + 34.345); |
|
return fract(p.x * p.y); |
|
} |
|
|
|
inline float noise(float2 p) { |
|
float2 i = floor(p); |
|
float2 f = fract(p); |
|
float a = hash21(i); |
|
float b = hash21(i + float2(1, 0)); |
|
float c = hash21(i + float2(0, 1)); |
|
float d = hash21(i + float2(1, 1)); |
|
float2 u = f * f * (3.0 - 2.0 * f); |
|
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); |
|
} |
|
|
|
inline float fbm(float2 p) { |
|
float v = 0.0; |
|
float a = 0.5; |
|
for (int i = 0; i < 4; i++) { |
|
v += a * noise(p); |
|
p *= 2.02; |
|
a *= 0.5; |
|
} |
|
return v; |
|
} |
|
|
|
inline float causticPattern(float2 uv, float t) { |
|
float2 p = uv * 6.0; |
|
float n1 = fbm(p + float2(t * 0.20, t * 0.13)); |
|
float n2 = fbm(p * 1.4 - float2(t * 0.15, t * 0.22)); |
|
return pow(saturate(1.0 - abs(n1 - n2) * 2.2), 3.0); |
|
} |
|
|
|
inline float3 underwaterShade(float2 uv, float2 px, float t, |
|
texture2d<float> tex, sampler samp, |
|
constant UnderwaterUniforms& u) { |
|
float2 p = uv * 2.0; |
|
float w1 = sin((p.x * 8.0 + t * 1.2) + sin(p.y * 3.0 + t * 0.7)); |
|
float w2 = sin((p.y * 10.0 + t * 1.0) + sin(p.x * 4.0 - t * 0.6)); |
|
float w3 = fbm(p * 3.0 + float2(t * 0.25, -t * 0.18)) * 2.0 - 1.0; |
|
float2 refr = float2(w1 + w3, w2 - w3); |
|
float dist = (0.8 + 2.6 * saturate(u.strength)) * 6.0; |
|
float2 uv2 = uv + refr * px * dist; |
|
float3 rgb = tex.sample(samp, uv2).rgb; |
|
|
|
float depth = saturate((uv.y - u.waterLevel) / max(1e-4, (1.0 - u.waterLevel))); |
|
float absorb = saturate(u.absorption) * (0.25 + 0.75 * depth); |
|
float3 waterTint = float3(0.07, 0.35, 0.45); |
|
rgb = mix(rgb, rgb * (1.0 - 0.35 * absorb) + waterTint * (0.55 * absorb), absorb); |
|
|
|
float c = causticPattern(uv + refr * 0.05, t); |
|
float ca = saturate(u.caustics) * (0.30 + 0.50 * (1.0 - depth)); |
|
rgb += c * ca; |
|
|
|
float haze = saturate(u.turbidity) * (0.12 + 0.55 * depth); |
|
float fogN = fbm(uv * 5.5 + float2(t * 0.03, -t * 0.02)); |
|
float3 hazeCol = waterTint + fogN * 0.06; |
|
rgb = mix(rgb, hazeCol, haze); |
|
|
|
float sp = step(0.987, hash21(floor(uv * 130.0 + t * 8.0))); |
|
rgb += sp * 0.05 * haze; |
|
|
|
return saturate(rgb); |
|
} |
|
|
|
fragment float4 underwaterSplitFragment( |
|
VertexOut in [[stage_in]], |
|
texture2d<float> tex [[texture(0)]], |
|
sampler samp [[sampler(0)]], |
|
constant UnderwaterUniforms& u [[buffer(0)]] |
|
) { |
|
float2 uv = in.uv; |
|
float4 base = tex.sample(samp, uv); |
|
float2 px = 1.0 / max(u.viewportSize, float2(1.0)); |
|
|
|
float waveAmp = (0.004 + 0.012 * u.edgeWaves); |
|
float wave = |
|
sin(uv.x * 12.0 + u.time * 1.2) * 0.5 + |
|
sin(uv.x * 24.0 - u.time * 0.9) * 0.35 + |
|
(fbm(float2(uv.x * 6.0, u.time * 0.3)) - 0.5) * 0.6; |
|
|
|
float waterline = u.waterLevel + wave * waveAmp; |
|
float feather = max(u.edgeFeather, 0.0); |
|
float m = smoothstep(waterline - feather, waterline + feather, uv.y); |
|
|
|
float3 uw = underwaterShade(uv, px, u.time, tex, samp, u); |
|
float3 outRGB = mix(base.rgb, uw, m); |
|
|
|
float rim = 1.0 - smoothstep(0.0, feather * 2.5 + 1e-4, fabs(uv.y - waterline)); |
|
outRGB += rim * 0.05 * u.edgeWaves; |
|
|
|
return float4(saturate(outRGB), base.a); |
|
} |