FLIP Fluid: Particles + Grid
FLIP (Fluid-Implicit-Particle) is the hybrid engine behind almost every high-quality fluid simulation you've seen in a game or a VFX shot. It carries the fluid's velocity on freely moving particles, but borrows an Eulerian grid to solve for pressure and enforce incompressibility — getting the best of both worlds: low numerical dissipation and rock-solid stability.
1. Why hybridise particles and a grid?
Two families of methods dominate fluid simulation. Pure Eulerian grid solvers (finite-difference / finite-volume Navier–Stokes) discretise velocity and pressure on a fixed lattice of cells. They make it trivial to solve for incompressible pressure — that's just a Poisson equation on the grid — but advecting the velocity field itself introduces numerical diffusion: the fluid loses energy and fine detail every step, and free surfaces (splashes, droplets) are awkward to track.
Pure Lagrangian particle solvers like SPH track mass, momentum and free surfaces perfectly (particles simply are where the fluid is), but computing pressure from an unstructured particle cloud is expensive and prone to noisy, compressible-looking behaviour unless heavily tuned.
FLIP, introduced by Brackbill & Ruppel (1986) for plasma physics and adapted for graphics fluids by Zhu & Bridson (2005), takes the strengths of both:
- Particles carry velocity, position and free-surface shape — no numerical diffusion, no smoothing of splashes
- A background grid solves pressure projection exactly, in one linear solve, guaranteeing an incompressible (divergence-free) velocity field
- Information is transferred particle → grid → particle every step, so each representation only does what it's good at
FLIP variants power Houdini's FLIP Fluids, Blender's Mantaflow, and most AAA game water/smoke systems (it also generalises to smoke, sand and snow, where the analogous method is called MPM — the Material Point Method).
2. PIC vs. FLIP vs. the blend
The transfer scheme has two extremes, and production simulators use a mix.
PIC (Particle-In-Cell)
After solving pressure on the grid, PIC simply copies the new grid velocity back onto every particle it touches:
This is unconditionally stable but re-interpolating onto the grid and back every frame acts as a low-pass filter — kinetic energy bleeds away, and the fluid looks thick, damped and "muddy" after a few dozen frames.
FLIP (Fluid-Implicit-Particle)
Instead of taking the new velocity outright, FLIP transfers only the change the grid solve produced, and adds that delta to the particle's own old velocity:
Because only the pressure correction passes through the lossy grid interpolation, almost all of the particle's kinetic energy is preserved. The downside: pure FLIP is noisier and can accumulate spurious high-frequency velocity ("particle noise") over long runs.
The PIC/FLIP blend
Nearly every real implementation linearly blends the two with a parameter α (typically 0.95–0.99, i.e. mostly FLIP):
A touch of PIC damping keeps the simulation from ever going unstable while retaining FLIP's lively, energetic look.
3. Particle-to-grid transfer (P2G)
Grid quantities live on a MAC (Marker-and-Cell) grid: pressure is stored at cell centres, but each velocity component is stored on the corresponding face of the cell (u on x-faces, v on y-faces, w on z-faces). This staggering avoids checkerboard pressure artefacts and makes the pressure gradient / divergence operators simple central differences.
To move particle velocities onto the grid, every particle scatters its momentum to nearby grid nodes using a smooth interpolation kernel — typically the trilinear "tent" function, sometimes a quadratic B-spline for extra smoothness:
where wₚ = trilinear weight of particle p at face (i,j,k)
Dividing by the sum of weights (rather than particle count) normalises for the uneven number of particles per cell — this is what keeps regions with few particles from getting spuriously small velocities.
While splatting velocity, each cell is also tagged
FLUID, AIR or SOLID
depending on whether it contains particles or lies inside a
collider. Only FLUID cells participate in the pressure solve —
AIR cells simply hold a Dirichlet pressure of zero (free
surface), which is why FLIP handles splashes without any
explicit surface-tracking step.
4. Pressure projection
After scattering, the grid velocity field is usually not divergence-free — fluid appears to be created or destroyed inside cells. Incompressibility requires ∇·u = 0 everywhere. We enforce this by subtracting the gradient of a pressure field p that exactly cancels the divergence (Helmholtz–Hodge decomposition):
Taking the divergence of both sides and requiring the result to be zero turns this into a Poisson equation for pressure:
Discretised on the MAC grid, this becomes one linear equation per FLUID cell relating its pressure to its (up to 6) neighbours — a large, sparse, symmetric positive-definite system:
A is the discrete Laplacian (a 7-point stencil in 3D), b is the (negative, scaled) divergence of the pre-projection velocity. It is solved iteratively — Preconditioned Conjugate Gradient (PCG) with a Modified Incomplete Cholesky (MIC) preconditioner is the industry-standard choice, converging in a few dozen iterations even for grids with millions of cells.
Once p is known, each velocity face adjacent to a FLUID cell is corrected:
The resulting grid field is divergence-free to solver tolerance — the fluid neither compresses nor expands.
5. Grid-to-particle transfer (G2P)
The corrected grid velocities are now interpolated back onto every particle using the same trilinear weights as P2G (this symmetry matters — using different kernels for scatter and gather breaks momentum conservation). As described in section 2, production code computes both the PIC value and the FLIP delta and blends them with α:
where Δvgrid = vgridnew − vgridold, both trilinearly interpolated at the particle's own position.
6. Advection and boundaries
With final particle velocities in hand, positions are integrated forward — usually with a 2nd or 3rd-order Runge-Kutta scheme (RK2/RK3) rather than plain Euler, since particles may travel several cells per step near a splash:
xpnew = xp + Δt · v(xmid)
Solid boundaries (container walls, colliders) are enforced twice: once on the grid, by zeroing the normal velocity component on SOLID faces before the pressure solve (a Neumann condition), and once on particles, by clamping any particle that tunnels past a wall back inside and killing its inward velocity component.
A subtlety unique to FLIP: because particles are never reseeded onto a uniform pattern, cells can become particle-starved (numerical "leakage" through thin gaps) or over-populated (clumping) over time. Most implementations run a lightweight per-cell particle count target and reseed/delete particles opportunistically to stay within, say, 4–12 particles per fluid cell.
7. Beyond FLIP: APIC and MLS-MPM
Plain FLIP throws away angular momentum during transfer — spin a particle in place and the P2G/G2P round trip damps it out. APIC (Affine Particle-In-Cell), introduced by Jiang et al. (2015), fixes this by storing a small affine velocity matrix Cp per particle (a local velocity gradient) alongside its velocity, and using it to reconstruct a locally-linear velocity field during both transfers:
APIC is exactly momentum- and angular-momentum-conserving in the transfer step itself, which is why it has largely replaced plain FLIP blending in modern production solvers. MLS-MPM (Hu et al., 2018) generalises this further with a moving-least-squares formulation and extends the same particle/grid machinery to elastic and granular materials (snow, sand, cloth) — outside the scope of pure fluid, but built on exactly the P2G/solve/G2P skeleton described above.
8. Pseudocode
One FLIP simulation step, simplified:
function stepFLIP(particles, grid, dt, alpha):
// 1. Particle → grid transfer (P2G)
grid.clear()
for each particle p:
splatVelocity(grid, p.pos, p.vel)
grid.markFluidCell(p.pos)
grid.normalizeByWeights()
gridVelOld = grid.velocity.copy()
// 2. External forces (gravity) on the grid
grid.applyGravity(dt)
grid.enforceSolidBoundaries()
// 3. Pressure projection: A·p = b, solved with PCG
b = grid.computeDivergence()
p = conjugateGradient(grid.laplacianA, b, iters=100)
grid.subtractPressureGradient(p, dt)
// 4. Grid → particle transfer (G2P), PIC/FLIP blend
for each particle p:
vPIC = interpolate(grid.velocity, p.pos)
dv = interpolate(grid.velocity - gridVelOld, p.pos)
vFLIP = p.vel + dv
p.vel = alpha * vFLIP + (1 - alpha) * vPIC
// 5. Advect particles (RK2) and handle collisions
for each particle p:
mid = p.pos + 0.5 * dt * p.vel
p.pos += dt * interpolate(grid.velocity, mid)
resolveCollisions(p)
// 6. Reseed / delete to keep particle density stable
grid.rebalanceParticles(particles, targetPerCell=8)
Note the two full grid sweeps: P2G must finish for every particle before the pressure solve runs, and the pressure solve must finish before any G2P interpolation — the grid is the synchronisation point that makes the whole scheme parallel-friendly on the GPU, since particles never need to know about each other directly.
💧 Try the fluid simulation
An interactive particle/grid fluid demo running in WebGL — click to pour in fluid, drag to stir the flow, and watch the pressure solve keep it incompressible in real time.
Open simulation →