12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
// Eldenwood river water, v3.
// Two-octave Gerstner displacement in the vertex stage, depth-tinted
// refraction plus screen-space foam in the fragment stage.
// Compile with WATER_VERTEX or WATER_FRAGMENT defined.
#version 330 core
uniform mat4 u_viewProj;
uniform mat4 u_model;
uniform float u_time;
uniform vec3 u_cameraPos;
uniform vec3 u_sunDir; // normalized, world space
uniform vec3 u_depthTint; // from biomes.toml [river].depth_tint
uniform float u_flowSpeed;
uniform float u_foamThreshold;
uniform sampler2D u_sceneColor; // refraction source
uniform sampler2D u_sceneDepth; // linearized scene depth
// Gerstner wave: returns displacement for a given direction/steepness.
vec3 gerstner(vec2 pos, vec2 dir, float steep, float wavelen, float t) {
float k = 6.28318530 / wavelen;
float c = sqrt(9.8 / k); // deep-water phase speed
float f = k * (dot(dir, pos) - c * t * u_flowSpeed);
float a = steep / k;
return vec3(dir.x * a * cos(f), a * sin(f), dir.y * a * cos(f));
}
#ifdef WATER_VERTEX
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec2 a_uv;
out vec3 v_worldPos;
out vec2 v_uv;
out float v_crest; // wave height factor for foam
void main() {
vec3 world = (u_model * vec4(a_position, 1.0)).xyz;
vec3 d = vec3(0.0);
d += gerstner(world.xz, normalize(vec2(1.0, 0.3)), 0.18, 7.0, u_time);
d += gerstner(world.xz, normalize(vec2(-0.4, 1.0)), 0.10, 3.1, u_time);
world += d;
v_worldPos = world;
v_uv = a_uv;
v_crest = clamp(d.y * 4.0 + 0.5, 0.0, 1.0);
gl_Position = u_viewProj * vec4(world, 1.0);
}
#endif
#ifdef WATER_FRAGMENT
in vec3 v_worldPos;
in vec2 v_uv;
in float v_crest;
out vec4 fragColor;
void main() {
vec3 viewDir = normalize(u_cameraPos - v_worldPos);
// Cheap animated normal: two scrolling sine gradients, no texture fetch.
vec2 p = v_worldPos.xz * 0.8;
float t = u_time * u_flowSpeed;
vec3 n = normalize(vec3(
sin(p.x * 2.1 + t) * 0.08 + sin(p.y * 3.7 - t * 1.3) * 0.05,
1.0,
cos(p.y * 1.9 + t * 0.7) * 0.08));
// Schlick fresnel against air/water (F0 = 0.02).
float fresnel = 0.02 + 0.98 * pow(1.0 - max(dot(n, viewDir), 0.0), 5.0);
// Refraction: offset the scene color lookup by the normal's xz.
vec2 screenUV = gl_FragCoord.xy / vec2(textureSize(u_sceneColor, 0));
vec3 refracted = texture(u_sceneColor, screenUV + n.xz * 0.03).rgb;
// Depth tint: deeper water absorbs more of the refracted light.
float sceneD = texture(u_sceneDepth, screenUV).r;
float depth = clamp((sceneD - gl_FragCoord.z) * 60.0, 0.0, 1.0);
vec3 body = mix(refracted, u_depthTint, depth * 0.85);
// Sun glint plus crest foam.
float glint = pow(max(dot(reflect(-u_sunDir, n), viewDir), 0.0), 240.0);
float foam = smoothstep(u_foamThreshold, 1.0, v_crest);
vec3 color = mix(body, vec3(0.92, 0.96, 0.94), foam) + glint * 0.6;
fragColor = vec4(color, mix(0.75, 1.0, fresnel));
}
#endif