馃嵑 tavern Version control for worlds too big for Git DocsSign in
water.glsl3 KB
Raw
// 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