Three.js From Zero · Article s13-04
R3F + Rapier Physics
R3F + Rapier Physics is Article s13-04 of Three.js From Zero, a MasterAllArts free interactive lesson for artists learning creative 3D on the web.
Season 13 · Article 04 · R3F Mastery
@react-three/rapier wraps the Rust-WASM Rapier physics engine into JSX. Rigid bodies, joints, sensors, character controllers — production-grade physics with React ergonomics. 200kb WASM, runs at 60fps with thousands of objects.
Code-walkthrough article
Best paired with a fresh project: npm i three @react-three/fiber @react-three/rapier. The snippets here run as-is in a Vite + React app.
The mental model
Every physics object is a <RigidBody> wrapping a regular <mesh>. The rigid body provides collision shape (auto-detected from the mesh), mass, friction, restitution. The mesh provides visuals. Rapier simulates positions; R3F syncs them back to the mesh transforms each frame.
Hello, gravity
import { Physics, RigidBody } from '@react-three/rapier';
<Canvas>
<Physics gravity={[0, -9.81, 0]}>
{/* Falling sphere */}
<RigidBody colliders="ball">
<mesh position={[0, 5, 0]}>
<sphereGeometry args={[0.5]} />
<meshStandardMaterial color="orange" />
</mesh>
</RigidBody>
{/* Static ground */}
<RigidBody type="fixed" colliders="cuboid">
<mesh position={[0, -1, 0]} receiveShadow>
<boxGeometry args={[10, 0.2, 10]} />
<meshStandardMaterial />
</mesh>
</RigidBody>
</Physics>
</Canvas>
Drop the sphere, it lands, it stops. The ball colliders="ball" tells Rapier to use a sphere collision shape (more efficient than mesh hull); type="fixed" on the ground keeps it from falling.
Forces and impulses
const ballRef = useRef();
return (
<>
<RigidBody ref={ballRef} colliders="ball">
<mesh><sphereGeometry /><meshStandardMaterial /></mesh>
</RigidBody>
<button onClick={() => ballRef.current.applyImpulse({ x: 0, y: 10, z: 0 }, true)}>
Jump
</button>
</>
);
The ref points to the Rapier rigid-body instance. applyImpulse = instantaneous velocity change; applyForce = continuous accumulation. The true arg wakes the body if it had gone to sleep.
Joints — connecting bodies
import { useFixedJoint, useSphericalJoint, useRevoluteJoint } from '@react-three/rapier';
function Pendulum() {
const anchor = useRef();
const ball = useRef();
useSphericalJoint(anchor, ball, [[0,0,0], [0,1,0]]); // joint position on each body
return (
<>
<RigidBody ref={anchor} type="fixed" position={[0, 4, 0]} />
<RigidBody ref={ball} colliders="ball" position={[1, 3, 0]}>
<mesh><sphereGeometry /></mesh>
</RigidBody>
</>
);
}
Five joint types: fixed (rigid attach), spherical (free rotation), revolute (single axis hinge), prismatic (slider), generic (custom). Use them for ragdolls (spherical at joints), doors (revolute), pistons (prismatic), or a chain (sequence of spherical).
Sensors — collision events without physics response
<RigidBody type="fixed" sensor onIntersectionEnter={() => pickupCoin()}>
<mesh><coinGeometry /></mesh>
</RigidBody>
A sensor doesn't block movement — bodies pass through — but fires intersection callbacks. Perfect for game pickups, trigger zones, "exit area" detection.
Character controllers
import { CapsuleCollider, RigidBody, useRapier } from '@react-three/rapier';
function Player() {
const ref = useRef();
const { rapier, world } = useRapier();
useFrame(() => {
const vel = ref.current.linvel();
// Read input, apply impulses for movement, etc.
if (keysPressed.has('w')) {
ref.current.applyImpulse({ x: 0, y: 0, z: -0.5 });
}
});
return (
<RigidBody ref={ref} colliders={false} lockRotations>
<CapsuleCollider args={[0.5, 0.3]} /> {/* half-height, radius */}
<mesh><capsuleGeometry /></mesh>
</RigidBody>
);
}
lockRotations stops the player from tumbling when bumping walls. colliders={false} + manual <CapsuleCollider /> lets you customize the shape independently of the mesh.
Debug rendering
<Physics debug>...</Physics>
One prop. Renders every collider shape as a wireframe overlay so you can see where Rapier thinks your bodies are. Always-on during development.
Common first-time pitfalls
colliders={false} on a body and forgot to add a manual collider component inside.colliders="trimesh" (mesh hull) for every body. Use "hull", "cuboid", "ball" when possible — they're 10–100× cheaper.linearDamping and ensure bodies actually come to rest. Otherwise increase Physics step rate or use a coarser collision tolerance.Exercises
- Tower of blocks. 50 cuboid bodies stacked. Click to apply impulse to a random one. Watch them collapse.
- Ragdoll. 7 spherical joints between body parts (head, chest, hips, two upper arms, two upper legs). Apply impulse to head; observe.
- Coin collector. Sensor rigid bodies for coins; capsule player; collect = increment React state. Game state from physics events.
UP NEXT
S13-05 — R3F + Zustand State → Game-scale state without re-render storms.