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); }