
Threejs Core Scene Graph
- 21 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-core-scene-graph is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-core-scene-graph
- AI & Agent Building
- AI-coding skill
Threejs Core Scene Graph by the numbers
- 21 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,307 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-core-scene-graphAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 21 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
threejs-core-scene-graph
Quick Reference
Scene Graph Hierarchy
| Class | Extends | Purpose |
|---|---|---|
Object3D | EventDispatcher | Base class for ALL 3D objects. Provides transform, hierarchy, traversal |
Scene | Object3D | Root container. Adds background, environment, fog, overrideMaterial |
Group | Object3D | Semantic container with no extra functionality. Use for logical grouping |
Mesh | Object3D | Geometry + Material. The primary visible object in a scene |
Camera | Object3D | View projection. ALWAYS add to scene for matrix updates |
Light | Object3D | Illumination. ALWAYS add to scene for rendering |
Object3D Core Properties
| Property | Type | Default | Description |
|---|---|---|---|
position | Vector3 | (0,0,0) | Local position relative to parent |
rotation | Euler | (0,0,0,'XYZ') | Local rotation. Linked to quaternion -- modifying one ALWAYS updates the other |
quaternion | Quaternion | (0,0,0,1) | Local rotation as quaternion. Linked to rotation |
scale | Vector3 | (1,1,1) | Local scale. Non-uniform scale causes normal distortion |
visible | boolean | true | When false, object and ALL descendants are skipped during rendering |
layers | Layers | layer 0 | 32-bit bitmask for selective rendering and raycasting |
castShadow | boolean | false | Whether object casts shadows |
receiveShadow | boolean | false | Whether object receives shadows |
frustumCulled | boolean | true | Set false for skyboxes or objects that MUST always render |
renderOrder | number | 0 | Higher values render later. Use for transparency sorting |
name | string | "" | Human-readable label. Use getObjectByName() for lookup |
userData | object | {} | Custom application data dictionary |
Identity Properties
| Property | Type | Description |
|---|---|---|
uuid | string | Read-only RFC 4122 v4 identifier. Auto-generated |
id | number | Auto-incrementing integer. Unique per runtime session |
name | string | User-assigned. NEVER relied upon as unique identifier |
type | string | Read-only class name (e.g., "Mesh", "Group") |
Critical Warnings
NEVER modify children array directly -- ALWAYS use add(), remove(), clear(), or attach(). Direct modification breaks internal bookkeeping (parent references, event dispatch).
NEVER modify the children array during traverse() -- collect objects first, then modify after traversal completes. Adding during traversal causes unpredictable iteration.
NEVER use add() when reparenting an object that must keep its world position -- ALWAYS use attach() instead. add() preserves local transform; attach() preserves world transform.
NEVER read matrixWorld after changing position/rotation/scale in the same frame without calling updateWorldMatrix(true, false) first -- the world matrix is stale until the next render or explicit update.
NEVER store Three.js objects in userData without manual disposal -- userData is NOT automatically cleaned up by dispose().
NEVER forget to dispose textures, geometries, and materials when removing objects -- remove() and clear() only detach from the scene graph, they do NOT free GPU memory.
---
Hierarchy Methods
add() vs attach() -- The Critical Difference
import { Scene, Group, Mesh, BoxGeometry, MeshStandardMaterial } from 'three';
const scene = new Scene();
const groupA = new Group();
const groupB = new Group();
groupA.position.set(10, 0, 0);
groupB.position.set(0, 5, 0);
scene.add(groupA, groupB);
const mesh = new Mesh(new BoxGeometry(), new MeshStandardMaterial());
mesh.position.set(0, 0, 0);
groupA.add(mesh);
// mesh world position = (10, 0, 0) -- inherited from groupA
// WRONG: add() preserves LOCAL transform -- mesh jumps to (0, 5, 0) world
groupB.add(mesh); // auto-removes from groupA first
// CORRECT: attach() preserves WORLD transform -- mesh stays at (10, 0, 0) visually
groupA.add(mesh); // reset
groupB.attach(mesh); // mesh.position is recalculated to maintain world positionremove(), removeFromParent(), clear()
// Remove specific children
group.remove(meshA, meshB);
// Remove self from parent (safe when parent is null)
mesh.removeFromParent();
// Remove ALL children -- ALWAYS prefer over manual iteration
group.clear();Edge case: remove() NEVER throws if the object is not a child -- it silently does nothing.
---
Traversal
// Depth-first traversal of ALL descendants
scene.traverse((object) => {
if (object.isMesh) {
object.castShadow = true;
}
});
// Skip invisible objects and their subtrees
scene.traverseVisible((object) => {
// only visits objects where visible === true
});
// Walk UP to root (does NOT include the starting object)
mesh.traverseAncestors((ancestor) => {
console.log(ancestor.name);
});
// Find by name, id, or arbitrary property
const wall = scene.getObjectByName('north-wall');
const obj = scene.getObjectById(42);
const selectable = scene.getObjectByProperty('userData', { selectable: true });Performance: All search methods are O(n). For frequent lookups, ALWAYS cache the reference.
---
Matrix System
Automatic Mode (Default)
The renderer calls updateMatrixWorld() on the scene before every render, which recursively: 1. Calls updateMatrix() on each object (composes matrix from position/rotation/scale) 2. Computes matrixWorld = parent.matrixWorld * matrix
Manual Mode (Performance Optimization)
// For static objects -- skip per-frame matrix recomputation
object.matrixAutoUpdate = false;
object.matrix.compose(position, quaternion, scale);
object.matrixWorldNeedsUpdate = true;Reading World-Space Values Mid-Frame
import { Vector3, Quaternion } from 'three';
object.position.set(10, 0, 0);
object.updateWorldMatrix(true, false); // update parents first
const worldPos = new Vector3();
object.getWorldPosition(worldPos);
const worldQuat = new Quaternion();
object.getWorldQuaternion(worldQuat);
const worldScale = new Vector3();
object.getWorldScale(worldScale);
const worldDir = new Vector3();
object.getWorldDirection(worldDir);Coordinate Conversion
// MUTATION WARNING: both methods modify the input vector in-place
const localPoint = new Vector3(5, 0, 0);
object.localToWorld(localPoint); // localPoint is now in world coordinates
const worldPoint = new Vector3(15, 3, 0);
object.worldToLocal(worldPoint); // worldPoint is now in object's local coordinates---
Scene Class
import { Scene, Color, Fog, FogExp2, TextureLoader } from 'three';
const scene = new Scene();
scene.background = new Color(0x222222); // solid color
scene.environment = hdrTexture; // IBL for all PBR materials
scene.environmentIntensity = 1.5; // boost environment lighting
scene.environmentRotation.set(0, Math.PI, 0); // rotate environment
scene.fog = new Fog(0xcccccc, 10, 100); // linear fog
scene.overrideMaterial = depthMaterial; // debug: force all objects to one material| Property | Type | Default | Description |
|---|---|---|---|
background | `Color \ | Texture \ | CubeTexture \ |
environment | `Texture \ | null` | null |
fog | `Fog \ | FogExp2 \ | null` |
overrideMaterial | `Material \ | null` | null |
backgroundBlurriness | number | 0 | Blur for background (0-1) |
backgroundIntensity | number | 1 | Background brightness multiplier |
backgroundRotation | Euler | (0,0,0) | Background rotation |
environmentIntensity | number | 1 | Environment map brightness multiplier |
environmentRotation | Euler | (0,0,0) | Environment map rotation |
---
Fog
Fog (Linear Interpolation)
import { Fog } from 'three';
scene.fog = new Fog(0xffffff, 10, 200); // color, near, farFogExp2 (Exponential Density)
import { FogExp2 } from 'three';
scene.fog = new FogExp2(0xffffff, 0.01); // color, densityMaterial interaction: Every material has a fog property (default: true). ShaderMaterial and RawShaderMaterial default to fog: false -- you MUST set fog: true and include fog shader chunks manually for custom shaders to respond to fog.
---
Group
Group extends Object3D with zero additional functionality. It exists purely as a semantic container for organizing objects:
import { Group } from 'three';
const buildingGroup = new Group();
buildingGroup.name = 'building-01';
buildingGroup.add(walls, roof, foundation);
scene.add(buildingGroup);
// Transform all children together
buildingGroup.position.set(50, 0, 0);
buildingGroup.rotation.y = Math.PI / 4;---
Mesh
Mesh combines a BufferGeometry with a Material to create a visible surface:
import { Mesh, BoxGeometry, MeshStandardMaterial } from 'three';
const mesh = new Mesh(
new BoxGeometry(1, 1, 1),
new MeshStandardMaterial({ color: 0x00ff00 })
);
// Multi-material with geometry groups
const materials = [materialA, materialB];
geometry.addGroup(0, 36, 0); // start, count, materialIndex
geometry.addGroup(36, 36, 1);
const multiMesh = new Mesh(geometry, materials);
// Morph targets
mesh.morphTargetInfluences[0] = 0.5; // blend between base and morph target---
Layers System
A 32-bit bitmask system for selective rendering and raycasting:
import { Layers } from 'three';
// Objects are on layer 0 by default
mesh.layers.set(1); // ONLY layer 1 (removes from layer 0)
mesh.layers.enable(2); // add layer 2 (keep layer 1)
mesh.layers.disable(1); // remove layer 1
mesh.layers.toggle(3); // flip layer 3
// Camera renders only objects with overlapping layers
camera.layers.enable(1); // camera now sees layers 0 AND 1
// Test overlap
const visible = camera.layers.test(mesh.layers); // true if any layer overlapsUse cases:
- Layer 0: default visible objects
- Layer 1: helpers/gizmos (disable on production camera)
- Layer 2: bloom-only objects (selective post-processing)
- Layers 3-31: custom (collision groups, selection sets, LOD groups)
---
lookAt() Behavior Difference
// Camera: points NEGATIVE-Z toward target (looks AT the target)
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
// Non-camera objects: points POSITIVE-Z toward target
mesh.lookAt(targetPosition);ALWAYS call lookAt() after setting position -- it computes rotation from the current position.
---
Reference Links
- references/methods.md -- Complete Object3D, Scene, Group, Mesh, Layers API signatures
- references/examples.md -- Working code examples for common scene graph operations
- references/anti-patterns.md -- What NOT to do with scene graph management
Official Sources
- https://threejs.org/docs/#api/en/core/Object3D
- https://threejs.org/docs/#api/en/scenes/Scene
- https://threejs.org/docs/#api/en/scenes/Fog
- https://threejs.org/docs/#api/en/scenes/FogExp2
- https://threejs.org/docs/#api/en/objects/Group
- https://threejs.org/docs/#api/en/objects/Mesh
- https://threejs.org/docs/#api/en/core/Layers
Anti-Patterns (Three.js Scene Graph r160+)
1. Using add() Instead of attach() for Reparenting
// WRONG: add() preserves local transform -- object jumps to unexpected position
const mesh = new Mesh(geometry, material);
mesh.position.set(0, 0, 0);
groupA.add(mesh); // world position = groupA.position + (0,0,0)
groupB.add(mesh); // mesh.position is still (0,0,0) but now relative to groupB
// object visually JUMPS to groupB's origin
// CORRECT: attach() preserves world transform -- object stays in place
groupB.attach(mesh); // mesh.position is recalculated so world position does not changeWHY: add() keeps the local position/rotation/scale unchanged, meaning the world transform changes when the new parent has a different world transform. attach() recalculates local transform to maintain the existing world transform.
---
2. Modifying Children Array During Traversal
// WRONG: modifying children during traverse causes skipped or double-visited objects
scene.traverse((object) => {
if (object.userData.expired) {
object.removeFromParent(); // mutates children array mid-iteration!
}
});
// CORRECT: collect first, then modify
const toRemove = [];
scene.traverse((object) => {
if (object.userData.expired) {
toRemove.push(object);
}
});
toRemove.forEach((obj) => obj.removeFromParent());WHY: traverse() iterates the children array with a for-loop. Removing or adding elements during iteration shifts indices, causing objects to be skipped or visited twice.
---
3. Directly Modifying the children Array
// WRONG: bypasses parent reference updates and event dispatch
group.children.push(mesh); // mesh.parent is still null!
group.children.splice(0, 1); // removed object.parent still points to group!
group.children = []; // orphans all children with stale parent refs
// CORRECT: ALWAYS use the hierarchy methods
group.add(mesh); // sets mesh.parent, fires 'added' event
group.remove(mesh); // clears mesh.parent, fires 'removed' event
group.clear(); // properly removes all childrenWHY: add() and remove() manage parent references, fire events, and handle auto-removal from previous parents. Direct array manipulation breaks all of these guarantees.
---
4. Reading Stale matrixWorld
// WRONG: matrixWorld is stale immediately after changing position
mesh.position.set(10, 5, 0);
const worldPos = new Vector3();
mesh.getWorldPosition(worldPos); // may return old position if parent chain is stale
// ALSO WRONG: accessing matrixWorld.elements directly without update
mesh.position.set(10, 5, 0);
const x = mesh.matrixWorld.elements[12]; // stale value!
// CORRECT: force update before reading
mesh.position.set(10, 5, 0);
mesh.updateWorldMatrix(true, false); // update parents first, not children
const worldPos = new Vector3();
mesh.getWorldPosition(worldPos); // now correctWHY: matrixWorld is only recalculated during renderer.render() or explicit updateWorldMatrix()/updateMatrixWorld() calls. Between frames, it contains the value from the last render.
NOTE: getWorldPosition(), getWorldQuaternion(), getWorldScale(), and getWorldDirection() call updateWorldMatrix(true, false) internally, so they are safe. But accessing matrixWorld properties directly is NOT safe without a manual update.
---
5. Forgetting to Dispose When Removing Objects
// WRONG: remove() only detaches from scene graph -- GPU memory leaks
scene.remove(mesh);
// geometry and material are still in GPU memory!
// CORRECT: dispose geometry, material, and textures
scene.remove(mesh);
mesh.geometry.dispose();
if (Array.isArray(mesh.material)) {
mesh.material.forEach((mat) => {
Object.values(mat).forEach((value) => {
if (value && value.isTexture) value.dispose();
});
mat.dispose();
});
} else {
Object.values(mesh.material).forEach((value) => {
if (value && value.isTexture) value.dispose();
});
mesh.material.dispose();
}WHY: Three.js separates scene graph management from GPU resource management. remove() and clear() only affect the parent-child hierarchy. Geometries, materials, and textures MUST be disposed explicitly to free GPU memory.
---
6. Using set() Instead of enable() on Layers
// WRONG: set() disables ALL other layers
mesh.layers.set(2); // mesh is now ONLY on layer 2, removed from layer 0!
// camera (on layer 0) can no longer see this mesh
// CORRECT: enable() adds a layer without removing existing ones
mesh.layers.enable(2); // mesh is now on layers 0 AND 2WHY: layers.set(n) replaces the entire bitmask with only bit n. layers.enable(n) performs a bitwise OR, adding the layer without affecting others. Use set() only when you want exclusive layer membership.
---
7. Iterating Children with for-loop for Removal
// WRONG: forward iteration skips elements when removing
for (let i = 0; i < group.children.length; i++) {
group.remove(group.children[i]); // children[1] becomes children[0], gets skipped
}
// ALSO WRONG: for-of with removal
for (const child of group.children) {
group.remove(child); // mutates the array being iterated
}
// CORRECT: use clear() for removing all children
group.clear();
// CORRECT: if selectively removing, iterate backwards or collect first
for (let i = group.children.length - 1; i >= 0; i--) {
if (group.children[i].userData.removable) {
group.remove(group.children[i]);
}
}WHY: Forward iteration with removal causes index shifting. After removing index 0, the previous index 1 slides to index 0 and is never visited. Reverse iteration or clear() avoids this.
---
8. Calling lookAt() Before Setting Position
// WRONG: lookAt computes rotation from current position
mesh.lookAt(targetPosition); // rotation computed from default (0,0,0)
mesh.position.set(10, 5, 0); // position changes but rotation is stale
// CORRECT: ALWAYS set position first, then lookAt
mesh.position.set(10, 5, 0);
mesh.lookAt(targetPosition); // rotation computed from (10,5,0)WHY: lookAt() calculates the rotation needed to face the target from the object's current position. Setting position after lookAt() moves the object without updating the rotation.
---
9. Storing Three.js Objects in userData Without Cleanup
// WRONG: userData references prevent garbage collection
mesh.userData.originalMaterial = mesh.material;
mesh.userData.helperMesh = new Mesh(geometry, helperMaterial);
// when mesh is removed and disposed, userData references keep material and helperMesh alive
// CORRECT: clean up userData references during disposal
function disposeMesh(mesh) {
if (mesh.userData.helperMesh) {
mesh.userData.helperMesh.geometry.dispose();
mesh.userData.helperMesh.material.dispose();
mesh.userData.helperMesh = null;
}
mesh.userData.originalMaterial = null;
mesh.geometry.dispose();
mesh.material.dispose();
mesh.removeFromParent();
}WHY: userData is a plain object that is NOT touched by dispose(). Any Three.js objects stored there (materials, textures, geometries, meshes) remain in memory until their references are explicitly cleared and their dispose() methods are called.
---
10. Assuming userData Survives Deep Clone
// WRONG: expecting userData to be deeply cloned
mesh.userData.config = { layers: [1, 2, 3], nested: { value: true } };
const clone = mesh.clone();
clone.userData.config.layers.push(4);
console.log(mesh.userData.config.layers); // [1, 2, 3, 4] -- original is mutated!
// CORRECT: manually deep-clone userData if it contains nested objects
const clone = mesh.clone();
clone.userData = JSON.parse(JSON.stringify(mesh.userData));
// now modifying clone.userData does not affect the originalWHY: Object3D.clone() performs a shallow copy of userData via Object.assign(). Nested objects and arrays are shared by reference between the original and clone.
---
11. Setting matrixAutoUpdate = false Without Manual Updates
// WRONG: disabling auto-update and then using position/rotation/scale
mesh.matrixAutoUpdate = false;
mesh.position.set(5, 0, 0); // position changed but matrix is NOT updated
// mesh renders at old position!
// CORRECT option A: manually update matrix after changes
mesh.matrixAutoUpdate = false;
mesh.position.set(5, 0, 0);
mesh.updateMatrix(); // recompute matrix from position/rotation/scale
// CORRECT option B: set matrix directly
mesh.matrixAutoUpdate = false;
mesh.matrix.makeTranslation(5, 0, 0);
mesh.matrixWorldNeedsUpdate = true;WHY: When matrixAutoUpdate is false, the renderer skips updateMatrix() for that object. The matrix property is only updated when you call updateMatrix() explicitly or set it directly. This is a performance optimization for static objects, but using position/rotation/scale without calling updateMatrix() results in the object rendering at its last computed transform.
Working Code Examples (Three.js Scene Graph r160+)
Example 1: Basic Scene Setup
A minimal scene with a mesh, camera, light, and render loop.
import {
Scene, PerspectiveCamera, WebGLRenderer,
Mesh, BoxGeometry, MeshStandardMaterial,
DirectionalLight, Color
} from 'three';
// Create scene
const scene = new Scene();
scene.background = new Color(0x1a1a2e);
// Create camera
const camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 2, 5);
camera.lookAt(0, 0, 0);
// Create light
const light = new DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 5);
light.castShadow = true;
scene.add(light);
// Create mesh
const mesh = new Mesh(
new BoxGeometry(1, 1, 1),
new MeshStandardMaterial({ color: 0x00ff88 })
);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
// Create renderer
const renderer = new WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Render loop
renderer.setAnimationLoop((time) => {
mesh.rotation.y = time * 0.001;
renderer.render(scene, camera);
});
// Handle resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});---
Example 2: Hierarchical Object Groups
Demonstrates parent-child relationships and group transforms.
import {
Scene, Group, Mesh, BoxGeometry, CylinderGeometry,
MeshStandardMaterial, Vector3
} from 'three';
const scene = new Scene();
// Create a building as a group of parts
const building = new Group();
building.name = 'building-01';
// Foundation
const foundation = new Mesh(
new BoxGeometry(10, 0.5, 10),
new MeshStandardMaterial({ color: 0x888888 })
);
foundation.position.y = 0.25;
foundation.name = 'foundation';
// Walls
const walls = new Mesh(
new BoxGeometry(9, 6, 9),
new MeshStandardMaterial({ color: 0xddccbb })
);
walls.position.y = 3.5;
walls.name = 'walls';
// Roof
const roof = new Mesh(
new CylinderGeometry(0, 7, 3, 4),
new MeshStandardMaterial({ color: 0xcc4444 })
);
roof.position.y = 8;
roof.rotation.y = Math.PI / 4;
roof.name = 'roof';
// Assemble -- all parts move together when building moves
building.add(foundation, walls, roof);
scene.add(building);
// Move entire building -- all children follow
building.position.set(20, 0, -15);
building.rotation.y = Math.PI / 6;
// Find parts by name
const roofRef = building.getObjectByName('roof');
if (roofRef) {
roofRef.material.color.set(0x2244cc); // change roof color
}---
Example 3: Reparenting with attach() vs add()
Demonstrates the critical difference when moving objects between groups.
import {
Scene, Group, Mesh, SphereGeometry, MeshBasicMaterial, Vector3
} from 'three';
const scene = new Scene();
const arm = new Group();
arm.position.set(5, 0, 0);
scene.add(arm);
const hand = new Group();
hand.position.set(3, 0, 0); // 3 units from arm pivot
arm.add(hand);
const ball = new Mesh(
new SphereGeometry(0.5),
new MeshBasicMaterial({ color: 0xff0000 })
);
ball.position.set(0, 0, 0);
hand.add(ball);
// Ball world position = arm(5,0,0) + hand(3,0,0) + ball(0,0,0) = (8,0,0)
// WRONG approach: add() to scene -- ball jumps to origin
// scene.add(ball);
// Ball world position would become (0,0,0) because local transform (0,0,0) is now relative to scene
// CORRECT approach: attach() to scene -- ball stays at (8,0,0)
scene.attach(ball);
// ball.position is now (8,0,0) to preserve its world position
// Verify
const worldPos = new Vector3();
ball.getWorldPosition(worldPos);
console.log(worldPos); // Vector3 { x: 8, y: 0, z: 0 }---
Example 4: Layers for Selective Rendering
Using layers to separate visible objects from helpers and bloom effects.
import {
Scene, PerspectiveCamera, WebGLRenderer,
Mesh, BoxGeometry, MeshStandardMaterial, MeshBasicMaterial,
AxesHelper, GridHelper
} from 'three';
const LAYERS = {
DEFAULT: 0,
HELPERS: 1,
BLOOM: 2,
};
const scene = new Scene();
// Production camera -- sees layer 0 only (default)
const prodCamera = new PerspectiveCamera(75, 16 / 9, 0.1, 1000);
prodCamera.position.set(0, 5, 10);
prodCamera.lookAt(0, 0, 0);
// Debug camera -- sees layers 0 AND 1
const debugCamera = new PerspectiveCamera(75, 16 / 9, 0.1, 1000);
debugCamera.position.set(0, 5, 10);
debugCamera.lookAt(0, 0, 0);
debugCamera.layers.enable(LAYERS.HELPERS);
// Regular mesh on layer 0 (default)
const cube = new Mesh(
new BoxGeometry(1, 1, 1),
new MeshStandardMaterial({ color: 0x00ff00 })
);
scene.add(cube);
// Helper on layer 1 -- invisible to prodCamera
const axes = new AxesHelper(5);
axes.layers.set(LAYERS.HELPERS); // ONLY on layer 1
scene.add(axes);
const grid = new GridHelper(20, 20);
grid.layers.set(LAYERS.HELPERS);
scene.add(grid);
// Bloom mesh on layer 2
const glowCube = new Mesh(
new BoxGeometry(0.5, 0.5, 0.5),
new MeshBasicMaterial({ color: 0xff8800 })
);
glowCube.layers.enable(LAYERS.BLOOM); // on layers 0 AND 2
glowCube.position.set(2, 1, 0);
scene.add(glowCube);
const renderer = new WebGLRenderer({ antialias: true });
renderer.setSize(800, 450);
document.body.appendChild(renderer.domElement);
// Toggle between cameras
let useDebug = false;
renderer.setAnimationLoop(() => {
const camera = useDebug ? debugCamera : prodCamera;
renderer.render(scene, camera);
});---
Example 5: Scene Fog and Environment
Setting up fog and environment-based lighting.
import {
Scene, PerspectiveCamera, WebGLRenderer,
Mesh, PlaneGeometry, BoxGeometry,
MeshStandardMaterial, MeshBasicMaterial,
Fog, FogExp2, Color, ACESFilmicToneMapping, SRGBColorSpace
} from 'three';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
const scene = new Scene();
scene.background = new Color(0xaabbcc);
// Linear fog: clear at 10 units, fully opaque at 150 units
scene.fog = new Fog(0xaabbcc, 10, 150);
// Or use exponential fog (comment out the above)
// scene.fog = new FogExp2(0xaabbcc, 0.015);
// Ground plane -- receives fog
const ground = new Mesh(
new PlaneGeometry(200, 200),
new MeshStandardMaterial({ color: 0x556655 })
);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
// Row of boxes fading into fog
for (let i = 0; i < 20; i++) {
const box = new Mesh(
new BoxGeometry(2, 2, 2),
new MeshStandardMaterial({ color: 0xcc8844 })
);
box.position.set(0, 1, -i * 8);
scene.add(box);
}
// Sky material ignores fog
const sky = new Mesh(
new PlaneGeometry(500, 500),
new MeshBasicMaterial({ color: 0xaabbcc, fog: false }) // fog: false
);
sky.position.set(0, 100, -200);
scene.add(sky);
// Load HDR environment for PBR lighting
const rgbeLoader = new RGBELoader();
rgbeLoader.load('environment.hdr', (texture) => {
scene.environment = texture;
scene.environmentIntensity = 0.8;
});
// Renderer with tone mapping
const renderer = new WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.toneMapping = ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputColorSpace = SRGBColorSpace;
document.body.appendChild(renderer.domElement);
const camera = new PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 500);
camera.position.set(0, 5, 20);
camera.lookAt(0, 1, -50);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});API Signatures Reference (Three.js Scene Graph r160+)
Object3D
The base class for all 3D objects. Every Mesh, Group, Light, Camera, and Scene inherits from Object3D.
Constructor
new Object3D()No parameters. Creates an object at origin with identity transform.
Transform Properties
position: Vector3 // default: (0, 0, 0) -- local position
rotation: Euler // default: (0, 0, 0, 'XYZ') -- linked to quaternion
quaternion: Quaternion // default: (0, 0, 0, 1) -- linked to rotation
scale: Vector3 // default: (1, 1, 1) -- local scale
up: Vector3 // default: (0, 1, 0) -- up direction for lookAt()Hierarchy Properties
parent: Object3D | null // set automatically by add()/remove()
children: Object3D[] // NEVER modify directlyRendering Properties
visible: boolean // default: true
castShadow: boolean // default: false
receiveShadow: boolean // default: false
frustumCulled: boolean // default: true
renderOrder: number // default: 0
layers: Layers // default: layer 0 enabledIdentity Properties
uuid: string // read-only, auto-generated RFC 4122 v4
id: number // read-only, auto-incrementing integer
name: string // default: ""
type: string // read-only, e.g., "Object3D", "Mesh"
userData: object // default: {}Matrix Properties
matrix: Matrix4 // local transform matrix
matrixWorld: Matrix4 // world transform matrix
matrixAutoUpdate: boolean // default: true
matrixWorldAutoUpdate: boolean // default: true
matrixWorldNeedsUpdate: boolean // default: false
modelViewMatrix: Matrix4 // computed per frame by renderer
normalMatrix: Matrix3 // computed per frame by renderer---
Hierarchy Methods
add(object: Object3D, ...args: Object3D[]): thisAdds one or more children. Auto-removes from previous parent. Fires added event on child. Returns this for chaining.
remove(object: Object3D, ...args: Object3D[]): thisRemoves one or more children. Fires removed event. NEVER throws if object is not a child. Returns this.
removeFromParent(): thisRemoves this object from its parent. Safe when parent is null. Returns this.
clear(): thisRemoves ALL children. ALWAYS prefer over manual iteration to avoid index-shifting bugs. Returns this.
attach(object: Object3D): thisAdds object as child while preserving its world transform. Internally recomputes local transform from world transform. Returns this.
---
Traversal Methods
traverse(callback: (object: Object3D) => void): voidDepth-first traversal of this object and ALL descendants. Performance: O(n).
traverseVisible(callback: (object: Object3D) => void): voidSame as traverse() but skips objects where visible === false and all their descendants.
traverseAncestors(callback: (object: Object3D) => void): voidWalks UP the tree from parent to root. Does NOT include the starting object.
getObjectByName(name: string): Object3D | undefinedRecursive search. Returns first match or undefined.
getObjectById(id: number): Object3D | undefinedRecursive search by auto-generated id. Returns first match or undefined.
getObjectByProperty(name: string, value: any): Object3D | undefinedRecursive search by arbitrary property. Returns first match or undefined.
---
Transform Methods
lookAt(x: number, y: number, z: number): void
lookAt(vector: Vector3): voidRotates to face a world-space point. Cameras point negative-Z; other objects point positive-Z toward target.
rotateOnAxis(axis: Vector3, angle: number): thisRotates around a local-space axis by angle (radians).
rotateOnWorldAxis(axis: Vector3, angle: number): thisRotates around a world-space axis by angle (radians).
rotateX(angle: number): this
rotateY(angle: number): this
rotateZ(angle: number): thisShorthand for rotateOnAxis() on the respective axis.
translateOnAxis(axis: Vector3, distance: number): thisTranslates along a local-space axis.
translateX(distance: number): this
translateY(distance: number): this
translateZ(distance: number): thisShorthand for translateOnAxis() on the respective axis.
---
Coordinate Conversion Methods
localToWorld(vector: Vector3): Vector3Converts vector from local space to world space. MUTATES the input vector. Returns the same vector.
worldToLocal(vector: Vector3): Vector3Converts vector from world space to local space. MUTATES the input vector. Returns the same vector.
---
Matrix Update Methods
updateMatrix(): voidRecomputes matrix from position, rotation, and scale. Called automatically when matrixAutoUpdate is true.
updateMatrixWorld(force?: boolean): voidRecursively updates matrixWorld for this object and all descendants. When force is true, recalculates regardless of flags.
updateWorldMatrix(updateParents: boolean, updateChildren: boolean): voidGranular matrix update. updateParents: walks up to root first. updateChildren: recursively updates descendants after.
---
World-Space Query Methods
getWorldPosition(target: Vector3): Vector3Writes world position to target. Calls updateWorldMatrix(true, false) internally.
getWorldQuaternion(target: Quaternion): QuaternionWrites world rotation to target. Calls updateWorldMatrix(true, false) internally.
getWorldScale(target: Vector3): Vector3Writes world scale to target. Calls updateWorldMatrix(true, false) internally.
getWorldDirection(target: Vector3): Vector3Writes world-space forward direction to target. For cameras: negative-Z. For others: positive-Z.
---
Utility Methods
clone(recursive?: boolean): Object3DReturns a new Object3D with copied properties. When recursive is true (default), clones all descendants. userData is shallow-copied.
copy(source: Object3D, recursive?: boolean): thisCopies properties from source. Returns this.
toJSON(meta?: object): objectSerializes to JSON format.
applyMatrix4(matrix: Matrix4): voidApplies a matrix transform to the object. Updates position, quaternion, and scale from the decomposed matrix.
applyQuaternion(quaternion: Quaternion): thisApplies a quaternion rotation. Returns this.
---
Scene
Extends Object3D. Root container for all renderable objects.
Constructor
new Scene()Properties
background: Color | Texture | CubeTexture | null // default: null
environment: Texture | null // default: null -- IBL for all PBR materials
fog: Fog | FogExp2 | null // default: null
overrideMaterial: Material | null // default: null
backgroundBlurriness: number // default: 0 (range 0-1)
backgroundIntensity: number // default: 1
backgroundRotation: Euler // default: (0, 0, 0)
environmentIntensity: number // default: 1
environmentRotation: Euler // default: (0, 0, 0)Type Flags
isScene: true // read-only type check flag---
Fog
Constructor
new Fog(color: Color | string | number, near?: number, far?: number)near: distance where fog starts (default:1)far: distance where fog is fully opaque (default:1000)- Linear interpolation between near and far
Properties
isFog: true // read-only type check flag
color: Color // fog color
near: number // start distance
far: number // end distance---
FogExp2
Constructor
new FogExp2(color: Color | string | number, density?: number)density: exponential density coefficient (default:0.00025)
Properties
isFogExp2: true // read-only type check flag
color: Color // fog color
density: number // exponential density---
Group
Extends Object3D. Semantic container with no additional properties or methods.
Constructor
new Group()Type Flags
isGroup: true // read-only type check flag---
Mesh
Extends Object3D. Combines geometry and material into a renderable surface.
Constructor
new Mesh(geometry?: BufferGeometry, material?: Material | Material[])geometry: defaults to emptyBufferGeometrymaterial: defaults toMeshBasicMaterialwith random color. Pass an array for multi-material rendering with geometry groups.
Properties
isMesh: true // read-only type check flag
geometry: BufferGeometry // the geometry
material: Material | Material[] // single or multi-material
morphTargetInfluences: number[] | undefined // blend weights for morph targets (0-1)
morphTargetDictionary: object | undefined // name-to-index mapping for morph targetsMethods
getVertexPosition(index: number, target: Vector3): Vector3Returns the local-space position of the vertex at the given index. Accounts for morph targets and skinning.
updateMorphTargets(): voidRebuilds morphTargetInfluences and morphTargetDictionary from the geometry.
raycast(raycaster: Raycaster, intersects: Intersection[]): voidTests for ray intersection. Called internally by Raycaster.intersectObject().
---
Layers
32-bit bitmask system for selective rendering and raycasting.
Constructor
new Layers()Creates a Layers object with layer 0 enabled.
Methods
set(channel: number): voidEnables ONLY this channel (0-31). Disables all others.
enable(channel: number): voidEnables a channel without affecting others.
enableAll(): voidEnables all 32 channels.
toggle(channel: number): voidFlips a channel on/off.
disable(channel: number): voidDisables a channel without affecting others.
disableAll(): voidDisables all 32 channels.
isEnabled(channel: number): booleanReturns true if the specified channel is enabled.
test(layers: Layers): booleanReturns true if ANY channel overlaps between this and the given Layers (bitwise AND).
Properties
mask: number // the raw 32-bit bitmask. Default: 1 (layer 0)