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