Liquid Distortion
A liquid effect that does not simulate water so much as it borrows the one thing water makes visible: momentum.
- Creative Dev
- Solo
- 2024
The Brief
I wanted a cursor effect that felt wet without turning into a full fluid solver. Something that could sit behind type, bend the image, and make fast movement feel different from slow movement.
The first versions were too literal: circles following the cursor, displacement tied directly to pointer position, a ripple that looked the same no matter how you moved. It reacted, but it had no memory.
The effect started working when I stopped asking where the cursor is and started asking how hard it moved this frame.
Momentum, Not Position
The simulation field stores velocity in a texture. Every frame, the pointer's displacement from its last position becomes the force injected into that field. A quick flick writes a stronger vector; a slow drag writes a softer one.
Measure movement
Mouse velocity is just current UV minus last UV, scaled and clamped so fast swipes stay expressive without blowing up the field.
Splat into the field
A radial falloff injects that vector around the cursor. Aspect correction keeps the ripple circular inside any frame.
Let it settle
Dissipation and a small diffusion step make the vectors smear and fade instead of snapping back to zero.
The Shader Pipeline
There are two passes. The simulation pass evolves the velocity field in a pair of ping-pong render targets. The display pass samples that field and uses it to offset a procedural canvas texture.
The content being distorted is intentionally simple: a gradient, a few color blooms, a dot grid, and large type. The dot grid makes small refractions legible, while the color blooms give the chromatic split something to bite into.
The field is not the image. The field is a map of motion. The image only becomes liquid when the display shader reads that map and bends the pixels through it.
The Playground
Swipe through the canvas, then change the controls. Trail controls how long the velocity field survives, distortion controls how far the texture bends, and ripple size controls how wide each pointer splat is.
The cursor writes velocity into a low-resolution field, then the display shader bends a procedural texture through that field. The trick is using movement delta as force — position alone only creates a spotlight, not liquid drag.
// sim pass — velocity field in a ping-pong render target
vec2 v = texture2D(uPrev, vUv - prevV * uAdvect).xy;
v = mix(v, neighborAverage, 0.14);
v *= uDissipation;
vec2 d = vUv - uMouse;
d.x *= uAspect;
float fall = exp(-dot(d, d) / uRadius);
v += uVel * fall;
// display pass — refract content through the field
vec2 field = texture2D(uField, vUv).xy;
vec2 uv = vUv + field * uStrength;
vec3 col = vec3(
texture2D(uTex, uv + field * uStrength * 0.6).r,
texture2D(uTex, uv).g,
texture2D(uTex, uv - field * uStrength * 0.6).b
);Where It Landed
- 2
- 0.5x
- 3
The final version is deliberately not a physically complete fluid sim. It is a smaller illusion: advect a velocity texture, let it decay, and use that texture as a lens. That made it cheap enough for an article embed while still feeling responsive.
The lesson was that believable interaction often comes from preserving the right state. Here, one frame of cursor history was the difference between a hover effect and something that felt like liquid.