Navigate
Search topics across all sections
Scene Graph
Every 3D scene is a tree of objects. Parents and children. When a parent moves, all its children follow. When a parent rotates, the children orbit along with it. This tree is called the scene graph, and in R3F, your JSX nesting IS the scene graph. Nest a <mesh> inside a <group>, and you have just built a parent-child relationship in 3D space.
You position the Moon at [5, 0, 0], thinking that is its world position. But it is a child of Earth, which is at [4, 0, 0]. The Moon ends up at [9, 0, 0] -- way out past Pluto. Your solar system looks ridiculous.
Moon world position: [9, 0, 0] Expected: [5, 0, 0] Actual: Earth(4) + Moon(5) = 9 units from center
Real-world
A scene graph works exactly like a family tree. If grandpa moves to a new city, the whole family goes with him. His children inherit his position. Their children inherit theirs.
Now picture our solar system: the Sun is grandpa. Earth orbits the Sun (Earth is a child of the Sun). The Moon orbits Earth (Moon is a child of Earth). Rotate the Sun, and Earth moves. Rotate Earth, and the Moon moves. But the Moon does not need to know where the Sun is -- it only cares about its position relative to Earth.
JSX Nesting
<group> > <mesh> > <mesh>
Scene Graph
Parent-child tree in 3D
Transform Inheritance
Children follow parents
Local Coordinates
Positions are relative
Building a Solar System
The scene graph is easiest to understand with an example. Let us build a tiny solar system where each orbit is just parent-child nesting.
The Sun sits at the origin [0, 0, 0]. It is the root of our little solar system.
<group>
<mesh> {/* Sun */}
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial emissive="orange" />
</mesh>
</group>Nothing fancy yet. Just a glowing sphere at the center.
Earth is 4 units from the Sun. We put it inside a rotating group so that when we spin the group, Earth orbits.
<group rotation={[0, angle, 0]}>
<mesh position={[4, 0, 0]}> {/* Earth */}
<sphereGeometry args={[0.3, 32, 32]} />
<meshStandardMaterial color="dodgerblue" />
</mesh>
</group>Earth is at [4, 0, 0] relative to its parent group. Rotate the group, and Earth sweeps in a circle. The key insight: Earth does not know about the Sun. It just knows it is 4 units from its parent.
The Moon is a child of Earth. It orbits Earth, and since Earth orbits the Sun, the Moon comes along for both rides.
<mesh position={[4, 0, 0]}> {/* Earth */}
<sphereGeometry args={[0.3, 32, 32]} />
<mesh position={[0.6, 0, 0]}> {/* Moon */}
<sphereGeometry args={[0.08, 16, 16]} />
</mesh>
</mesh>The Moon is at [0.6, 0, 0] -- that is 0.6 units from Earth, not from the Sun. Its world position is Earth's position plus 0.6. That is transform inheritance in action.
What you just learned
The scene graph is a tree where children inherit parent transforms
JSX nesting directly creates the scene graph hierarchy
Child positions are relative to their parent, not the world
Moving a parent moves all of its children automatically
You can use React patterns (map, conditional rendering) to build dynamic scene graphs
Question
If a child only knows its local position relative to its parent, how do you figure out where something actually is in the world?
Three.js provides object.getWorldPosition(). It walks up the tree, multiplying all the parent transforms together, to give you the final world-space position. You rarely need this, but it is there when you do -- like for collision detection or snapping objects together.
Think about it...
A group is at position [3, 0, 0]. Inside it, a mesh is at position [0, 2, 0]. What is the mesh's world position?
Hint: Think about the family tree analogy: if grandpa is 3 blocks east, and you are 2 blocks north of grandpa...
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
Move the Earth group to [5,0,0] -- does the Moon follow?
Try This!
Beginner
Scale the Sun to 3 -- do the orbits change?
Try This!
Intermediate
Nest a third level -- give the Moon its own moon!
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
// Moon at [5,0,0] is relative to Earth!
// World position = [4 + 5, 0, 0] = [9, 0, 0]
<mesh position={[4, 0, 0]}> {/* Earth */}
<mesh position={[5, 0, 0]}> {/* Moon */}
<sphereGeometry args={[0.08, 16, 16]} />
</mesh>
</mesh>const ref = useRef<THREE.Group>(null)
// Second one overwrites the first!
<group ref={ref} position={[0, 0, 0]} />
<group ref={ref} position={[2, 0, 0]} />// Same scene object in two spots -- second steals it
<primitive object={gltf.scene} position={[0,0,0]} />
<primitive object={gltf.scene} position={[3,0,0]} />Best Practices
Use groups for logical units
Wrap related meshes in a <group> so you can move, rotate, or hide them as a single unit. Groups cost nothing to render.
Name your objects
Add a name prop for easier debugging. It shows up in the Three.js inspector and makes console logs readable.
Clone loaded models
A Three.js object can only have one parent. If you need the same model in multiple places, call .clone() for each instance.
Use keys for dynamic lists
When rendering meshes with .map(), provide stable unique keys. Without them, React cannot properly track which 3D objects to add, update, or remove.