↑ Vocaro Guide to MPM

An interactive tutorial

Material Point Method: Step by Step

A visual, arithmetic-first introduction to the simulation method behind Frozen's snow and modern real-time sand, mud and water — written for engineers and designers who have never taken a fluid mechanics course.

Audience · engineers & game designers Assumes · vectors, matrices, a game loop Goal · read the solver, tune the scene

Every simulation method is a bet about what is hard. Rigid bodies bet that shape never changes. Cloth bets that connectivity never changes. The Material Point Method bets that connectivity is the thing you should never have had in the first place — and pays for that bet by renting a grid, one timestep at a time.

That single trade explains almost everything about how MPM code looks, why it is fast on a GPU, why it is slow on a clock, and why your snow sometimes behaves like jello. This page builds that intuition from arithmetic you can do on paper, with a worked transfer you can drag around, and ends by mapping the phases onto the kernels of a real solver.

Prerequisites, honestly

You need vectors, matrices, and a mental model of a fixed-timestep game loop. You do not need continuum mechanics; every physics term is defined at first use. If you want the formal treatment, Prerequisites and Theory in this guide are the companion reads, and the papers behind every claim are in References.

1 · The bargain

There are two classical ways to simulate a deforming material, and each one fails in a characteristic, visible way.

A Lagrangian method glues its sample points to the material. Think of a mesh whose vertices ride along with the stuff. This is great bookkeeping: mass is exactly conserved, and every point remembers its own history. It is also brittle. Push the material hard enough and elements shear, flatten, and eventually turn inside out, at which point the math that computes forces from element shape starts returning nonsense.

An Eulerian method fixes its sample points in space and watches material flow past. Think of a fixed grid of cells. Nothing can tangle, because nothing moves. But every step you have to advect — copy each cell's contents into its neighbors — and that copy is a blur. Do it a few hundred times and a crisp block of snow has smeared into a fog with no edges and no memory of which grain came from where.

A · MESH (LAGRANGIAN) J = 1.0 J = 1.0 J = −0.3 shear →→→ the element turns inside out B · GRID ONLY (EULERIAN) step 0 step 200 advection is a blur; the edge is gone C · MPM (BOTH) particles keep the edge; the grid does the math
The two failure modes MPM is designed around. A mesh element can shear until its volume ratio J goes negative — inside-out, and every force computed from its shape is now wrong. A pure grid never tangles but has to repaint itself every step, and each repaint is lossy. MPM keeps the crisp, history-carrying particles of the first and borrows the tangle-proof grid of the second for the duration of one step.

MPM's answer is to refuse to choose. Material lives on particles that carry mass, velocity and deformation history and move freely through space, with no notion of who their neighbors are. Once per step, every particle dumps its state onto a background grid, the grid does the one job a grid is genuinely good at — computing spatial derivatives and resolving contact — and the answer is handed straight back to the particles. Then the grid is thrown away.

The idea to keep

The particles are the simulation; the grid is scratch memory. Nothing on the grid survives to the next step. If you find yourself wanting to store something on the grid across steps, you have left MPM.

What that buys, in the language of things you would otherwise have to build:

Self-collision, free

Two chunks of material that reach the same grid node read the same single-valued velocity there, so they simply move together. No broad phase, no contact manifolds, no penetration recovery.

No neighbor search

Particle methods like SPH pay for a spatial hash every step to find who is nearby. MPM's neighborhood is fixed by construction: the handful of grid nodes the particle currently sits between.

Fracture and merging, free

Nothing holds particles together except stress transmitted through the grid. When a region is pulled apart hard enough that particles stop sharing nodes, it separates — no topology surgery required.

Mixed materials, free

Sand, snow and water differ only in a per-particle stress function. Put all three in one domain and they interact through the shared grid without any coupling code.

And the bill, which arrives later in this page: a small timestep, a lot of memory traffic, contact that is too sticky, and a resolution floor that means a thin object simply cannot be simulated no matter how you tune it.

2 · Two data structures

Before the algorithm, the two records it moves data between. Everything else in MPM is a consequence of what is in these two structs.

Particle — persistent

x  position (float3)
v  velocity (float3)
C  affine velocity matrix (3×3)
F  deformation gradient (3×3)
Jp plastic volume ratio (scalar)
m  mass — never changes
group which body / material row

Lives for the whole simulation. Its array index never moves — that stability is what makes GPU replays bit-identical.

Grid node — disposable

mass  accumulated (scalar)
mv    accumulated momentum (float3)




Zeroed at the top of every step. Two accumulators, and briefly a velocity derived from them. That is the entire Eulerian side.

Note what is not on the particle: any reference to another particle. No neighbor list, no spring, no element index. Two particles that are about to collide have no idea the other exists. They will find out through the grid, and section 4 shows exactly how.

Note also what is not on the node: anything material. A node has no idea whether the mass sitting on it is snow or water. It is a place where momentum is added up and divided by mass.

Coordinates

This solver runs the whole simulation in a normalized [0,1]3[0,1]^{3} box, with gridN cells per axis, so cell width is dx=1/gridN\mathrm{dx} = 1/\mathrm{gridN}. World-space mapping happens at the buffer boundary, never inside a kernel. Gravity is therefore a coefficient in domain units, not 9.8 m/s² — a point that matters a great deal in section 9.

3 · The four-phase loop

One MPM timestep is always the same four phases, in the same order. Every variant of MPM you will read about — APIC, MLS-MPM, PB-MPM — changes what is carried across these arrows, never the shape of the loop.

1 · CLEAR 2 · P2G 3 · GRID 4 · G2P mass = 0 momentum = 0 at every node stress from F scatter m, mv 27 atomic adds each v = mv / m + gravity, + BCs colliders, walls, floor gather v and C update F, move x plus return mapping next substep — the grid's contents do not survive this arrow EULERIAN — grid reads particles EULERIAN — grid writes particles ▲ the parallel bottleneck ▲ where contact happens
One substep. Two of the four phases run over particles and two over nodes, which is why an MPM step is at least four GPU dispatches. Everything a designer thinks of as "collision" happens in phase 3, on a regular grid, which is why it is cheap — and why it is stickier than a real contact solver.

Two remarks that will pay off later. First, phase 2 is where every particle writes into nodes that other particles are also writing into, simultaneously. On a GPU that is a pile of atomic adds into contended addresses, and it is the single biggest performance problem in the method. Second, phase 3 is the only place where two separate pieces of material can influence each other. That is the whole contact model.

4 · Do one step by hand

This is the section that builds the intuition. We will take two particles moving toward each other, transfer them to a grid, solve, and transfer back — with real numbers at every stage. Nothing here is simplified away; these are the same formulas the compute shader runs, in 2D with dx=1\mathrm{dx} = 1 so the arithmetic is legible.

4.1 · Which nodes does a particle touch?

A particle at position x does not talk to "the cell it is in". It talks to a 3×3 block of nodes (3×3×3 in 3D) chosen so the particle sits near the middle. The solver finds that block with two lines:

int3  base = int3(floor(p.x * invdx - 0.5));   // lowest node of the 3×3×3 block
float3 fx  = p.x * invdx - float3(base);       // where in the block, in [0.5, 1.5)

fx is the particle's offset from that lowest node, measured in cells. The −0.5 is what centers the block: it guarantees fx always lands in [0.5, 1.5), so the particle is always between the first and third node with the middle node nearest. Take particle A at x=2.3x = 2.3: base=1.8=1\mathrm{base} = \lfloor 1.8 \rfloor = 1 and fx=1.3\mathrm{fx} = 1.3. It touches nodes 1, 2 and 3 — and it is closest to node 3, so node 3 will get the most of it.

4.2 · How much of the particle does each node get?

The share each node gets is its weight. MPM uses a quadratic B-spline, which in code is three lines with no branches:

w0 = 0.5 * (1.5 - fx) * (1.5 - fx);
w1 = 0.75 - (fx - 1.0) * (fx - 1.0);
w2 = 0.5 * (fx - 0.5) * (fx - 0.5);   // w0 + w1 + w2 == 1, always

For A's fx=1.3\mathrm{fx} = 1.3: w=(0.02,0.66,0.32)w = (0.02,\; 0.66,\; 0.32). They sum to exactly 1 — that property is called partition of unity, and it is what makes the transfer conserve mass. In 2D the weight for a node is the product of its x-weight and its y-weight; in 3D, all three. Nine numbers per particle in 2D, twenty-seven in 3D.

4.3 · Scatter, solve, gather

Now the whole cycle, with two particles heading at each other at ±1 unit/s. Step through it, drag them closer or further apart, and watch what happens at the nodes they share. Neither particle ever reads the other.

Worked transfer — 2D, dx = 1, quadratic B-spline
Particle A
Particle B
Node (3, 3) — click any node

Walk the phases and read the numbers. The four things worth noticing, in order:

1 — the shared node is a mass-weighted average

At the default spacing, node (3, 3) receives weight 0.189 from A (moving right) and weight 0.437 from B (moving left). Its momentum is 0.189(+1)+0.437(1)=0.2480.189\cdot(+1) + 0.437\cdot(-1) = -0.248 and its mass is 0.626, so its velocity is −0.396. A node cannot hold two velocities. Divide momentum by mass and the disagreement is resolved, permanently, in favor of whichever side brought more mass. That single division is MPM's entire collision response.

2 — the particles change velocity without ever meeting

In phase 4 each particle gathers wvnode\sum w\,v_\mathrm{node} over its own nine nodes. A's stencil now contains nodes that B polluted, so A comes back slower than +1+1. B likewise. Neither did a distance check, a raycast, or a neighbor query. This is what "self-collision is automatic" actually means at the arithmetic level.

3 — momentum is conserved exactly; energy is not

The verdict bar shows total momentum before and after: identical to the last digit. That is a theorem, not a coincidence — it follows directly from partition of unity, and it holds for every MPM transfer. Total kinetic energy, meanwhile, drops hard.

Two different things are behind that drop, and it is worth separating them because only one is a defect. The first is real physics we have deliberately switched off: this lab scatters bare momentum, with no stress term, so two masses meeting head-on simply merge their motion — a perfectly inelastic collision. In the full solver that energy is not lost; it is stored in each particle's deformation gradient and pushed back out as stress on the following step. The second is transfer dissipation, and it is unrecoverable: any motion the grid's one-vector-per-node representation cannot express — rotation, and any structure finer than a cell — averages to nothing on the way in and does not come back. That second part is the defining flaw of the scheme, and section 6 is about how much of it can be clawed back.

4 — separate them far enough and nothing happens

Push the gap up and watch the shared-node count step down: six shared nodes, then three, then at a gap of 4.5 — about 2.2 cells of separation — zero, and the exchange stops dead. The particles pass without ever affecting each other. The interaction range of MPM is exactly the stencil width — three cells, from the shape function. It is not a tunable radius and not a material property. A grid cell is the length scale of everything.

The consequence that matters

Grid resolution is not a quality slider. It is the physical size of the smallest feature your simulation can have, the range of every interaction, and (through the CFL limit in section 9) a hard cap on your timestep. Change gridN and you have changed the material.

4.4 · What the real kernel adds

The lab above scatters plain momentum, wmvw\,m\,v. The production kernel scatters wm(v+Cdpos)w\,m\,(v + C\,\mathrm{dpos}), where dpos is the offset from particle to node and C is a 3×3 matrix. Two separate things are folded into that one matrix:

// an MLS-MPM P2G kernel (HLSL), the lines that matter
float3x3 stress = (-4.0 * invdx * invdx * _Dt) * PF;   // force, as an affine term
float3x3 C      = stress + p.C;                        // + the particle's own affine field
...
float3 mom = particleMass * (p.v + mul(C, dpos));      // one scatter carries both

That fusion is why modern real-time MPM starts from MLS-MPM rather than classical MPM: it removes an entire gradient evaluation from the innermost loop, at no cost in behavior.

5 · Where the weights come from

The three-line weight function is doing more work than it looks. Here is the same B-spline drawn across a row of nodes, so you can see the property that matters: at every position, exactly three bumps are non-zero and they always sum to one.

Σ w = 1 particle 0.03 0.69 0.28 support = 3 cells n−3 n−1 n n+1 n+8
The quadratic B-spline basis. One bump per node, each spanning three cells. Wherever you drop a particle, exactly three bumps are non-zero and their heights sum to 1 — so mass and momentum are split among nodes without being created or destroyed. Move the particle and the split changes smoothly; no node's share ever jumps.

5.1 · Why not the obvious linear weights?

You could split a particle between the two nodes it sits between, linearly. That works, it is cheaper, and it produces a notorious artifact called cell-crossing instability. The reason is not in the weight — it is in the weight's derivative, which is what force depends on.

LINEAR HAT — ∂w/∂x jumps force snaps sign here cell edge node cell edge QUADRATIC B-SPLINE — ∂w/∂x is continuous no jump anywhere cell edge cell edge node
The reason MPM uses splines. Force is proportional to the gradient of the weight. With linear weights that gradient is piecewise constant, so a particle drifting across a cell boundary feels its force flip instantaneously — a per-particle kick with no physical cause, which accumulates into visible jitter and can destabilize a stiff material. The quadratic spline's gradient is continuous, so the kick disappears. This costs a 3×3 stencil instead of 2×2, and it is worth it.
If you have used SPH

A B-spline weight looks like an SPH smoothing kernel, and the resemblance is real, but the use is inverted. SPH evaluates its kernel between pairs of particles, which is why it needs a neighbor search. MPM evaluates its kernel between a particle and a fixed node, so the "neighborhood" is decided by array arithmetic instead of a spatial query.

6 · What the transfer forgets

In the lab you saw total kinetic energy drop on every round trip. Here is the mechanism, and it is worth seeing because it explains three decades of method names.

Consider four particles rotating around a single node. Their velocities are tangential, in four different directions. Scatter them onto the node and they cancel: the node's momentum is zero, so its velocity is zero. Gather back and all four particles come home dead. The rotation is gone, and there was never a moment where anything "lost" it — the node simply has no slot in which spin could be stored.

PIC — the node cannot hold spin 4 particles, spinning v = 0 scatter → gathered back: all dead APIC — each particle carries C C = [ 0 −ω ]    [ ω   0 ] spin stored per particle round trip: rotation survives
The angular-momentum leak, and its fix. A grid node stores one velocity vector. Any motion that averages to zero there — rotation, and any finer-than-a-cell structure — is invisible to it and does not come back. APIC adds a 3×3 matrix C per particle that records the local velocity gradient, so the scatter carries v+Cdposv + C\,\mathrm{dpos} instead of just v and the rotation is reconstructed on the way home.

The family tree, which is just a sequence of answers to "what should we transfer back?":

SchemeYearTransfers backBehavior
PIC1960sgrid velocityRock stable, and visibly dead — every step is a blur of the velocity field.
FLIP1986grid velocity changeKeeps the energy, keeps the noise too — particles accumulate motion the grid never saw.
APIC2015velocity + affine matrix CStable like PIC, lively like FLIP, and conserves angular momentum. The modern default.
MLS-MPM2018same C, reused as the velocity gradientNotices C and the stress-divergence gradient are the same object; drops an entire evaluation. ~2× faster.
Trap — the blend that is secretly a damper

Older code often exposes a PIC/FLIP blend: v=βvflip+(1β)vpicv = \beta\,v_\mathrm{flip} + (1-\beta)\,v_\mathrm{pic}, applied once per substep. That is not a material property. It is exactly FLIP plus a damper whose rate is (1β)/Δt(1-\beta)/\Delta t — so halving your substep doubles the damping and silently changes every material you tuned. If you need a drag knob, expose a rate α\alpha in 1/s and derive β=1αΔt\beta = 1 - \alpha\,\Delta t. The same audit applies to any per-substep fixed-fraction blend anywhere in your solver.

7 · F — the memory in a particle

Everything so far would give you a fluid with no memory. What makes MPM able to simulate a rubber ball, packed snow, and a sand pile is one 3×3 matrix per particle: the deformation gradient, F.

F answers one question: if I drew a tiny arrow inside this material when it was at rest, what would that arrow look like now? It is the local linear map from rest shape to current shape. That is all.

REST F = I J = 1.00 ROTATE F = R J = 1.00 no stress STRETCH diag(1.5, 1) J = 1.50 expanded SHEAR [[1, .6],[0, 1]] J = 1.00 volume kept, shape not CRUSH diag(1, 0.25) J = 0.25 compressed 12 34 INVERTED winding reversed J = −0.30 must be survivable
One matrix, every kind of local deformation. J=det(𝑭)J = \det(\mathbf{F}) is the local volume ratio: 1 means volume preserved, below 1 compressed, above 1 expanded, and negative means the material has been turned inside out. A mesh solver dies at J < 0; an MPM solver has to keep running through it, which is why graphics MPM prefers constitutive models that stay well-behaved there.

7.1 · From F to force

A constitutive model is a pure function from F to stress. That is the entire definition of "material" in MPM — swap the function, get a different substance, change nothing else in the solver. The elastic model graphics MPM has favored for snow and rubber since the Disney paper is fixed corotated, and the intuition behind it is worth a sentence:

float3x3 R  = polar_rotation(F);           // the nearest pure rotation to F
PF  = (2.0 * mu) * mul(F - R, transpose(F));   // shear: penalize deviation from that rotation
PF += lambda * (J - 1.0) * J * I;              // volume: penalize J drifting from 1

R is the rotation part of F. Subtracting it means rotation is free — spin a rubber block and it feels nothing, which is exactly right and is the whole reason this model is preferred over the naive alternatives. mu and lambda are the two stiffness constants derived from Young's modulus E and Poisson's ratio nu; mu resists shape change, lambda resists volume change.

7.2 · Plasticity is a clamp

Elastic materials spring back. Snow packs, sand piles, and mud slumps — they keep the dent. The mechanism is startlingly simple: split F into an elastic part that makes stress and a plastic part that does not, and enforce the split by clamping.

Factor F with an SVD into 𝑼Σ𝑽T\mathbf{U}\Sigma\mathbf{V}^{T}. The diagonal of Σ\Sigma holds the singular values — the stretch factors along the principal axes. Snow plasticity clamps each one into [1θc,1+θs][1-\theta_c,\; 1+\theta_s]. Whatever you clamped away is now permanent deformation. Sand does the same thing against a cone rather than a box.

SNOW — clamp the singular values σ₁ σ₂ admissible 1−θc 1+θs trial Fₑ projected the discarded part becomes permanent SAND — project onto a friction cone pressure → shear holds (elastic) flows (yields) friction angle apex — zero strength in tension cohesion shifts the whole cone this way →
Two yield surfaces, one idea. Compute the deformation you would have had if the material were purely elastic, then project it back onto the region the material can actually sustain. Snow's region is a box in singular-value space, so packing and fracture both fall out of a clamp. Sand's is a cone whose opening angle is the friction angle — the direct cause of the angle of repose. The cone's apex sits at zero, which is why dry sand has no strength at a free surface. Sliding the cone into tension by an amount c is cohesion: grains that stick to each other and to walls.
Design intuition

The friction angle is the angle of repose. If your sand pile should stand at 38°, set frictionAngle = 38. It is one of the very few MPM parameters that maps directly to something you can measure with a protractor.

8 · The material zoo

With F, a stress function, and a yield surface in place, a material is just a row of constants. Here is a worked palette from one production solver — the names are its own, but the shape of the table is what every MPM material set looks like.

PresetmodeEhardeningCharacter
Soft0 snow15004Stiffest rung of the snow ladder; holds a firm mound.
Mushy0 snow6002The crater reference: bowl plus debris plume.
Goopy0 snow2501Mid-ladder; looser than mushy.
Wet0 snow1500.7Near-fluid slush.
Slush0 snow900.5Softest rung. Deforms and stays deformed.
Elastic0 snow12000Yields pushed to 1e9 so the clamp never fires — rubber, jelly, the bouncing-armadillo material.
Granular1 sand4000Drucker-Prager, frictionAngle = 38°. Piles, avalanches, no jiggle.
CohesiveGranular1 sand4000Same plus cohesion = 0.0006. The packed-snow answer: clumps, sticks to walls, still blasts apart.
Water2 fluidTait equation of state, pressure only, no shear stress at all. bulkModulus = 100.
Trap — the name lies

Preset names describe an author's intent, not the physics. Nothing in the palette above models ice, yet a reader shopping for ice will reach for Slush or Elastic — and ice holds its shape and then breaks, which neither a snow-plasticity clamp (deforms and stays deformed) nor an elastic body (springs back) can do. When a palette has no brittle branch, no name in it is ice. Read the mode column, not the names.

8.1 · Water is a different shape of code

Notice that Water has no Young's modulus. A weakly-compressible fluid does not resist shape change at all — only volume change — so the entire deviatoric branch, and with it the per-particle SVD, disappears:

// fluid: pressure only, no shear term, no SVD on this path
float pres = bulkK * (pow(max(J, 1e-4), -eosGamma) - 1.0);
float dd   = -pres * J;
PF = float3x3(dd,0,0, 0,dd,0, 0,0,dd);   // purely isotropic

That is why the fluid path is meaningfully cheaper per particle than the solid path, and why bulkModulus for a fluid is doing double duty: it sets both how crisp a splash looks and how strongly a floating object is pushed up.

9 · Tuning a scenario

This is where intuition earns its keep, because most MPM parameters do not do what their name suggests, and several of them are entangled. The following is the field-tested version.

9.1 · Which knob owns what

KnobLives onActually controls
gridNsolverFeature size, interaction range, timestep ceiling. The most consequential number in the whole configuration.
substep dtstepperStability, and — through the boundary law — how far material creeps on a slope. Not a performance dial.
GravitysolverThe balance between weight and material strength: mound versus pancake. Not playback speed.
DampingsolverRing-out. Applied as a rate: v*=(1dampingdt)v \mathrel{*\!=} (1 - \mathrm{damping}\cdot\mathrm{dt}).
playback speedstepperPlayback speed. This is the dial you actually wanted when you reached for gravity.
youngsModulusmaterialElastic rebound. Raising it buys springiness and costs stability.
cohesionmaterialHow steep and tall a pile can stand. The real "hardness" dial.
frictionAnglematerialAngle of repose, in degrees.
hardeningmaterialWhether yielding permanently softens the material. Terrain wants it; bodies do not.

9.2 · The five traps

Trap 1 — gravity is a balance knob, not a speed knob

Raising gravity to make things happen faster also flattens every equilibrium: piles that were mounds become pancakes. You changed the ratio of weight to material strength, which is a material-appearance change wearing a timing change's clothes. Fix gravity at whatever gives the pile shape you want, tune stiffness against it, and put playback speed on the stepper.

Trap 2 — dt is not a free performance knob

Treat (dt, gravity, substeps-per-frame) as a tuned set. Two specific ways a bigger dt lies to you. First, a boundary law that retains a fraction of tangential velocity retains it once per step, so steady-state creep on a slope scales with dt — material that sits still at 1e-4 slides forever at 2.5e-4, with nothing else changed, and a flat floor hides this completely. Second, "no NaNs" is not stability: in one measured fluid rig every step through 4e-4 stayed finite for two seconds, yet steps at 2.5e-4 and above visibly damped the motion the scene existed to show. Sweep against a smaller-step reference and compare observables, not just a finite check.

Trap 3 — hardness comes from cohesion, not from E

The instinct on being told "this should be rock, not pudding" is to raise Young's modulus. That gives you ringing jello: E adds elastic rebound, and rebound is exactly what rock does not do. Shape-holding strength comes from cohesion, which supplies the restoring force that lets a tall mound stand. Raise cohesion roughly 10×, keep E low, and one parameter family spans slush → wet snow → packed snow → rock.

Trap 4 — thin features are impossible, at every stiffness

A B-spline stencil spans three cells, so a feature about two cells thick has no interior: every particle in it shares nodes with the outside world on both sides at once, and there is no node-to-node lever arm for a bending moment to act through. Such a limb stands still, survives gentle taps, and loses its pose under real impact at every stiffness across an order of magnitude. Around five cells across, the same shape comes through intact. Stiffness sets stress per unit strain; it cannot manufacture separation. This is a constraint on the art brief, not on the tuning pass.

Trap 5 — without damping, MPM rings forever

An undamped elastic continuum is a spring network and it will oscillate visibly long after the scene should have settled. Add a per-second velocity decay, dt-scaled. This is a physics term, not a render polish step — a temporal filter in the renderer cannot hide it.

TWO CELLS THICK — no interior 3-cell stencil reaches free space above AND below every particle is a surface particle → no bending moment possible FIVE CELLS THICK — has an interior stencil stays inside the body interior particles exist → the limb can carry a moment
Why thin things fail and no knob rescues them. A bending moment needs two parts of the body to push in opposite directions through a lever arm. In a two-cell feature, every particle's stencil straddles both surfaces, so there is no pair of nodes to lever against. The failure has two independent halves — fragmentation and pose collapse — and they respond to different fixes, so measure them separately.

9.3 · Symptom → knob

What you seeMost likely causeWhat to change
Everything explodes / NaNsCFL violated — a particle crosses more than a fraction of a cell per steplower the substep dt, or lower E / bulkModulus
Jiggles like jello, never settlesElastic energy with nowhere to goraise Damping; lower E; consider a granular mode
Pile spreads into a pancakeGravity is winning against material strengthlower Gravity, or raise cohesion
Pile is too steep / too tallExcess cohesion or frictionlower cohesion, lower frictionAngle
Material creeps down a slope foreverFraction-retaining boundary applied per steplower the substep dt and re-measure over a long window
Two separate bodies fuse on contactSingle velocity field per node — they share itenable multi-field contact — one velocity field per body
A limb snaps off or loses its poseFeature spans too few cellsthicken the geometry, or raise gridN
Repeatedly hit body erodes into dustSnow hardening permanently softens on each yieldhardening = 0
Fluid level slowly dropsVolume integrated through F leaks under shearenable grid volume recovery
Material won't level out flatAnything with a yield surface pools as a heap; friction stops the slide, it does not levelover-fill and delete above the waterline
Sand re-settles permanently fluffed after an impactThe yield cone's apex projection forgets the expansionenable granular volume correction

9.4 · How to tune, as a process

Nobody has calibrated intuition for MPM parameters — not even people who have shipped one. Twiddling a dial and asking "is that better?" does not converge. Two rules make it converge:

  1. Physics first on a deliberately ugly render. Draw particles as big soft overlapping blobs, lighting off, iso-surface off, and tune motion until the material reads as the right substance. Human vision is biased toward motion, and a pretty render hides motion behind silhouette and shading artifacts. Only once the motion is right do you switch on the real renderer and tune the look — against locked physics. The debug renderer is standing tooling, not scaffolding to delete.
  2. Side-by-side, one axis per round. Run three or four deterministic sims simultaneously that differ in exactly one parameter and ask "which of these is more X" — a question vision answers far better than "is this good". Keep the previous champion on screen so you notice when round 2 gains something and quietly loses what round 1 had. Bracket wide first to find the regime boundaries on both sides, then bisect. Lock the scene seed across resets, or every variant is judged on a different scene and the comparison is noise that looks like signal.

9.5 · The performance model, in one figure

The last thing to internalize before touching a config: what actually costs time. It is not the particle count in memory. It is the number of times per second you stream all of that state through the two transfers, which the CFL condition puts a floor under.

CFL — a particle may not outrun the grid < 1 cell stable the particle's stencil overlaps where it came from — forces stay continuous > 3 cells in one step explodes it lands in a stencil that never saw it — momentum appears from nowhere WHAT THAT COSTS YOU Δt < C · dx / c dx = cell width  ·  c = wave speed in the material double gridN → halve Δt 4× stiffer material → halve Δt 3D vs 2D → 27 nodes, not 9 Doubling grid resolution in 3D therefore costs 8× the nodes and 2× the substeps: ~16×.
The CFL condition and why it dominates the budget. Stability requires that information travel no further than the grid can follow in one step, so the step shrinks as the grid gets finer or the material gets stiffer. Since each substep restreams every particle's state through P2G and G2P, the binding cost is memory traffic per second, not particles in memory. Optimizing MPM means moving less data per step — compact state, fused passes, tile-friendly layouts — not fitting more particles in RAM.

10 · Reading a solver

Everything above maps onto a small number of places in any MLS-MPM codebase, whatever the language. Find these eight first and the rest of the file reads itself. An explicit MLS-MPM solver is the one to read first; PB-MPM is a different bargain and should be read second.

ConceptWhere it lives in a typical solver
The three-line B-spline weightsa small shared weight function, called by both transfers
Phase 2 — scatter, and the stress fused into Cthe P2G kernel
Phase 3 — v=(mv)/mv = (mv)/m, gravity, colliders, wallsthe grid-update kernel
Phase 4 — gather, rebuild C, advance F, advectthe G2P kernel
The constitutive branch (snow / sand / fluid / tracer)inside both transfers, keyed on a per-particle material mode
Material constants and presetsa material table, one row per material
Gravity, damping, colliders, grid sizethe solver's own parameters — not the material's (section 9)
Fixed-timestep playback and catch-upa stepper that owns the wall-clock-to-substep accounting

10.1 · Two implementation details worth knowing up front

A deterministic scatter uses integer atomics, on purpose. Floating-point addition is not associative, so the order in which particles land on a node would change the result — and GPU thread order is not reproducible. A scatter that accumulates into fixed-point uint values with InterlockedAdd, which is associative, buys bit-identical replays on the same machine. It is also the only portable option: Metal has no float atomics at all.

The grid clear can be folded into the grid update. If each node's accumulator is read exactly once per step, in the grid-update kernel, it can be zeroed right there and the separate clear dispatch disappears. This works only because of the "exactly once" property — a pass that reads neighboring nodes' accumulators during the same dispatch (a multi-field contact pass, for instance) has to keep its explicit clear.

Where the other documents pick up

Prerequisites for the continuum mechanics behind section 7 · Theory for the weak form, the variant zoo, and method comparisons · Related Methods for the incumbents MPM is weighed against · Implementation for GPU optimization of the scatter and a file-by-file read of EA's PB-MPM · Games for whether anything commercial ships MPM · Rendering for turning particles into a surface · References for the papers behind every claim on this page.

11 · Other MPM tutorials

Tutorials and guides only — no papers. Each entry says what it is actually good for, because they are not interchangeable.

Suggested order

If you are starting cold and want to be productive: skim this page for the mental model → read mpm88 in one sitting to see the whole method at once → work through nialltl's guide with code open → reach for the SIGGRAPH course whenever a step feels arbitrary. Come back to NairnMPM's docs the day two bodies need to touch without merging.

Written guides & courses

ResourceFormatBest for
SIGGRAPH 2016 MPM Course
Jiang, Schroeder, Teran, Stomakhin, Selle — course notes PDF
~100pp course notes + video The canonical tutorial and the source everything else defers to. Builds continuum mechanics from scratch, then the full discretization. Read this when you want the derivations rather than the recipe.
nialltl — MPM guide Blog series + Unity C# repo The most approachable from-scratch walkthrough anywhere. Builds up APIC, then MLS-MPM, then Neo-Hookean elasticity and fluids, each with runnable code. Start here if you want working pixels this week.
GAMES201 — Advanced Physics Engines
Yuanming Hu (author of MLS-MPM)
10-lecture hands-on course, slides + code MPM taught by the person who wrote the fast version, in the context of the other solvers it competes with. The lecture notes and homework code are on GitHub; the videos are on Bilibili.
CB-Geo — LearnMPM Concise educational notes The engineering/geomechanics presentation of the same cycle, which is a genuinely useful second angle — different vocabulary, different worries (convergence, quadrature error) than the graphics line.
NairnMPM documentation
also Contact Laws
Shipping code's user manual Not an introduction — the reference to read once you hit contact. A production engineering solver's documented answer to every multi-body question: per-pair contact laws, normal estimation, detection criteria, and the constants nobody else publishes.
IIT Delhi COL865 — Plasticity & MPM Lecture slides A compact, well-drawn treatment of the part most people find hardest: elastoplastic splitting and return mapping. Good as a second explanation of section 7.2.
Chenfanfu Jiang — MPM resources Curated index Not a tutorial itself; the maintained map of which paper or code to read next once you have outgrown the intros.
Wikipedia — Material point method Encyclopedia article Orientation and history in ten minutes. Useful for placing MPM among neighboring methods before committing to a longer read.

Read-the-code walkthroughs

These are implementations small enough to be read as tutorials, which for this method is often the faster path than prose.

ResourceStackBest for
taichi_mpm — mls-mpm88 C++ / CUDA (88 lines) The famous 88-line MLS-MPM. The whole method fits on one screen; reading it once is worth several chapters. Also ships as a Taichi Python example.
nialltl/incremental_mpm Unity / C# Jobs The code companion to the guide above, staged as separate readable examples rather than one optimized blob. The natural first port if you work in Unity.
electronicarts/pbmpm WebGPU / WGSL EA SEED's Position-Based MPM: the real-time reformulation that trades physical calibration for stability at large timesteps. Read the siggraph2024 branch. The clearest public example of what a game-shaped MPM looks like.
mls-mpm.js JavaScript A direct port of mpm88 that runs in a browser tab. Handy for poking at parameters with zero build setup.
vanish87/UnityMPM Unity / C# + compute CPU and GPU, 2D and 3D, side by side — useful specifically for seeing what changes when you move the same algorithm onto compute shaders.
CB-Geo mpm C++ / MPI Engineering-grade, cluster-parallel MPM. The counterexample to every shortcut a real-time solver takes; worth a skim to see which of your simplifications actually matter.
Grant Kot — 2010 applet notes
Internet Archive; original is gone. Code descendants: FluidCinder, MPM-Fluid
Short implementation note Historically the most consequential paragraph in hobbyist MPM: why quadratic B-splines over cubic for real time, and why to recompute density every frame instead of integrating it. Most of the browser MPM demos you have seen descend from this applet.