Water is always moving.
Sometimes it forms gentle ripples across a lake.
Sometimes it crashes into waves.
Sometimes it reflects the sky so clearly that it almost looks like glass.
Although real water is incredibly complex, we can recreate many of its characteristics using simple mathematics.
Most procedural water begins with four simple ingredients.
Smooth waves.
Subtle distortion.
Continuous animation.
A cool color gradient.
The combination of these four ideas creates surprisingly convincing results.
We'll begin with our familiar coordinates.
vec2 uv = vUv;
Everything in the shader will be built from these coordinates.
The easiest way to make water is with a sine wave.
float wave =
sin(
uv.x * 12.0
);
This creates evenly spaced ripples.
At the moment they are perfectly straight.
Real water is never this perfect.
Water is always moving.
We simply shift the wave over time.
float wave =
sin(
uv.x * 12.0 +
uTime * 2.0
);
The ripples now travel smoothly across the screen.
Perfect waves still look artificial.
Let's distort them with FBM.
float n =
fbm(
uv * 3.0
);
This creates smooth variation.
Add the noise into the sine function.
float water =
sin(
uv.x * 12.0 +
n * 4.0 +
uTime * 2.0
);
The waves now bend naturally instead of remaining perfectly straight.
Real water contains many overlapping waves.
Create another one.
water +=
sin(
uv.y * 18.0 -
uTime * 1.5
) * 0.5;
The two wave systems interact to create a richer surface.
Bring everything back into the familiar range.
water =
water * 0.5 +
0.5;
Now the values are ready to use for colors.
#ifdef GL_ES
precision mediump float;
#endif
uniform float uTime;
varying vec2 vUv;
void main(){
vec2 uv = vUv;
float n = fbm(uv * 3.0);
float water =
sin(
uv.x * 12.0 +
n * 4.0 +
uTime * 2.0
);
water +=
sin(
uv.y * 18.0 -
uTime * 1.5
) * 0.5;
water = water * 0.5 + 0.5;
gl_FragColor = vec4(vec3(water),1.0);
}
Even without colors, the animation already resembles moving water.
Now apply a blue gradient.
vec3 color = mix(
vec3(0.05,0.15,0.45),
vec3(0.35,0.75,1.0),
water
);
Dark blue represents deeper areas.
Light blue highlights the wave peaks.
Reduce the animation speed.
uTime * 0.8
The water now resembles a quiet lake.
Increase the FBM scale.
fbm(
uv * 6.0
);
The waves become much more detailed.
Instead of moving only the waves, move the coordinates.
uv.x +=
uTime * 0.1;
The entire water surface begins to drift naturally.
Procedural water appears everywhere.
Many professional water shaders begin with nothing more than animated sine waves and procedural noise.
Create gentle ripples.
Create stormy water.
Increase the distortion.
Slow the animation.
Speed it up.
Change the color gradient.
Combine three wave layers instead of two.
Observe how each adjustment changes the feeling of the water.
Can you create these effects?
A peaceful lake.
Ocean waves.
A magical glowing river.
Rain ripples.
A moving underwater background.
Every one of these begins with the same mathematical building blocks.