Incremental 3D Lesson

Build a 3D scene one piece at a time.

Start from an empty canvas. Make the scene explicit, add a group, add geometry and material, wrap them into a mesh, then add light, camera control, and motion.

Incremental Builder

How this scene is built

Each section adds one piece to the scene. Read the explanation and code on the left, and the render on the right updates to match the active step.

Common use cases

This pattern is most useful for a landing page hero, a login or sign-in screen, an onboarding intro, a product reveal, or a marketing section where one strong 3D moment carries the page.

Step 1

As this card reaches the middle of the page, the live scene updates.

Canvas

This creates the renderer and an empty Three.js scene. React Three Fiber also gives you a default camera automatically.

Renderer
Scene

Nothing visible yet

The scene is real, but there is no mesh to draw yet. A group is only structure. Geometry and material are only parts until you combine them into a mesh.

Active

1/7

Why it matters

Without `Canvas`, nothing can render.

import { Canvas } from "@react-three/fiber" return <Canvas />
Code At This Step
01import { Canvas } from "@react-three/fiber"
02
03export default function Example() {
04 return <Canvas />
05}

Step 2

As this card reaches the middle of the page, the live scene updates.

Add a group

A `group` is a scene-graph container. It does not draw anything by itself, but it holds transform values like position, rotation, and scale.

Why it matters

You animate the group later so every child inside it moves together.

return ( <Canvas> <group />
Code At This Step
01import { Canvas } from "@react-three/fiber"
02
03export default function Example() {
04 return (
05 <Canvas>
06 <group />
07 </Canvas>
08 )
09}

Step 3

As this card reaches the middle of the page, the live scene updates.

Define geometry and material

Now you create the shape and the surface, but you still have not made a renderable object yet.

Why it matters

Geometry and material are separate pieces. The mesh is what combines them.

import * as THREE from "three"const geometry = new THREE.BoxGeometry(1, 1, 1)const material = new THREE.MeshBasicMaterial({ color: "#67a7ff" })
Code At This Step
01import { Canvas } from "@react-three/fiber"
02import * as THREE from "three"
03
04const geometry = new THREE.BoxGeometry(1, 1, 1)
05const material = new THREE.MeshBasicMaterial({ color: "#67a7ff" })
06
07export default function Example() {
08 return (
09 <Canvas>
10 <group />
11 </Canvas>
12 )
13}

Step 4

As this card reaches the middle of the page, the live scene updates.

Make a mesh

Once geometry and material are combined inside a `mesh`, Three.js finally has an object it can draw.

Why it matters

A mesh is the renderable object.

<group> <mesh geometry={geometry} material={material} /> </group>
Code At This Step
01import { Canvas } from "@react-three/fiber"
02import * as THREE from "three"
03
04const geometry = new THREE.BoxGeometry(1, 1, 1)
05const material = new THREE.MeshBasicMaterial({ color: "#67a7ff" })
06
07export default function Example() {
08 return (
09 <Canvas>
10 <group>
11 <mesh geometry={geometry} material={material} />
12 </group>
13 </Canvas>
14 )
15}

Step 5

As this card reaches the middle of the page, the live scene updates.

Add light

Now switch to a light-reactive material and add lights. The box starts reading as a real 3D form instead of a flat colored block.

Why it matters

Light reveals shape.

const material = new THREE.MeshStandardMaterial({ color: "#67a7ff" }) <ambientLight intensity={0.45} /> <directionalLight position={[2, 2, 3]} intensity={1.2} />
Code At This Step
01import { Canvas } from "@react-three/fiber"
02import * as THREE from "three"
03
04const geometry = new THREE.BoxGeometry(1, 1, 1)
05const material = new THREE.MeshStandardMaterial({ color: "#67a7ff" })
06
07export default function Example() {
08 return (
09 <Canvas>
10 <ambientLight intensity={0.45} />
11 <directionalLight position={[2, 2, 3]} intensity={1.2} />
12 <group>
13 <mesh geometry={geometry} material={material} />
14 </group>
15 </Canvas>
16 )
17}

Step 6

As this card reaches the middle of the page, the live scene updates.

Add an explicit camera

Canvas already gave you a default camera. Adding your own `PerspectiveCamera` makes the framing intentional.

Why it matters

The camera decides what the user actually sees.

import { PerspectiveCamera } from "@react-three/drei" <PerspectiveCamera makeDefault
Code At This Step
01import { Canvas } from "@react-three/fiber"
02import { PerspectiveCamera } from "@react-three/drei"
03import * as THREE from "three"
04
05const geometry = new THREE.BoxGeometry(1, 1, 1)
06const material = new THREE.MeshStandardMaterial({ color: "#67a7ff" })
07
08export default function Example() {
09 return (
10 <Canvas>
11 <PerspectiveCamera
12 makeDefault
13 position={[0, 0.2, 4.9]}
14 fov={32}
15 onUpdate={(camera) => camera.lookAt(0, 0.1, 0)}
16 />
17 <ambientLight intensity={0.45} />
18 <directionalLight position={[2, 2, 3]} intensity={1.2} />
19 <group>
20 <mesh geometry={geometry} material={material} />
21 </group>
22 </Canvas>
23 )
24}

Step 7

As this card reaches the middle of the page, the live scene updates.

Use refs, then build a real reveal

React refs give you the real Three.js objects. With refs to the group, mesh, and light, you can stage a proper reveal instead of a basic loop.

Why it matters

Refs are the bridge between React JSX and the live Three.js objects you want to move.

import { useEffect, useRef } from "react"import gsap from "gsap" const groupRef = useRef<THREE.Group>(null)
Code At This Step
01import { Canvas } from "@react-three/fiber"
02import { PerspectiveCamera } from "@react-three/drei"
03import { useEffect, useRef } from "react"
04import gsap from "gsap"
05import * as THREE from "three"
06
07const geometry = new THREE.BoxGeometry(1, 1, 1)
08const material = new THREE.MeshStandardMaterial({ color: "#67a7ff" })
09
10export default function Example() {
11 const groupRef = useRef<THREE.Group>(null)
12 const meshRef = useRef<THREE.Mesh>(null)
13 const lightRef = useRef<THREE.DirectionalLight>(null)
14
15 useEffect(() => {
16 if (!groupRef.current || !meshRef.current || !lightRef.current) return
17 groupRef.current.position.y = -1
18 groupRef.current.rotation.set(0.95, -0.75, 0)
19 meshRef.current.scale.setScalar(0.72)
20 lightRef.current.position.set(3, 1.5, 2.2)
21 lightRef.current.intensity = 0.45
22
23 const timeline = gsap.timeline({ repeat: -1, repeatDelay: 0.8 })
24 timeline.to(groupRef.current.position, { y: 0.15, duration: 1.15, ease: 'power3.out' }, 0)
25 timeline.to(groupRef.current.rotation, { x: 0.18, y: 0.35, duration: 1.15, ease: 'power3.out' }, 0)
26 timeline.to(meshRef.current.scale, { x: 1, y: 1, z: 1, duration: 1.05, ease: 'back.out(1.7)' }, 0.05)
27 timeline.to(lightRef.current.position, { x: -1.6, y: 2.4, z: 3.1, duration: 1.1, ease: 'power2.out' }, 0)
28 timeline.to(lightRef.current, { intensity: 1.8, duration: 0.9, ease: 'power2.out' }, 0)
29 timeline.to(groupRef.current.rotation, { y: 0.52, duration: 1, ease: 'sine.inOut' }, 1.2)
30 timeline.to(lightRef.current.position, { x: -0.4, duration: 1, ease: 'sine.inOut' }, 1.2)
31
32 return () => timeline.kill()
33 }, [])
34
35 return (
36 <Canvas>
37 <PerspectiveCamera
38 makeDefault
39 position={[0, 0.2, 4.9]}
40 fov={32}
41 onUpdate={(camera) => camera.lookAt(0, 0.1, 0)}
42 />
43 <ambientLight intensity={0.45} />
44 <directionalLight ref={lightRef} position={[2, 2, 3]} intensity={1.2} />
45 <group ref={groupRef}>
46 <mesh ref={meshRef} geometry={geometry} material={material} />
47 </group>
48 </Canvas>
49 )
50}
Mental Model

Keep the scene model simple.

If you remember what each primitive is responsible for, React Three Fiber becomes much easier to read and debug.

Scene

The scene is the root container for everything renderable. Canvas creates it for you before you add any objects.

Group

A group is a transform wrapper in the scene graph. It owns values like position, rotation, and scale, so moving the group moves every child inside it.

Geometry

Geometry is only the raw shape data. It knows vertices and faces, not color, light, or polish.

Material

Material controls how the shape reacts to light. A standard material needs light before it really looks 3D.

Mesh

Mesh is the actual scene object. It is the place where shape and surface meet so the renderer has something to draw.

Light

Light reveals the form. It makes edges, faces, and rotation feel readable instead of flat.

Camera

Camera decides what the user sees. Position, angle, and field of view change the feeling more than most beginners expect.

Refs

A React ref gives you the real Three.js object instance. That is how GSAP can animate things like group.position, mesh.rotation, or light.intensity.

Animation

Animation is just change over time. Once the scene is built correctly, animating group.position or group.rotation can move the whole object cleanly.