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