Advanced shaders · lesson 18 of 19 · Advanced · about 5 min

Water simulation 1: one drop

You will build: Make a ring that spreads from a drop at the centre: a wave packet that fades with time. Show a stony pond bed through the water, bent by the surface slope, with a sparkle on the ripples.

The picture produced by the shader in the lesson "Water simulation 1: one drop"
The target picture. In the lesson you write the shader that draws it, and a match bar shows how close you are.

A drop falls into a still pond. A ring spreads out, and as it travels it gets weaker and a little wider. We can describe that ring directly instead of simulating the water, as a wave packet: a short burst of sine wave that moves outwards.

Let r be the distance from the drop and age the time since it fell. The front of the ring is at age * 0.28 (the wave speed). Let x = r - front: how far this pixel is ahead of the ring (positive) or behind it (negative).

  • The wiggles: sin(x * 55.0).
  • Only near the front: exp(-x * x * 140.0) is a bell curve around x = 0, so the sine only shows in a narrow band: a packet.
  • Weaker over time: exp(-age * 0.75).
  • Nothing in front of the ring yet: step(0.0, x + 0.12).

To make the drop repeat, age = fmod(time, 4.0).

The picture is the pond seen from above, with a stony bed visible through the water. How does it look wet? From the slope of the surface. Measure the height at four neighbours (finite differences again) and get the slope g. The slope does two things: it bends the view of the bed (refraction: look up the bed at p + g * 0.5 instead of p) and it makes sparkles where the surface tilts towards the light.

float x = r - age * 0.28;
float wave = sin(x * 55.0) * exp(-x * x * 140.0) * exp(-age * 0.75);
return wave * 0.016 * step(0.0, x + 0.12);

The numbers you need

Everything the picture depends on is given, so the challenge is the code, not guessing.

Drop
height(p): age = fmod(time, 4.0); r = length(p); x = r - age * 0.28; wave = sin(x * 55.0) * exp(-x * x * 140.0) * exp(-age * 0.75); return wave * 0.016 * step(0.0, x + 0.12)
Slope
p = (uv - 0.5) * float2(aspect, 1.0); e = 0.004; g = float2(h(p + (e, 0)) - h(p - (e, 0)), h(p + (0, e)) - h(p - (0, e))) / (2.0 * e)
Bent view
q = p + g * 0.5
Pond bed
stones = fbm2(q * 6.0); bed = lerp(float3(0.05, 0.2, 0.26), float3(0.32, 0.55, 0.5), smoothstep(0.35, 0.7, stones)); bed *= 0.8 + 0.5 * noise2(q * 20.0)
Normal and light
n = normalize(float3(-g * 0.8, 1.0)); L = normalize(float3(-0.4, 0.5, 0.8)); spec = pow(max(dot(reflect(-L, n), float3(0.0, 0.0, 1.0)), 0.0), 50.0)
Sky sheen
sheen = pow(1.0 - n.z, 1.0) * 6.0; col = lerp(bed, float3(0.55, 0.78, 0.92), saturate(sheen) * 0.5)
Final
col += float3(1.0, 1.0, 1.0) * spec * 0.9

Try it in your own language

The lesson is written once and shown in every language. Pick yours: Unity (Built-in), Unity (URP), Unreal, Shadertoy / WebGL, OpenGL (LWJGL, raylib), three.js, GameMaker, LÖVE (Lua), Godot, Apple (iOS, macOS), WebGPU / Bevy.

Start coding