0.00
60.0 fps

Ocean Waves

splashy

Log in to post a comment.

precision highp float;

uniform float iTime;
uniform vec2  iResolution;
uniform vec3  sunDir;            // normalized direction to sun
uniform vec3  sunColor;          // sun color (e.g. slightly yellowish)
uniform float sunAngularRadius;  // sun disc radius in radians

// index of refraction (air → water)
const float eta = 1.0 / 1.333;

// seabed parameters
const float seaDepth = 2.0;
const vec3 seabedColor = vec3(0.8, 0.7, 0.5);

// Gerstner-wave parameters: vec4(dirX, dirZ, wavelength, amplitude)
const int NUM_WAVES = 5;
vec4 waves[NUM_WAVES];

void initWaves() {
    waves[0] = vec4( 1.0,  0.0, 6.0, 0.15);
    waves[1] = vec4( 0.8,  0.6, 3.0, 0.05);
    waves[2] = vec4(-0.6,  0.8, 2.0, 0.025);
    waves[3] = vec4(-0.8, -0.4, 4.0, 0.08);
    waves[4] = vec4( 0.4, -0.9, 1.5, 0.02);
}

vec3 gerstner(vec2 p) {
    float height = 0.0;
    vec2 disp = vec2(0.0);
    for (int i = 0; i < NUM_WAVES; i++) {
        vec4 w = waves[i];
        vec2 dir = normalize(w.xy);
        float k = 2.0 * 3.14159 / w.z;
        float phase = k * dot(dir, p) + iTime * sqrt(9.8 * k);
        float amp = w.w;
        height += sin(phase) * amp;
        disp   += dir * (cos(phase) * amp);
    }
    return vec3(p + disp, height);
}

vec3 getNormal(vec2 p) {
    vec3 n = vec3(0.0);
    for (int i = 0; i < NUM_WAVES; i++) {
        vec4 w = waves[i];
        vec2 dir = normalize(w.xy);
        float k = 2.0 * 3.14159 / w.z;
        float phase = k * dot(dir, p) + iTime * sqrt(9.8 * k);
        float amp = w.w;
        n.x += -dir.x * amp * k * cos(phase);
        n.z += -dir.y * amp * k * cos(phase);
        n.y += 1.0;
    }
    return normalize(n);
}

bool intersectSea(vec3 ro, vec3 rd, out vec3 pos, out vec3 nor) {
    float t = 0.0;
    for (int i = 0; i < 80; i++) {
        pos = ro + rd * t;
        vec3 gp = gerstner(pos.xz);
        float d = pos.y - gp.z;
        if (d < 0.001) {
            nor = getNormal(gp.xy);
            pos = vec3(gp.xy, gp.z);
            return true;
        }
        t += d * 0.7;
        if (t > 100.0) break;
    }
    return false;
}

// sample sky color with sun disc
vec3 skyColor(vec3 rd) {
    float t = clamp(rd.y * 0.5 + 0.5, 0.0, 1.0);
    vec3 base = mix(vec3(0.8, 0.9, 1.0), vec3(0.4, 0.6, 0.9), t);

    float cosA = dot(rd, sunDir);
    float mask = smoothstep(sunAngularRadius * 0.9, sunAngularRadius, cosA);
    base += sunColor * mask;

    return base;
}

void main() {
    initWaves();
    vec2 uv = (gl_FragCoord.xy * 2.0 - iResolution) / iResolution.y;

    // camera with higher altitude and dynamic orbit
    float camAngle = iTime * 0.2;
    float camHeight = 4.0 + sin(iTime * 0.5) * 1.0;            // lift camera up and bob
    float radius    = 5.0 + sin(iTime * 0.3) * 1.5;            // vary distance slightly
    vec3 camPos = vec3(
        sin(camAngle) * radius,
        camHeight,
        cos(camAngle * 1.3) * radius
    );
    vec3 target = vec3(0.0);
    vec3 fwd    = normalize(target - camPos);
    vec3 right  = normalize(cross(fwd, vec3(0.0, 1.0, 0.0)));
    vec3 up     = cross(right, fwd);
    vec3 rd     = normalize(uv.x * right + uv.y * up + 1.5 * fwd);

    vec3 col;
    vec3 pos, nor;
    if (intersectSea(camPos, rd, pos, nor)) {
        // reflection + specular
        vec3 refl = reflect(rd, nor);
        vec3 colR = skyColor(refl);
        vec3 halfVec = normalize(-rd + sunDir);
        float spec = pow(max(dot(nor, halfVec), 0.0), 64.0);
        colR += sunColor * spec;

        // refraction
        vec3 refr = refract(rd, nor, eta);
        float tDist = (pos.y + seaDepth) / -refr.y;
        vec3 hit = pos + refr * tDist;
        float depth = length(hit - pos);
        float atten = exp(-depth * 0.5);

        // seabed lighting
        float diff = max(dot(vec3(0.0, 1.0, 0.0), -sunDir), 0.0);
        vec3 sunLit = seabedColor * diff * atten;
        vec3 colT   = mix(sunLit, vec3(0.0, 0.1, 0.2), 1.0 - atten);

        // Fresnel blend
        float fres = pow(1.0 - max(dot(-rd, nor), 0.0), 3.0);
        col = mix(colT, colR, fres);
    } else {
        col = skyColor(rd);
    }

    gl_FragColor = vec4(col, 1.0);
}