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.
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.
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 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
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
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.
This solver runs the whole simulation in a normalized box, with
gridN cells per axis, so cell width is . 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.
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 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 :
and . 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 : . 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.
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
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 over its own nine nodes. A's stencil now contains nodes that B polluted, so A comes back slower than . 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.
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, . The production
kernel scatters , 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:
- the particle's affine velocity field — its local spin and stretch, which plain averaging would throw away (section 6);
-
the particle's internal stress, converted to a force. This is the
MLS-MPM trick: instead of evaluating shape-function gradients, the stress is scaled by
and simply added to
C, so one scatter delivers momentum and force together.
// 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.
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.
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.
C per
particle that records the local velocity gradient, so the scatter carries
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?":
| Scheme | Year | Transfers back | Behavior |
|---|---|---|---|
| PIC | 1960s | grid velocity | Rock stable, and visibly dead — every step is a blur of the velocity field. |
| FLIP | 1986 | grid velocity change | Keeps the energy, keeps the noise too — particles accumulate motion the grid never saw. |
| APIC | 2015 | velocity + affine matrix C | Stable like PIC, lively like FLIP, and conserves angular momentum. The modern default. |
| MLS-MPM | 2018 | same C, reused as the velocity gradient | Notices C and the stress-divergence gradient are the same object; drops an entire evaluation. ~2× faster. |
Older code often exposes a PIC/FLIP blend: , applied once per substep. That is not a material property. It is exactly FLIP plus a damper whose rate is — so halving your substep doubles the damping and silently changes every material you tuned. If you need a drag knob, expose a rate in 1/s and derive . 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.
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 . The diagonal of
holds the singular values — the stretch factors
along the principal axes. Snow plasticity clamps each one into
. Whatever you clamped away
is now permanent deformation. Sand does the same thing against a cone rather than a box.
c is
cohesion: grains that stick to each other and to walls.
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.
| Preset | mode | E | hardening | Character |
|---|---|---|---|---|
| Soft | 0 snow | 1500 | 4 | Stiffest rung of the snow ladder; holds a firm mound. |
| Mushy | 0 snow | 600 | 2 | The crater reference: bowl plus debris plume. |
| Goopy | 0 snow | 250 | 1 | Mid-ladder; looser than mushy. |
| Wet | 0 snow | 150 | 0.7 | Near-fluid slush. |
| Slush | 0 snow | 90 | 0.5 | Softest rung. Deforms and stays deformed. |
| Elastic | 0 snow | 1200 | 0 | Yields pushed to 1e9 so the clamp never fires — rubber, jelly, the bouncing-armadillo material. |
| Granular | 1 sand | 400 | 0 | Drucker-Prager, frictionAngle = 38°. Piles, avalanches, no jiggle. |
| CohesiveGranular | 1 sand | 400 | 0 | Same plus cohesion = 0.0006. The packed-snow answer: clumps, sticks to walls, still blasts apart. |
| Water | 2 fluid | — | — | Tait equation of state, pressure only, no shear stress at all. bulkModulus = 100. |
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
| Knob | Lives on | Actually controls |
|---|---|---|
| gridN | solver | Feature size, interaction range, timestep ceiling. The most consequential number in the whole configuration. |
| substep dt | stepper | Stability, and — through the boundary law — how far material creeps on a slope. Not a performance dial. |
| Gravity | solver | The balance between weight and material strength: mound versus pancake. Not playback speed. |
| Damping | solver | Ring-out. Applied as a rate: . |
| playback speed | stepper | Playback speed. This is the dial you actually wanted when you reached for gravity. |
| youngsModulus | material | Elastic rebound. Raising it buys springiness and costs stability. |
| cohesion | material | How steep and tall a pile can stand. The real "hardness" dial. |
| frictionAngle | material | Angle of repose, in degrees. |
| hardening | material | Whether yielding permanently softens the material. Terrain wants it; bodies do not. |
9.2 · The five traps
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.
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.
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.
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.
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.
9.3 · Symptom → knob
| What you see | Most likely cause | What to change |
|---|---|---|
| Everything explodes / NaNs | CFL violated — a particle crosses more than a fraction of a cell per step | lower the substep dt, or lower E / bulkModulus |
| Jiggles like jello, never settles | Elastic energy with nowhere to go | raise Damping; lower E; consider a granular mode |
| Pile spreads into a pancake | Gravity is winning against material strength | lower Gravity, or raise cohesion |
| Pile is too steep / too tall | Excess cohesion or friction | lower cohesion, lower frictionAngle |
| Material creeps down a slope forever | Fraction-retaining boundary applied per step | lower the substep dt and re-measure over a long window |
| Two separate bodies fuse on contact | Single velocity field per node — they share it | enable multi-field contact — one velocity field per body |
| A limb snaps off or loses its pose | Feature spans too few cells | thicken the geometry, or raise gridN |
| Repeatedly hit body erodes into dust | Snow hardening permanently softens on each yield | hardening = 0 |
| Fluid level slowly drops | Volume integrated through F leaks under shear | enable grid volume recovery |
| Material won't level out flat | Anything with a yield surface pools as a heap; friction stops the slide, it does not level | over-fill and delete above the waterline |
| Sand re-settles permanently fluffed after an impact | The yield cone's apex projection forgets the expansion | enable 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:
- 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.
- 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.
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.
| Concept | Where it lives in a typical solver |
|---|---|
| The three-line B-spline weights | a small shared weight function, called by both transfers |
Phase 2 — scatter, and the stress fused into C | the P2G kernel |
| Phase 3 — , gravity, colliders, walls | the grid-update kernel |
Phase 4 — gather, rebuild C, advance F, advect | the G2P kernel |
| The constitutive branch (snow / sand / fluid / tracer) | inside both transfers, keyed on a per-particle material mode |
| Material constants and presets | a material table, one row per material |
| Gravity, damping, colliders, grid size | the solver's own parameters — not the material's (section 9) |
| Fixed-timestep playback and catch-up | a 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.
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.
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
| Resource | Format | Best 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.
| Resource | Stack | Best 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. |