home

Gerstner Waves with Dynamic Lighting

Gerstner waves is a simple yet effective way to simuate the motion of water waves. This implementation uses a flat plane geometry with the gerstner waves produced directly in the vertex shader.

vertex.glsl
vec3 gertsnerWave(vec4 wave, vec3 point, inout vec3 tangent, inout vec3 binormal){
vec2 d = normalize(wave.xy);
float s = wave.z; // steepness
float lambda = wave.w; // wavelength
float k = 2.0 * PI / lambda;
float c = sqrt(1.0 / k); // Should be 9.81 / k but reduced for slower speed.
float f = k * (dot(d, point.xz) - (c * u_Time));
// ...

Since the motion of the wave is explicitly defined, the normals can be calculated to allow for correct lighting and shadows.

vertex.glsl
vec3 gertsnerWave(vec4 wave, vec3 point, inout vec3 tangent, inout vec3 binormal){
// ...
// Compute normals, then pass to the fragment shader
tangent += vec3(-d.x * d.x * (s * sin(f)),
d.x * (s * cos(f)),
-d.x * d.y * (s * sin(f)));
binormal += vec3(-d.x * d.y * (s * sin(f)),
d.y * (s * cos(f)),
-d.y * d.y * (s * sin(f)));
return vec3(d.x * (s / k * cos(f)),
s / k * sin(f),
d.y * (s / k * cos(f)));
}
v_Normal = normalize(cross(binormal, tangent));
fragment.glsl
vec3 lightReflection(vec3 lightColor, vec3 surfaceToLight){
// Normalise v_Normal
vec3 normal = normalize(v_Normal);
// Set ambient, calculate dot product for light levels on normal faces
vec3 ambient = lightColor;
vec3 diffuse = lightColor * dot(surfaceToLight, normal);
return (ambient + diffuse);
}