HomeArticlesVFX

Stable Fluids: How Games Simulate Smoke in Real Time

Drag your mouse to inject smoke into Jos Stam's Stable Fluids solver and watch vorticity confinement curl it into swirling plumes.

mysimulator teamUpdated June 2026≈ 9 min read▶ Open the simulation

Why naive fluid solvers explode

The Navier-Stokes equations describe how incompressible fluids move, and the textbook way to simulate them numerically is an explicit finite-difference scheme: advance velocity forward by a small time step, using the current field's own derivatives. Explicit schemes like that are only stable if the time step stays below a limit set by the CFL condition — roughly, nothing may cross more than one grid cell per step — and honouring that limit at real-time frame rates would demand hundreds of substeps per frame on any reasonably fine grid. Jos Stam's 1999 SIGGRAPH paper Stable Fluids sidesteps the whole problem: it achieves stability at any time step, however large, by replacing forward integration of velocity with something that literally cannot blow up — interpolation.

live demo · smoke injected and advected by a stable-fluids grid● LIVE

The three-step solve

Every frame of a Stable Fluids simulation runs the same short pipeline on its velocity grid: apply external forces, move the field along itself, then correct it so mass is conserved.

addForce(w, dt)     // buoyancy, an injected smoke source, wind
advect(w, w, dt)    // semi-Lagrangian: trace back, bilinear sample
project(w)          // enforce ∇·w = 0 — incompressibility

Semi-Lagrangian advection: trading accuracy for stability

Instead of asking "where does this cell's content go next," semi-Lagrangian advection asks the reverse question for every cell: "where did this cell's content come from?" It traces a virtual particle backward through the current velocity field by one time step, then reads off the previous frame's value at that point using bilinear interpolation:

for each cell x:
  x0    = x - dt * velocity(x)        // trace backward one time step
  q(x)  = bilinearSample(q_prev, x0)  // read the previous field there

Because the update is nothing more than sampling an existing field, it cannot amplify a value beyond what was already present — there is no feedback loop for an error to grow through, so the method is unconditionally stable regardless of how large the time step is. The price is numerical diffusion: bilinear interpolation quietly averages away fine detail every single step, so simulated smoke visibly smears and loses sharp structure faster than real smoke would.

The projection step: making it incompressible

The Helmholtz-Hodge decomposition says any vector field can be split into a divergence-free part and the gradient of a scalar. The projection step exploits that directly: it solves a Poisson equation for a scalar pressure field, then subtracts that pressure's gradient from the velocity field, leaving a result with zero divergence — the discrete equivalent of "no fluid is being created or destroyed at any point."

∇²p = ∇·w        // Poisson equation for pressure
w   = w - ∇p     // subtract the pressure gradient → divergence-free velocity

The Poisson equation is solved iteratively on the grid — Gauss-Seidel or Jacobi relaxation is standard — typically 20 to 40 iterations are enough for visually stable, incompressible-looking smoke at interactive frame rates.

Getting the curl back: vorticity confinement

The one real cost of this stable, diffusive scheme is that the fine swirling vortices a real smoke plume develops get smoothed away by repeated interpolation, leaving the result looking flat and mushy instead of turbulent. Fedkiw, Stam and Jensen's 2001 vorticity confinement technique measures the local curl of the velocity field, then adds a small artificial force that pushes fluid toward regions of higher vorticity magnitude — deliberately reinjecting the rotational energy the numerical scheme keeps dissipating, without needing a much finer, far more expensive grid.

ω      = ∇ × w                       // vorticity — the curl of velocity
N      = ∇|ω| / |∇|ω||               // normalised gradient of vorticity magnitude
f_conf = ε * (N × ω)                 // confinement force added back into w

Why this method still dominates real-time VFX

Unconditional stability at large time steps on a coarse grid is exactly what makes interactive smoke and fire possible in games and browser demos at 60 frames per second at all. Offline film production, using Houdini's FLIP or particle-based solvers, runs at far higher resolution with more physically accurate schemes because it does not have a real-time budget to respect. But the same core recipe — semi-Lagrangian advection, a pressure projection step, and vorticity confinement to restore lost swirl — at higher resolution and with more substeps, still underlies most GPU smoke and fire solvers used in production today, more than two decades after Stam's original paper.

Frequently asked questions

Why is semi-Lagrangian advection unconditionally stable?

It never integrates a forward difference equation that can blow up. Instead it traces each cell backward through the velocity field and interpolates the previous frame's values at that point, and interpolation can only average existing values, never amplify them, no matter how large the time step is.

Why does the simulated smoke look softer than real smoke?

Every semi-Lagrangian step resamples the field with bilinear interpolation, which smooths away fine detail a little more each frame — a numerical side effect called artificial diffusion. Vorticity confinement injects some of that lost swirl back in, but it only partially compensates for the smoothing.

What does the projection step actually enforce?

Incompressibility. It solves a Poisson equation for a scalar pressure field and subtracts its gradient from the velocity field, leaving a result with zero divergence — matching the physical fact that a gas or liquid does not locally compress or expand under typical simulated conditions.

Try it live

Everything above runs in your browser — open Smoke VFX — Stable Fluids and change the parameters while it is running. Nothing is installed, nothing is uploaded, the whole model lives in one tab.

▶ Open Smoke VFX — Stable Fluids simulation

What did you find?

Add reproduction steps (optional)