šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Physics in Three.js 3D WebGL

Learn about Physics in this comprehensive Three.js 3D WebGL tutorial. Understand how Physics Engines work alongside WebGL renderers and how `@react-three/rapier` simplifies everything.

⚔ Total XP: 0|šŸ’» threejs XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core physics simulation concepts.

Quick Quiz //

What must a vanilla Three.js + Cannon.js app do every animation frame to make physics visible?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Listen up. If you're building modern applications, understanding Physics in Three.js 3D WebGL is non-negotiable. This is where simple logic turns into intelligent behavior.

1Why You Need a Physics Engine

You can move objects manually, but if you want realistic gravity, collisions, and bouncing, you need a Physics Engine.

Hand-rolling gravity by nudging a mesh's position.y down a little each frame works fine for a single falling object, but it falls apart once objects need to collide, stack, or transfer momentum to one another — that requires broad-phase collision detection, contact resolution, and constraint solving, which is exactly what a dedicated physics engine is built to do.

Physics engines like Cannon.js and Rapier run this simulation independently of Three.js's rendering loop, computing positions, rotations, and collision responses in their own internal units before you ever touch a mesh.

āœ•
—
+
// šŸŽ Adding gravity to the world
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

2Three.js Has No Built-In Physics

Three.js doesn't have built-in physics. We must pair it with a separate physics library like Cannon.js or Rapier.

Three.js only owns the rendering side of the equation — geometries, materials, cameras, and the WebGL draw calls that turn them into pixels. It has no concept of mass, velocity, or collision shapes anywhere on a Mesh or Scene.

Cannon.js (and its actively maintained fork cannon-es) run pure JavaScript physics math, while Rapier is a Rust library compiled to WebAssembly, which makes it noticeably faster for scenes with many simultaneously colliding bodies.

āœ•
—
+
// Three.js handles the GRAPHICS.
// Cannon.js handles the MATH.
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

3Checkpoint: Does Three.js Include Physics?

Does the core Three.js library include built-in physics for calculating gravity and collisions between meshes?

The answer is no — THREE.Physics doesn't exist, and there's no gravity property anywhere on a Mesh or Scene. Three.js deliberately stays a rendering-only library so it can be paired with whichever physics engine best fits a given project.

This separation is a common pattern in graphics programming: the renderer draws whatever state it's given, while a completely separate simulation system decides what that state should be each frame.

āœ•
—
+
// Does THREE.Physics exist?
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

4The Physics World and Body Pairing

How does it work? You create a hidden 'Physics World'. Every visible 3D Mesh gets a matching invisible 'Body' in the physics world.

The World object is the physics engine's own scene graph — it tracks every rigid body, its shape, mass, and material properties, completely separately from THREE.Scene. Setting world.gravity.set(0, -9.82, 0) applies Earth-like acceleration to every dynamic body inside it.

Each Body you add to the world is invisible on its own; it's pure math (position, velocity, collision shape) with nothing to render. You always keep a Mesh and a Body as a paired set, one for looks and one for physics.

āœ•
—
+
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0); // Earth gravity!
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

5Stepping the World and Syncing Transforms

In your animation loop, you tell the physics engine to calculate the next step, and then you copy the coordinates from the invisible Body to the visible Mesh.

world.step(1/60) advances the simulation by a fixed time slice, resolving gravity, collisions, and constraints for every body in the world during that tick. It doesn't touch your meshes at all — it only updates each physics body's internal position and quaternion.

The copy calls are what actually make the simulation visible: mesh.position.copy(body.position) and mesh.quaternion.copy(body.quaternion) pull the freshly computed physics transform onto the rendered object every frame, which is why forgetting this step is such a common bug — the physics runs perfectly, but nothing on screen moves.

āœ•
—
+
function animate() {
  world.step(1/60); // Math!
  mesh.position.copy(body.position); // Sync visual to math
  mesh.quaternion.copy(body.quaternion);
}
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

6Checkpoint: Syncing Body to Mesh Each Frame

In vanilla Three.js physics, what must you constantly do inside your requestAnimationFrame loop?

The correct answer is copying Body coordinates to Mesh coordinates. Every frame you must call world.step() to advance the simulation, then sync each tracked mesh's position and quaternion from its corresponding body.

Skip the step call and gravity never advances; skip the copy and the physics simulation runs invisibly while every mesh stays frozen in its last rendered position — both are among the most common bugs when wiring up a physics engine by hand.

āœ•
—
+
function animate() {
  ???
}
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

7Simplifying Physics With @react-three/rapier

Doing this manually is tedious. React Three Fiber has a package called @react-three/rapier that abstracts all the syncing for you!

@react-three/rapier wraps the Rapier WASM engine in a declarative React API — the <Physics> component creates and steps the World for you automatically on every frame, hooked directly into React Three Fiber's render loop.

This eliminates the manual world.step() and position-copying boilerplate entirely; you describe which meshes should behave physically using components, and the library handles the Body-to-Mesh synchronization internally.

āœ•
—
+
import { Physics, RigidBody } from '@react-three/rapier';

<Canvas>
  <Physics>
     {/* Physics World */}
  </Physics>
</Canvas>
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

8Wrapping Meshes in RigidBody

Any mesh you want to be affected by gravity must be wrapped in a <RigidBody>. It's that simple.

<RigidBody> automatically infers a collider shape from its child geometry by default (a box for boxGeometry, a sphere for sphereGeometry, and so on), and it keeps the wrapped mesh's transform in sync with the underlying Rapier rigid body every frame.

By default a <RigidBody> is 'dynamic' — affected by gravity and collisions — but that behavior can be overridden with the type prop, which is exactly what's needed for objects like floors that should never fall.

āœ•
—
+
<RigidBody>
  <mesh>
    <boxGeometry />
  </mesh>
</RigidBody>
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

9Checkpoint: The RigidBody Component

In @react-three/rapier, what component must you wrap around your <mesh> so that it falls due to gravity?

The answer is <RigidBody>. Without it, a mesh is purely visual — React Three Fiber will render it, but Rapier has no rigid body registered for it, so gravity and collisions simply don't apply.

A bare <mesh> and a <mesh> wrapped in <RigidBody> look identical the moment the scene mounts; the difference only becomes visible once the physics world starts stepping and one of them starts falling while the other stays frozen in place.

āœ•
—
+
<???>
  <mesh />
</???>
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

10Fixed RigidBodies for Static Geometry

But wait, what about the floor? We don't want the floor to fall! We must set the floor's RigidBody type to 'fixed'.

A type="fixed" RigidBody is immovable — it still participates in collisions (other bodies bounce off it) but gravity and forces never move it, which is exactly the behavior a floor, wall, or terrain mesh needs.

The colliders="cuboid" prop on the falling box explicitly sets its collision shape to a box matching the geometry's bounds, which is more predictable than relying on shape auto-detection for non-trivial geometries.

āœ•
—
+
 {/* A falling box */}
<RigidBody colliders="cuboid">
  <mesh><boxGeometry /></mesh>
</RigidBody>

{/* A static floor */}
<RigidBody type="fixed">
  <mesh><planeGeometry /></mesh>
</RigidBody>
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

11Simulating a Physics Drop With useFrame

Let's see a 3D Preview! Because we can't load Rapier here natively, we will simulate a physics drop using pure math and useFrame.

This preview hand-rolls the same gravity-and-bounce behavior a real physics engine would give you, just without collision detection between multiple bodies: velocityY accumulates a small downward increment every frame inside useFrame, and that velocity is added to the mesh's position.y.

The floor collision check (if (position.y < -1.5)) clamps the ball back to the floor height and flips velocityY negative, multiplying it by 0.8 so each bounce loses 20% of its energy — a cheap way to fake restitution without an actual physics library.

āœ•
—
+
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';

const BouncingBall = () => {
  const ref = useRef();
  let velocityY = 0;
  
  useFrame(() => {
    if (ref.current) {
      velocityY -= 0.01; // Gravity!
      ref.current.position.y += velocityY;
      
      // Collision with floor (y = -1.5)
      if (ref.current.position.y < -1.5) {
        ref.current.position.y = -1.5;
        velocityY *= -0.8; // Bounce and lose energy
      }
    }
  });

  return (
    <mesh ref={ref} position={[0, 3, 0]}>
      <sphereGeometry args={[0.5, 32, 32]} />
      <meshStandardMaterial color="#00F0FF" />
    </mesh>
  );
};

const App = () => (
  <Canvas camera={{ position: [0, 0, 5] }}>
    <ambientLight intensity={0.5} />
    <directionalLight position={[5, 5, 5]} />
    <BouncingBall />
    <mesh position={[0, -2, 0]} rotation={[-Math.PI/2, 0, 0]}>
      <planeGeometry args={[10, 10]} />
      <meshStandardMaterial color="gray" />
    </mesh>
  </Canvas>
);

render(<App />);
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

12Physics Lesson Complete

And that's it! By combining Three.js, React Three Fiber, Shaders, and Physics, you can literally build anything you can imagine.

With rendering, cameras, materials, shaders, and now physics in your toolkit, you have every core building block a real-time 3D web application needs — the remaining work is mostly about composing these pieces for your specific project.

Physics engines like Rapier scale to hundreds of simultaneously simulated bodies with careful use of fixed timesteps and sleeping bodies (physics bodies that stop being simulated once they come to rest), so what you've learned here is directly usable well beyond toy demos.

āœ•
—
+
// šŸ† You have completed the Three.js journey!
localhost:3000
Browser Preview
WebGL Output
3D Scene rendered. Objects: 4, Draw Calls: Optimized.

13Step-by-Step Breakdown

You can move objects manually, but if you want realistic gravity, collisions, and bouncing, you need a Physics Engine.

Three.js doesn't have built-in physics. We must pair it with a separate physics library like Cannon.js or Rapier.

Does the core Three.js library include built-in physics for calculating gravity and collisions between meshes?

  • →Yes, it is built-in
  • →No, you must use an external library

How does it work? You create a hidden 'Physics World'. Every visible 3D Mesh gets a matching invisible 'Body' in the physics world.

In your animation loop, you tell the physics engine to calculate the next step, and then you copy the coordinates from the invisible Body to the visible Mesh.

In vanilla Three.js physics, what must you constantly do inside your requestAnimationFrame loop?

  • →Delete overlapping objects
  • →Copy Body coordinates to Mesh coordinates
  • →Nothing, it's automatic

Doing this manually is tedious. React Three Fiber has a package called @react-three/rapier that abstracts all the syncing for you!

Any mesh you want to be affected by gravity must be wrapped in a <RigidBody>. It's that simple.

In @react-three/rapier, what component must you wrap around your <mesh> so that it falls due to gravity?

  • →PhysicsBox
  • →Gravity
  • →RigidBody

But wait, what about the floor? We don't want the floor to fall! We must set the floor's RigidBody type to 'fixed'.

Let's see a 3D Preview! Because we can't load Rapier here natively, we will simulate a physics drop using pure math and useFrame.

And that's it! By combining Three.js, React Three Fiber, Shaders, and Physics, you can literally build anything you can imagine.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Respect prefers-reduced-motion for Physics-Driven Animation

Continuously falling, bouncing, or colliding objects driven by a physics simulation can trigger discomfort for users with vestibular motion sensitivity — check window.matchMedia('(prefers-reduced-motion: reduce)') and offer a way to pause the simulation or settle bodies into a static resting pose instead of an active drop.

const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; <Physics paused={reduceMotion}>...</Physics>

SEO Implications

  • 1

    Physics Configuration Is Runtime State, but the Concepts Are Crawlable

    Gravity values, RigidBody types, and collider shapes are runtime simulation state with no crawlable footprint, but well-written, indexable explanations of RigidBody, colliders, and restitution are genuine SEO value, since these are frequently searched Three.js and React Three Fiber terms.

Best Practices

Use type="fixed" for Static Geometry, Not Extreme Mass

Mark floors, walls, and terrain with type="fixed" rather than trying to fake immovability with a very large mass value. Fixed bodies are excluded from the dynamic solver entirely, which is both physically correct and cheaper to simulate.

Let Bodies Sleep When They Stop Moving

Physics engines like Rapier automatically put bodies to sleep once their velocity drops below a threshold, removing them from the active simulation step. Scenes with many resting objects rely on this to stay performant, so avoid continuously applying forces to bodies that don't need to keep moving.

Frequent Bugs

THE BUG

The mesh doesn't move even though the physics simulation appears to be running correctly.

THE FIX

In vanilla Three.js plus Cannon.js, you forgot to copy the Body's position and quaternion onto the Mesh every frame. In @react-three/rapier, check that the mesh is actually wrapped in a <RigidBody> — a bare <mesh> has no physics body registered for it at all.

Real-World Examples

Interactive Product Configurators with Falling Parts

E-commerce configurators often use RigidBody physics so accessories or attachments visually drop and settle into place when added to a product, giving the 3D preview a designed, physical feel instead of parts snapping instantly into position.

<RigidBody colliders="hull">
  <mesh geometry={accessoryGeometry} />
</RigidBody>

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Not reading error messages carefully

Uncaught TypeError: Cannot read properties of undefined (reading 'length') // Solution: Ensure the variable you are calling .length on is initialized as a string or an array, not undefined.

The Solution //

Most of the time, the compiler or interpreter tells you exactly what line caused the crash and why. Read stack traces from the top down to identify the root cause.

The Error //

Hardcoding sensitive credentials

// Wrong const API_KEY = 'sk-123456789'; // Correct const API_KEY = process.env.API_KEY;

The Solution //

Never hardcode API keys, passwords, or secrets in your source code. Use environment variables (.env files) to keep them secure and out of version control.

Continue Learning