SPH Particle Fluids

Splash a bucket of water across a floor and it does something no polygon mesh ever wants to do: it tears apart, throws off droplets, sheets into a thin film, then puddles and merges again. The topology changes every frame. One way to tame this chaos for a film shot is to stop thinking of the water as a shape at all, and think of it as a crowd of tiny particles — each carrying a little mass, each nudging its neighbours — that together behave like a fluid.

That is Smoothed Particle Hydrodynamics (SPH): the Lagrangian (particle-following) way to simulate liquids, as opposed to the Eulerian (fixed-grid) way of Stable Fluids. Born in 1977 for simulating stars in astrophysics, SPH turned out to be a wonderfully natural fit for the splashy, merging, tearing liquids that grids find awkward. This page builds the SPH idea from its one central trick — the kernel-weighted sum — up to a working pressure force, and is honest about the two things it makes you pay for.

The one idea: estimate any field by a weighted sum over neighbours

In SPH the fluid is the particles. Each particle j carries a mass m_j, a position \mathbf{x}_j, a velocity, and whatever field values it samples (density, pressure, colour…). The magic is a recipe for reading off the value of any field A at any point in space, even where no particle sits, by a smoothed average of the nearby particles:

A(\mathbf{x}) \;=\; \sum_{j} m_j \, \frac{A_j}{\rho_j} \, W(\mathbf{x} - \mathbf{x}_j,\, h).

Here \rho_j is particle j's density and W is the smoothing kernel: a smooth, bump-shaped weight that is largest at the centre and falls to exactly zero beyond a support radius h. So the sum is really only over the handful of particles within h of \mathbf{x} — near neighbours count a lot, far ones not at all, and everything past h contributes nothing. The factor m_j/\rho_j is just the little volume that particle j represents, so the formula is a volume-weighted, blurred sample of the field.

That last point is the quiet superpower. In a grid method you approximate gradients with finite differences between cells; in SPH the gradient of a field is a sum against the analytic gradient of a smooth kernel you chose. Pressure gradients, viscosity Laplacians — all become sums over neighbours of \nabla W or \nabla^2 W.

The neighbourhood picture

Everything in SPH is local. To evaluate any quantity at particle i you look only inside a disc (a sphere in 3-D) of radius h centred on it. The dark points below sit inside that radius and contribute to i's sums; the faint points are farther than h away and the kernel zeroes them out entirely.

Choosing h is a real trade-off: too small and a particle has too few neighbours, so its sums are noisy and lumpy; too large and every particle sees hundreds of others and the simulation crawls. A common target is a few dozen neighbours inside the radius. And because the sums only ever touch nearby particles, the whole cost hinges on being able to find those neighbours quickly — which is why SPH is glued to a neighbour search structure (more below).

From particles to a force: density → pressure → motion

The loop that makes a puddle move has three beats each step. First, set A = \rho in the master formula. The \rho_j and the A_j = \rho_j cancel, leaving the beautifully simple density estimate:

\rho_i \;=\; \sum_{j} m_j \, W(\mathbf{x}_i - \mathbf{x}_j,\, h).

A particle is dense where its neighbours crowd in close (the kernel is large) and light where they spread out. Second, turn that density into a pressure with a stiff equation of state — the water pushes back when squeezed past its rest density \rho_0:

p_i \;=\; k\,(\rho_i - \rho_0),

where k is a stiffness constant. Third, drive the particles with the pressure force, which pushes fluid from high pressure to low — the negative gradient of pressure, evaluated as a kernel sum (a symmetrised form keeps the forces equal-and-opposite):

\mathbf{f}^{\text{pressure}}_i \;=\; -\sum_{j} m_j \, \frac{p_i + p_j}{2\,\rho_j}\, \nabla W(\mathbf{x}_i - \mathbf{x}_j,\, h).

Add a viscosity force (a kernel Laplacian of velocity that smooths neighbouring velocities together — this is the fluid's internal friction) and a surface-tension force (which pulls the ragged boundary inward, letting droplets ball up), throw in gravity, and you have the full acceleration \mathbf{a}_i = (\mathbf{f}^{\text{pressure}}_i + \mathbf{f}^{\text{visc}}_i + \mathbf{f}^{\text{tens}}_i + \mathbf{f}^{\text{ext}}_i)/\rho_i. Integrate that forward in time and the crowd of particles sloshes like water.

Worked example: one particle's density and pressure

Let's do the first two beats by hand. Take a particle i with exactly two neighbours inside its radius, and use the classic poly6 density kernel in 2-D-ish constant form. To keep the arithmetic clean we'll use a kernel whose value at distance r is

W(r, h) \;=\; \frac{4}{\pi h^{2}}\left(1 - \frac{r^{2}}{h^{2}}\right)^{3} \quad \text{for } r \le h, \; 0 \text{ otherwise.}

Say every particle has mass m = 1, the support radius is h = 2, and i's two neighbours sit at distances r_1 = 1 and r_2 = 1.5. Include the particle's own contribution at r_0 = 0. The kernel prefactor is 4/(\pi \cdot 4) = 1/\pi \approx 0.318.

Self term (r_0=0): (1 - 0)^3 = 1, so W_0 = 0.318.
Neighbour 1 (r_1=1): 1 - 1/4 = 0.75, cubed is 0.4219, so W_1 = 0.318 \times 0.4219 \approx 0.1342.
Neighbour 2 (r_2=1.5): 1 - 2.25/4 = 0.4375, cubed is 0.0837, so W_2 = 0.318 \times 0.0837 \approx 0.0266.

Sum with the masses for the density:

\rho_i \;=\; 1\cdot 0.318 + 1\cdot 0.1342 + 1\cdot 0.0266 \;\approx\; 0.479.

Now the pressure. Suppose the rest density is \rho_0 = 0.4 and the stiffness is k = 100. Then

p_i \;=\; k\,(\rho_i - \rho_0) \;=\; 100\,(0.479 - 0.4) \;=\; 7.9.

Because i is a touch denser than rest, its pressure is positive, so the pressure force will push its neighbours away — the fluid resisting compression, exactly as it should. If those neighbours had been farther out, \rho_i would drop below \rho_0, the pressure would go negative, and the force would pull inward instead.

Finding the neighbours: a spatial grid

Every sum is over particles within h. Checking all N particles against all others is O(N^2) — hopeless for the hundreds of thousands of particles a splash needs. The standard fix is a uniform spatial grid (or spatial hash) with cell size h: bin every particle into its cell, and to find a particle's neighbours you only scan its own cell and the eight (in 3-D, twenty-six) touching cells. Every neighbour within h is guaranteed to live in that 3\times3\times3 block, so the search drops to roughly O(N). Rebuilding this grid each step is often the single biggest chunk of an SPH frame's time budget.

Because there is no shape to keep consistent. In a mesh-based or grid-based method, water tearing into droplets is a topology change — the surface has to split, and tracking that split is fiddly and error-prone. In SPH there is nothing to split: a droplet flying off is just a few particles that happened to drift beyond kernel range of the rest, so they stop feeling the main body's pressure and sail away on their own. Two streams merging is just their particles coming within h of each other and starting to share forces. Splitting and merging aren't special cases you code — they fall out of the neighbourhood sums for free. That is exactly why SPH is the go-to for the wild, foamy, topology-changing water that grids dread.

Two traps bite everyone who writes their first SPH solver.

1. Weakly-compressible SPH needs a stiff pressure → tiny timesteps. The simple equation of state p = k(\rho - \rho_0) only fights compression if k is large — otherwise the "water" squashes like a sponge and looks springy and wrong. But a stiff pressure means huge, sudden restoring forces, and an explicit integrator can only follow those with a very small timestep. Push the step too far and the fluid jitters, boils, and then explodes — particles flung to infinity. That is the weakly-compressible (WCSPH) bargain: stiff enough to look incompressible, and therefore slow. This is why the field invented PCISPH, IISPH and DFSPH, which enforce (near-)incompressibility with predictor–corrector or implicit pressure solves and so allow far larger, stable steps than raw WCSPH.

2. There is no explicit surface. You have a cloud of points, not a water boundary. Render the particles raw and you get a lumpy field of blobs, not a smooth liquid. To get a renderable surface you must reconstruct one from the particles each frame — typically build a density (or signed-distance) field on a grid and run marching cubes (or a screen-space filter) to extract the isosurface. Forgetting this step is why a first SPH render looks like a swarm of beads instead of water.

Contrast: particles (Lagrangian) vs grid (Eulerian)

SPH's opposite number is the grid approach of Stable Fluids, where the fluid quantities live on a fixed mesh of cells and the fluid "flows past" the grid. Each style is strong exactly where the other is weak.

QuestionSPH (Lagrangian, particles)Grid (Eulerian, cells)
Where does data live?on moving particlesat fixed grid points
Mass conservationexact — particles never vanishneeds care at cell boundaries
Splash / droplets / mergingfree — no topology to trackhard — must track the surface
Incompressibilityawkward — stiff EOS or PCISPH/IISPH/DFSPHnatural — a global pressure Poisson solve
Empty space costfree — no particles, no workyou still store empty cells
Renderable surfacemust be reconstructed from pointsoften a level set already present

The rough rule of thumb: reach for particles when the shot is splashy, thin, and topology-changing (a crashing wave, a thrown bucket, spray), and for a grid when you want smooth, strongly incompressible bulk motion (a calm pool, smoke, a filling tank). Modern production solvers often blend the two — FLIP/PIC methods carry particles and a grid, taking incompressibility from the grid and detail from the particles.