mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
- Spawn dark point-sprite insects buzzing around cattails/reeds/kelp/seaweed - Fix firefly M2 particles: exempt from alpha dampening and forced gravity - Make water shoreline/crest foam more irregular with UV warping and bluer tint
364 lines
14 KiB
GLSL
364 lines
14 KiB
GLSL
#version 450
|
|
|
|
layout(set = 0, binding = 0) uniform PerFrame {
|
|
mat4 view;
|
|
mat4 projection;
|
|
mat4 lightSpaceMatrix;
|
|
vec4 lightDir;
|
|
vec4 lightColor;
|
|
vec4 ambientColor;
|
|
vec4 viewPos;
|
|
vec4 fogColor;
|
|
vec4 fogParams;
|
|
vec4 shadowParams;
|
|
};
|
|
|
|
layout(push_constant) uniform Push {
|
|
mat4 model;
|
|
float waveAmp;
|
|
float waveFreq;
|
|
float waveSpeed;
|
|
float liquidBasicType;
|
|
} push;
|
|
|
|
layout(set = 1, binding = 0) uniform WaterMaterial {
|
|
vec4 waterColor;
|
|
float waterAlpha;
|
|
float shimmerStrength;
|
|
float alphaScale;
|
|
};
|
|
|
|
layout(set = 2, binding = 0) uniform sampler2D SceneColor;
|
|
layout(set = 2, binding = 1) uniform sampler2D SceneDepth;
|
|
layout(set = 2, binding = 2) uniform sampler2D ReflectionColor;
|
|
layout(set = 2, binding = 3) uniform ReflectionData {
|
|
mat4 reflViewProj;
|
|
};
|
|
|
|
layout(location = 0) in vec3 FragPos;
|
|
layout(location = 1) in vec3 Normal;
|
|
layout(location = 2) in vec2 TexCoord;
|
|
layout(location = 3) in float WaveOffset;
|
|
layout(location = 4) in vec2 ScreenUV;
|
|
|
|
layout(location = 0) out vec4 outColor;
|
|
|
|
// ============================================================
|
|
// Dual-scroll detail normals (multi-octave ripple overlay)
|
|
// ============================================================
|
|
vec3 dualScrollWaveNormal(vec2 p, float time) {
|
|
vec2 d1 = normalize(vec2(0.86, 0.51));
|
|
vec2 d2 = normalize(vec2(-0.47, 0.88));
|
|
vec2 d3 = normalize(vec2(0.32, -0.95));
|
|
float f1 = 0.19, f2 = 0.43, f3 = 0.72;
|
|
float s1 = 0.95, s2 = 1.73, s3 = 2.40;
|
|
float a1 = 0.22, a2 = 0.10, a3 = 0.05;
|
|
|
|
vec2 p1 = p + d1 * (time * s1 * 4.0);
|
|
vec2 p2 = p + d2 * (time * s2 * 4.0);
|
|
vec2 p3 = p + d3 * (time * s3 * 4.0);
|
|
|
|
float c1 = cos(dot(p1, d1) * f1);
|
|
float c2 = cos(dot(p2, d2) * f2);
|
|
float c3 = cos(dot(p3, d3) * f3);
|
|
|
|
float dHx = c1 * d1.x * f1 * a1 + c2 * d2.x * f2 * a2 + c3 * d3.x * f3 * a3;
|
|
float dHy = c1 * d1.y * f1 * a1 + c2 * d2.y * f2 * a2 + c3 * d3.y * f3 * a3;
|
|
|
|
return normalize(vec3(-dHx, -dHy, 1.0));
|
|
}
|
|
|
|
// ============================================================
|
|
// GGX/Cook-Torrance BRDF
|
|
// ============================================================
|
|
float DistributionGGX(vec3 N, vec3 H, float roughness) {
|
|
float a = roughness * roughness;
|
|
float a2 = a * a;
|
|
float NdotH = max(dot(N, H), 0.0);
|
|
float NdotH2 = NdotH * NdotH;
|
|
float denom = NdotH2 * (a2 - 1.0) + 1.0;
|
|
return a2 / (3.14159265 * denom * denom + 1e-7);
|
|
}
|
|
|
|
float GeometrySmith(float NdotV, float NdotL, float roughness) {
|
|
float r = roughness + 1.0;
|
|
float k = (r * r) / 8.0;
|
|
float ggx1 = NdotV / (NdotV * (1.0 - k) + k);
|
|
float ggx2 = NdotL / (NdotL * (1.0 - k) + k);
|
|
return ggx1 * ggx2;
|
|
}
|
|
|
|
vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
|
|
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
|
}
|
|
|
|
// ============================================================
|
|
// Linearize depth
|
|
// ============================================================
|
|
float linearizeDepth(float d, float near, float far) {
|
|
return near * far / (far - d * (far - near));
|
|
}
|
|
|
|
// ============================================================
|
|
// Noise functions for foam
|
|
// ============================================================
|
|
float hash21(vec2 p) {
|
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
|
}
|
|
|
|
float hash22x(vec2 p) {
|
|
return fract(sin(dot(p, vec2(269.5, 183.3))) * 43758.5453);
|
|
}
|
|
|
|
float noiseValue(vec2 p) {
|
|
vec2 i = floor(p);
|
|
vec2 f = fract(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
float a = hash21(i);
|
|
float b = hash21(i + vec2(1.0, 0.0));
|
|
float c = hash21(i + vec2(0.0, 1.0));
|
|
float d = hash21(i + vec2(1.0, 1.0));
|
|
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
|
}
|
|
|
|
float fbmNoise(vec2 p, float time) {
|
|
float v = 0.0;
|
|
v += noiseValue(p * 3.0 + time * 0.3) * 0.5;
|
|
v += noiseValue(p * 6.0 - time * 0.5) * 0.25;
|
|
v += noiseValue(p * 12.0 + time * 0.7) * 0.125;
|
|
return v;
|
|
}
|
|
|
|
// Voronoi-like cellular noise for foam particles
|
|
// jitter parameter controls how much cell points deviate from grid centers
|
|
// (0.0 = regular grid, 1.0 = fully random within cell)
|
|
float cellularFoam(vec2 p, float jitter) {
|
|
vec2 i = floor(p);
|
|
vec2 f = fract(p);
|
|
float minDist = 1.0;
|
|
for (int y = -1; y <= 1; y++) {
|
|
for (int x = -1; x <= 1; x++) {
|
|
vec2 neighbor = vec2(float(x), float(y));
|
|
vec2 cellId = i + neighbor;
|
|
// Jittered cell point — higher jitter = more irregular placement
|
|
vec2 point = vec2(hash21(cellId), hash22x(cellId)) * jitter
|
|
+ vec2(0.5) * (1.0 - jitter);
|
|
float d = length(neighbor + point - f);
|
|
minDist = min(minDist, d);
|
|
}
|
|
}
|
|
return minDist;
|
|
}
|
|
float cellularFoam(vec2 p) { return cellularFoam(p, 1.0); }
|
|
|
|
void main() {
|
|
float time = fogParams.z;
|
|
float basicType = push.liquidBasicType;
|
|
|
|
vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(SceneColor, 0));
|
|
|
|
// --- Normal computation ---
|
|
vec3 meshNorm = normalize(Normal);
|
|
vec3 detailNorm = dualScrollWaveNormal(FragPos.xy, time);
|
|
vec3 norm = normalize(mix(meshNorm, detailNorm, 0.55));
|
|
|
|
// Player interaction ripple normal perturbation
|
|
vec2 playerPos = vec2(shadowParams.z, shadowParams.w);
|
|
float rippleStrength = fogParams.w;
|
|
float d = length(FragPos.xy - playerPos);
|
|
float rippleEnv = rippleStrength * exp(-d * 0.12);
|
|
if (rippleEnv > 0.001) {
|
|
vec2 radialDir = (FragPos.xy - playerPos) / max(d, 0.01);
|
|
float dHdr = rippleEnv * 0.12 * (-0.12 * sin(d * 2.5 - time * 6.0) + 2.5 * cos(d * 2.5 - time * 6.0));
|
|
norm = normalize(norm + vec3(-radialDir * dHdr, 0.0));
|
|
}
|
|
|
|
vec3 viewDir = normalize(viewPos.xyz - FragPos);
|
|
vec3 ldir = normalize(-lightDir.xyz);
|
|
float NdotV = max(dot(norm, viewDir), 0.001);
|
|
float NdotL = max(dot(norm, ldir), 0.0);
|
|
|
|
float dist = length(viewPos.xyz - FragPos);
|
|
|
|
// --- Schlick Fresnel ---
|
|
const vec3 F0 = vec3(0.02);
|
|
float fresnel = F0.x + (1.0 - F0.x) * pow(1.0 - NdotV, 5.0);
|
|
|
|
// ============================================================
|
|
// Refraction (screen-space from scene history)
|
|
// ============================================================
|
|
vec2 refractOffset = norm.xy * (0.02 + 0.03 * fresnel);
|
|
vec2 refractUV = clamp(screenUV + refractOffset, vec2(0.001), vec2(0.999));
|
|
vec3 sceneRefract = texture(SceneColor, refractUV).rgb;
|
|
|
|
float sceneDepth = texture(SceneDepth, refractUV).r;
|
|
|
|
float near = 0.05;
|
|
float far = 30000.0;
|
|
float sceneLinDepth = linearizeDepth(sceneDepth, near, far);
|
|
float waterLinDepth = linearizeDepth(gl_FragCoord.z, near, far);
|
|
float depthDiff = max(sceneLinDepth - waterLinDepth, 0.0);
|
|
|
|
// Convert screen-space depth difference to approximate vertical water depth.
|
|
// depthDiff is along the view ray; multiply by the vertical component of
|
|
// the view direction so grazing angles don't falsely trigger shoreline foam
|
|
// on occluding geometry (bridges, posts) that isn't at the waterline.
|
|
float verticalFactor = abs(viewDir.z); // 1.0 looking straight down, ~0 at grazing
|
|
float verticalDepth = depthDiff * max(verticalFactor, 0.05);
|
|
|
|
// ============================================================
|
|
// Beer-Lambert absorption
|
|
// ============================================================
|
|
vec3 absorptionCoeff = vec3(0.46, 0.09, 0.06);
|
|
if (basicType > 0.5 && basicType < 1.5) {
|
|
absorptionCoeff = vec3(0.35, 0.06, 0.04);
|
|
}
|
|
vec3 absorbed = exp(-absorptionCoeff * verticalDepth);
|
|
|
|
// Underwater blue fog — geometry below the waterline fades to a blue haze
|
|
// with depth, masking occlusion edge artifacts and giving a natural look.
|
|
vec3 underwaterFogColor = waterColor.rgb * 0.5 + vec3(0.04, 0.10, 0.20);
|
|
float underwaterFogFade = 1.0 - exp(-verticalDepth * 0.35);
|
|
vec3 foggedScene = mix(sceneRefract, underwaterFogColor, underwaterFogFade);
|
|
|
|
vec3 shallowColor = waterColor.rgb * 1.2;
|
|
vec3 deepColor = waterColor.rgb * vec3(0.3, 0.5, 0.7);
|
|
float depthFade = 1.0 - exp(-verticalDepth * 0.15);
|
|
vec3 waterBody = mix(shallowColor, deepColor, depthFade);
|
|
|
|
vec3 refractedColor = mix(foggedScene * absorbed, waterBody, depthFade * 0.7);
|
|
|
|
if (verticalDepth < 0.01) {
|
|
float opticalDepth = 1.0 - exp(-dist * 0.004);
|
|
refractedColor = mix(foggedScene, waterBody, opticalDepth * 0.6);
|
|
}
|
|
|
|
vec3 litBase = waterBody * (ambientColor.rgb * 0.7 + NdotL * lightColor.rgb * 0.5);
|
|
refractedColor = mix(refractedColor, litBase, clamp(depthFade * 0.3, 0.0, 0.5));
|
|
|
|
// ============================================================
|
|
// Planar reflection — subtle, not mirror-like
|
|
// ============================================================
|
|
// reflWeight starts at 0; only contributes where we have valid reflection data
|
|
float reflAmount = 0.0;
|
|
vec3 envReflect = vec3(0.0);
|
|
|
|
vec4 reflClip = reflViewProj * vec4(FragPos, 1.0);
|
|
if (reflClip.w > 0.1) {
|
|
vec2 reflUV = reflClip.xy / reflClip.w * 0.5 + 0.5;
|
|
reflUV.y = 1.0 - reflUV.y;
|
|
reflUV += norm.xy * 0.015;
|
|
|
|
// Wide fade so there's no visible boundary — fully gone well inside the edge
|
|
float edgeFade = smoothstep(0.0, 0.15, reflUV.x) * smoothstep(1.0, 0.85, reflUV.x)
|
|
* smoothstep(0.0, 0.15, reflUV.y) * smoothstep(1.0, 0.85, reflUV.y);
|
|
|
|
reflUV = clamp(reflUV, vec2(0.002), vec2(0.998));
|
|
vec3 texReflect = texture(ReflectionColor, reflUV).rgb;
|
|
|
|
float reflBrightness = dot(texReflect, vec3(0.299, 0.587, 0.114));
|
|
float reflValidity = smoothstep(0.002, 0.05, reflBrightness) * edgeFade;
|
|
|
|
envReflect = texReflect * 0.5;
|
|
reflAmount = reflValidity * 0.4;
|
|
}
|
|
|
|
// ============================================================
|
|
// GGX Specular
|
|
// ============================================================
|
|
float roughness = 0.18;
|
|
vec3 halfDir = normalize(ldir + viewDir);
|
|
float D = DistributionGGX(norm, halfDir, roughness);
|
|
float G = GeometrySmith(NdotV, NdotL, roughness);
|
|
vec3 F = fresnelSchlickRoughness(max(dot(halfDir, viewDir), 0.0), F0, roughness);
|
|
vec3 specular = (D * G * F) / (4.0 * NdotV * NdotL + 0.001) * lightColor.rgb * NdotL;
|
|
specular = min(specular, vec3(2.0));
|
|
|
|
// Noise-based sparkle
|
|
float sparkleNoise = fbmNoise(FragPos.xy * 4.0 + time * 0.5, time * 1.5);
|
|
float sparkle = pow(max(sparkleNoise - 0.55, 0.0) / 0.45, 3.0) * shimmerStrength * 0.10;
|
|
specular += sparkle * lightColor.rgb;
|
|
|
|
// ============================================================
|
|
// Subsurface scattering
|
|
// ============================================================
|
|
float sssBase = pow(max(dot(viewDir, -ldir), 0.0), 4.0);
|
|
float sss = sssBase * max(0.0, WaveOffset * 3.0) * 0.25;
|
|
vec3 sssColor = vec3(0.05, 0.55, 0.35) * sss * lightColor.rgb;
|
|
|
|
// ============================================================
|
|
// Combine — reflection only where valid, no dark fallback
|
|
// ============================================================
|
|
// reflAmount is 0 where no valid reflection data exists — no dark arc
|
|
float reflectWeight = clamp(fresnel * reflAmount, 0.0, 0.30);
|
|
vec3 color = mix(refractedColor, envReflect, reflectWeight);
|
|
color += specular + sssColor;
|
|
|
|
float crest = smoothstep(0.5, 1.0, WaveOffset) * 0.04;
|
|
color += vec3(crest);
|
|
|
|
// ============================================================
|
|
// Shoreline foam — scattered particles, not smooth bands
|
|
// Only on terrain water (waveAmp > 0); WMO water (canals, indoor)
|
|
// has waveAmp == 0 and should not show shoreline interaction.
|
|
// ============================================================
|
|
if (basicType < 1.5 && verticalDepth > 0.01 && push.waveAmp > 0.0) {
|
|
float foamDepthMask = 1.0 - smoothstep(0.0, 1.8, verticalDepth);
|
|
|
|
// Warp UV coords with noise to break up cellular regularity
|
|
vec2 warpOffset = vec2(
|
|
noiseValue(FragPos.xy * 2.5 + time * 0.08) - 0.5,
|
|
noiseValue(FragPos.xy * 2.5 + vec2(37.0) + time * 0.06) - 0.5
|
|
) * 1.6;
|
|
vec2 foamUV = FragPos.xy + warpOffset;
|
|
|
|
// Fine scattered particles
|
|
float cells1 = cellularFoam(foamUV * 14.0 + time * vec2(0.15, 0.08));
|
|
float foam1 = (1.0 - smoothstep(0.0, 0.12, cells1)) * 0.45;
|
|
|
|
// Tiny spray dots
|
|
float cells2 = cellularFoam(foamUV * 28.0 + time * vec2(-0.12, 0.22));
|
|
float foam2 = (1.0 - smoothstep(0.0, 0.07, cells2)) * 0.3;
|
|
|
|
// Micro specks
|
|
float cells3 = cellularFoam(foamUV * 50.0 + time * vec2(0.25, -0.1));
|
|
float foam3 = (1.0 - smoothstep(0.0, 0.05, cells3)) * 0.18;
|
|
|
|
// Noise breakup for clumping
|
|
float noiseMask = noiseValue(FragPos.xy * 3.0 + time * 0.15);
|
|
float foam = (foam1 + foam2 + foam3) * foamDepthMask * smoothstep(0.3, 0.6, noiseMask);
|
|
|
|
foam *= smoothstep(0.0, 0.1, verticalDepth);
|
|
// Bluer foam tint instead of near-white
|
|
color = mix(color, vec3(0.68, 0.78, 0.88), clamp(foam, 0.0, 0.40));
|
|
}
|
|
|
|
// ============================================================
|
|
// Wave crest foam (ocean only) — particle-based
|
|
// ============================================================
|
|
if (basicType > 0.5 && basicType < 1.5 && push.waveAmp > 0.0) {
|
|
float crestMask = smoothstep(0.5, 1.0, WaveOffset);
|
|
vec2 crestWarp = vec2(
|
|
noiseValue(FragPos.xy * 1.8 + time * 0.1) - 0.5,
|
|
noiseValue(FragPos.xy * 1.8 + vec2(53.0) + time * 0.07) - 0.5
|
|
) * 2.0;
|
|
float crestCells = cellularFoam((FragPos.xy + crestWarp) * 6.0 + time * vec2(0.12, 0.08));
|
|
float crestFoam = (1.0 - smoothstep(0.0, 0.18, crestCells)) * crestMask;
|
|
float crestNoise = noiseValue(FragPos.xy * 3.0 - time * 0.3);
|
|
crestFoam *= smoothstep(0.3, 0.6, crestNoise);
|
|
color = mix(color, vec3(0.68, 0.78, 0.88), crestFoam * 0.30);
|
|
}
|
|
|
|
// ============================================================
|
|
// Alpha and fog
|
|
// ============================================================
|
|
float baseAlpha = mix(waterAlpha, min(1.0, waterAlpha * 1.5), depthFade);
|
|
float alpha = mix(baseAlpha, min(1.0, baseAlpha * 1.3), fresnel) * alphaScale;
|
|
alpha *= smoothstep(1600.0, 350.0, dist);
|
|
alpha = clamp(alpha, 0.15, 0.92);
|
|
|
|
float fogFactor = clamp((fogParams.y - dist) / (fogParams.y - fogParams.x), 0.0, 1.0);
|
|
color = mix(fogColor.rgb, color, fogFactor);
|
|
|
|
outColor = vec4(color, alpha);
|
|
}
|