A 2D velocity field evolves under the Navier-Stokes equations, based on Jos Stam's "Stable Fluids" (1999), the paper that made real-time fluid simulation practical. The key insight is semi-Lagrangian advection: instead of pushing fluid forward (which explodes), you trace each cell backward through the velocity field and sample where it came from. This is unconditionally stable regardless of timestep or velocity magnitude.
Each frame runs four stages: advect velocity through itself, compute the divergence of the result, solve for pressure via Jacobi iteration, then subtract the pressure gradient to project the field back to divergence-free. This last step is the Helmholtz-Hodge decomposition: any vector field splits uniquely into a divergence-free part and a curl-free (gradient) part. Subtracting the gradient part enforces incompressibility.
The pressure solver runs 25 Jacobi iterations per frame, a tradeoff between accuracy and speed. More iterations produce cleaner incompressibility but cost proportionally more fragment shader passes. Vorticity confinement is applied after the pressure projection to re-inject rotational energy lost to numerical dissipation, keeping small-scale swirls alive instead of letting them smear into laminar flow.
Moving your mouse injects velocity and dye as Gaussian splats. Dye rides the velocity field as a passive tracer, revealing the flow structure. Every operation is a fullscreen fragment shader pass. No CPU physics. Six shader programs, eight framebuffers, one triangle. Watch for vortex shedding behind fast-moving injections and the way dye filaments stretch and fold into fractal-like structures.
Wikipedia