Navigate
Search topics across all sections
Physics with Rapier
Drop a ball on a table and it bounces. Push a box and it slides. Stack blocks and they topple. Rapier turns your 3D scene into a real physics simulation where gravity, collisions, and forces just work. The react-three/rapier library brings the Rapier physics engine to R3F with a declarative API that feels like regular React components.
You build a game where clicking a ball should launch it upward. Instead of using the physics API, you update the ball's position prop with React state. The ball teleports to the new position, clipping through walls and floors because the physics engine never knew it moved.
RigidBody position changed via React props. Object teleported from [0, 0, 0] to [0, 5, 0]. Collision detection skipped -- object passed through floor collider. Use rigidBodyRef.current.applyImpulse() for physics-driven movement.
Real-world
Imagine building a Rube Goldberg machine. You place a ball at the top of a ramp, a domino chain in the middle, and a bucket at the end. You do not animate each piece individually -- you set up the initial conditions and let gravity do the rest.
That is exactly what Rapier does. You describe the shapes, masses, and materials. You tell it about gravity. Then you press play and the physics engine calculates what happens -- 60 times per second, with proper collision detection, friction, and bouncing.
<Physics> is your universe. <RigidBody> is any object that lives in that universe and obeys its laws.
How Rapier Physics Works
Wrap in <Physics>
Creates a Rapier world with gravity
Add <RigidBody>
Each one becomes a physics-simulated object
Auto colliders
Rapier generates collision shapes from your meshes
Simulation runs
Forces, gravity, collisions computed each frame
Meshes sync
Visual positions update to match physics state
Newton's Playground
A physics sandbox. Boxes are stacked on a floor, ready to be knocked over. Use the controls to drop balls from above, adjust gravity strength, and change bounciness. Watch how objects interact, stack, bounce, and topple with real-time physics.
Building It Step by Step
Step 1 -- Set up the physics world
Wrap your scene content in a <Physics> component. This creates the Rapier simulation world. The gravityprop takes a [x, y, z] vector -- Earth gravity is roughly [0, -9.81, 0].
import { Physics, RigidBody } from "@react-three/rapier"
function Scene() {
return (
<Canvas>
<Physics gravity={[0, -9.81, 0]}>
{/* Everything inside here is part of the simulation */}
</Physics>
</Canvas>
)
}Step 2 -- Add rigid bodies
Wrap any mesh in <RigidBody> to make it a physics object. By default it is dynamic (affected by gravity and forces). Use type="fixed" for immovable objects like floors and walls.
<Physics gravity={[0, -9.81, 0]}>
{/* Dynamic -- falls and bounces */}
<RigidBody restitution={0.7} friction={0.5}>
<mesh>
<sphereGeometry args={[0.5, 32, 32]} />
<meshStandardMaterial color="tomato" />
</mesh>
</RigidBody>
{/* Fixed -- immovable ground */}
<RigidBody type="fixed">
<mesh>
<boxGeometry args={[10, 0.5, 10]} />
<meshStandardMaterial color="slategray" />
</mesh>
</RigidBody>
</Physics>restitution controls bounciness (0 = no bounce, 1 = perfect bounce). friction controls sliding resistance.
Step 3 -- Apply forces and impulses
To move physics objects, use the rigid body API. Get a ref to the RigidBody and call methods like applyImpulse (instant push) or applyForce (continuous push).
import { useRef } from "react"
import { RigidBody, RapierRigidBody } from "@react-three/rapier"
function LaunchBall() {
const body = useRef<RapierRigidBody>(null)
const launch = () => {
body.current?.applyImpulse(
{ x: 0, y: 8, z: 0 }, // upward push
true // wake up if sleeping
)
}
return (
<RigidBody ref={body} colliders="ball">
<mesh onClick={launch}>
<sphereGeometry args={[0.4]} />
<meshStandardMaterial color="dodgerblue" />
</mesh>
</RigidBody>
)
}What you just learned
<Physics> creates a Rapier simulation world with configurable gravity
<RigidBody> makes any mesh a physics object -- dynamic by default, or type='fixed' for static
Colliders are auto-generated from mesh geometry, or you can specify 'ball', 'cuboid', etc.
Move physics objects with applyImpulse or applyForce, never by changing position props
Restitution controls bounciness (0-1+), friction controls sliding resistance
Question
What is the difference between applyImpulse and applyForce? An impulse is an instant push -- like hitting a billiard ball with a cue. A force is continuous -- like wind pushing a sail. Use impulse for one-time events (clicks, jumps) and force for ongoing effects (thrusters, magnets) applied inside useFrame.
Think about it...
You set restitution to 1.0 on both a ball and the floor. You drop the ball from 3 meters. What happens?
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Set gravity to -1 — moon gravity!
Try This!
Beginner
Set bounciness to 1.2 — super bouncy!
Try This!
Beginner
Drop 15 balls at once — chaos!
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function Ball({ pos }) {
// This teleports the object, ignoring physics!
return (
<RigidBody position={pos}>
<mesh>
<sphereGeometry />
</mesh>
</RigidBody>
)
}
// Updating pos re-renders and teleports
<Ball pos={[newX, newY, newZ]} /><Canvas>
{/* No <Physics> wrapper! */}
<RigidBody>
<mesh>
<boxGeometry />
</mesh>
</RigidBody>
</Canvas><Physics>
{/* Balls just fall forever */}
<RigidBody>
<mesh>
<sphereGeometry />
</mesh>
</RigidBody>
</Physics>Best Practices
Use simple colliders when possible
Sphere and cuboid colliders are much faster than trimesh colliders. Use colliders="ball" or colliders="cuboid" instead of the auto-generated mesh shape for better performance.
Set type="fixed" on static objects
Floors, walls, and platforms should be fixed. This tells Rapier they never move, allowing major performance optimizations in collision detection.
Limit active rigid bodies
Each dynamic rigid body adds computational cost. For scenes with many objects, use type="kinematicPosition"for objects you control manually, and keep truly dynamic objects under ~100 for smooth performance.
Use events for game logic
Rapier fires collision events via onCollisionEnterand onCollisionExit on RigidBody. Use these to trigger game logic (scoring, damage, sound effects) instead of checking positions manually.