Why
Asher Zhu's UE5 slime showed how much character a blob of goo can have. I wanted to build my own at the engine level, with physics that hold up up close and a look that reads as clear, wet and alive rather than as particles. The approach and code are my own. It's an engine plugin rather than a Niagara setup, hooked into the renderer through a scene view extension, and it runs natively on Apple Silicon.
Simulation
- MLS-MPM (Hu et al. 2018) with APIC transfer on a quadratic B-spline stencil, substepped by a CFL condition on the pressure wave speed.
- Viscoplastic material after Yue et al. 2015. Particles store the elastic left Cauchy-Green tensor rather than F, so there's no per-particle SVD. Below yield it wobbles like jelly; above yield it flows, with Herschel-Bulkley shear-thinning.
- Surface tension as a grid-based continuum surface force (Brackbill 1992). Without it, pokes punch permanent holes; with it, dents heal and edges round off.
- Portable atomics. Particle-to-grid scatters in 16.16 fixed point with integer
InterlockedAdd, so it doesn't depend on float-atomic support, which differs between D3D12, Vulkan and Metal.
Scene collision
The slime collides with any mesh that has a distance field, with no authored colliders. The simulation runs in PostRenderBasePassDeferred, the earliest view-extension hook after the renderer updates the Global Distance Field for the frame. The grid update removes inward velocity with Coulomb friction, and particles that tunnel in are pushed out. It's a shader permutation, so platforms without distance fields fall back to the box. It costs about 0.6 ms.
A character, not a puddle
The body's shape is a mold: a signed distance field in the body frame. Goo outside it is pulled back in, and grid density control (after Thuerey et al. 2006) pushes goo from overfull cells into empty parts of the mold. The body frame comes from a deterministic two-pass GPU reduction (center of mass, velocity and rotation).
My first attempt gave each particle a rest position. It failed because mixing scrambles which particle belongs where, and the pulls cancel out. The mold doesn't care which particle goes where, so it re-goos however mixed things get.
Shape-shifting is a mold swap. Five volume-matched SDFs (blob, cube, donut, pill, star) blend into each other. Squash and lean come from springs driven by the body's velocity. The eyes ride whichever particle is nearest each socket, picked every frame with a packed (distance, index) atomic min, so they stay on the surface through any squash.
Rendering
- Ray-marched isosurface. The grid already holds a smooth fill fraction and its gradient (used by surface tension and the mold). The composite marches
fill = 0.5per pixel with bisection refinement, taking normals from the gradient and thickness from marching through to the exit point. Close up, there are no particle lumps for the refraction to magnify. - Two-surface lens refraction at IOR 1.33 with slight per-channel dispersion. Beer-Lambert absorption driven by thickness gives the clear teal body.
- Bubbles are particles that carry a radius. They rise, pop at the surface and respawn deep inside, splatted into their own depth target and shaded as air pockets.
- Reflections: a screen-space march against scene depth. Rays that escape over the sky sample the sky pixels themselves, and anything else falls back to the skylight SH.
- The pass runs in
BeforeDOFat internal resolution with jittered depth, so TSR anti-aliases the slime and depth of field treats it like scene geometry. - A cheaper screen-space splat mode (narrow-range filter, Truong & Yuksel 2018) remains for mid-distance shots.
Performance
Packaged Development build, Apple M5 Pro (Metal SM6), 2560×1440, Lumen on, 51,198 particles, 64³ grid. Each number is the median of 10 ProfileGPU captures, because single captures on Metal varied more than 3×.
| Scope | Collision on | Collision off |
|---|---|---|
| Simulation (54 substeps) | 8.20 ms | 7.63 ms |
| Rendering | 1.21 ms | 0.91 ms |
The substep count dominates cost. Simulated time per frame is capped at 1/60 s: substeps scale with simulated time, so without the cap a slow frame requests more substeps and the next frame is slower still.
Bugs worth telling
Texture atomics silently did nothing on Metal
Splatting was correct in logic but produced nothing. A debug view that tinted the whole screen proved the composite ran; one that wrote a fixed block of pixels, bypassing projection, proved the atomics weren't landing. Metal needs TexCreate_AtomicCompatible on any texture used with shader atomics. D3D12 doesn't.
The floor never pushed back
Particles were clamped 2.5 cells from the wall, but only cells 0–1 enforced the boundary, so no particle's 3×3×3 stencil ever touched a boundary cell. A GPU readback showed particle 0 pinned at the clamp with a velocity of −180 and still falling.
An unbound uniform buffer only the cook caught
The distance-field permutation includes engine helpers that read the View uniform buffer, which my parameter struct didn't declare. The editor ran it without complaint while reading garbage; the cook's shader-binding validation failed the build.
The shape mold turned into a rocket
Anchored at the body's lowest point, density control pushed the bottom layer upward, which moved the anchor up, which pushed again, and the slime flew. Anchoring on the lowest particle that is actually resting on something fixed it.
References
- Hu et al. 2018, A Moving Least Squares Material Point Method with Displacement Discontinuity and Two-Way Rigid Body Coupling
- Yue et al. 2015, Continuum Foam: A Material Point Method for Shear-Dependent Flows
- Brackbill, Kothe, Zemach 1992, A Continuum Method for Modeling Surface Tension
- Thuerey et al. 2006, density control for fluid shape guidance
- Truong & Yuksel 2018, A Narrow-Range Filter for Screen-Space Fluid Rendering