This guide covers the architecture, internals, build systems, performance tuning, and contribution workflow for three.js v0.184.0 (revision r184). It is written for developers maintaining, extending, or deploying applications built on three.js. For usage tutorials, installation walkthroughs, and feature overviews, see the User Guide.
1. Architecture Overview
three.js is a JavaScript 3D graphics library that creates and renders 3D scenes using WebGL and WebGPU. The library follows a scene-graph architecture where all renderable objects inherit from Object3D and are organized in a tree rooted at a Scene instance. A Camera defines the viewpoint, and a Renderer traverses the scene graph to produce frames.
1.1 High-Level Architecture
Application Code
│
▼
┌──────────────────────────────────────────────┐
│ Scene Graph (Object3D tree) │
│ ├── Mesh (BufferGeometry + Material) │
│ ├── Light (DirectionalLight, PointLight...) │
│ ├── Camera (PerspectiveCamera, Ortho...) │
│ ├── Group, LOD, InstancedMesh, BatchedMesh │
│ └── Audio, Sprite, Line, Points, Bone... │
└──────────────────────────────┬───────────────┘
│
┌──────────▼──────────┐
│ Renderer │
│ ┌───────────────┐ │
│ │ WebGLRenderer │ │ (Legacy path)
│ └───────────────┘ │
│ ┌───────────────┐ │
│ │ WebGPURenderer│ │ (Modern path)
│ │ ├─ WebGPU │ │
│ │ └─ WebGL │ │ (fallback backend)
│ └───────────────┘ │
└─────────────────────┘
│
┌──────────▼──────────┐
│ GPU / Canvas │
└─────────────────────┘
1.2 Dual Renderer Strategy
three.js maintains two rendering paths:
| Path | Entry | Renderer | Shader Language | Status |
|---|---|---|---|---|
| Legacy | three (default import) |
WebGLRenderer |
GLSL | Stable, widely deployed |
| Modern | three/webgpu |
WebGPURenderer |
WGSL (via TSL) | Active development |
The modern path uses a backend abstraction layer (src/renderers/common/) that allows WebGPURenderer to run on either the native WebGPU API or fall back to WebGL via WebGLBackend. This is distinct from the legacy WebGLRenderer, which directly manages WebGL state.
1.3 Core Design Patterns
- Scene graph: Tree of
Object3Dnodes with parent/child relationships, local and world transforms - Component composition: Objects combine geometry (
BufferGeometry) with appearance (Material) - Event system:
EventDispatchermixin providesaddEventListener/removeEventListener/dispatchEventon all major classes - Clone/copy pattern: Every class implements
clone()(returns new instance) andcopy()(copies into existing instance) - Serialization:
toJSON()on Object3D, Material, BufferGeometry, Texture produces a portable JSON format;ObjectLoaderreconstructs - Dispose pattern: GPU resources require explicit
dispose()calls (geometry, material, texture, render target, renderer) - DataMap pattern: The common renderer infrastructure uses
DataMapinstances to associate internal GPU state with user-facing objects without polluting their properties
Note
three.js has zero runtime dependencies. All 5,875 source files are self-contained. Every package listed in
package.jsonis a devDependency used only for building, testing, or documentation generation.
1.4 Module Organization
The library is organized into a strict module hierarchy:
src/
├── Three.Core.js ← Barrel export for the WebGL path (default "three" import)
├── Three.TSL.js ← Re-exports for the TSL node shading language
├── Three.WebGPU.js ← Barrel export for WebGPU path ("three/webgpu")
├── constants.js ← All enumerations and constant values
└── utils.js ← Console output helpers (warn/error deduplication)
The exports field in package.json defines the public API surface:
| Import Path | Resolves To | Purpose |
|---|---|---|
three |
build/three.module.js |
Core library (WebGL renderer) |
three/webgpu |
build/three.webgpu.js |
WebGPU renderer + TSL |
three/tsl |
build/three.tsl.js |
TSL shader language only |
three/addons |
examples/jsm/Addons.js |
All addon modules |
three/addons/* |
examples/jsm/* |
Individual addons |
three/src/* |
src/* |
Raw source modules |
2. System Requirements
2.1 Runtime Requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| Browser (WebGL) | Chrome 80+, Firefox 78+, Safari 14+, Edge 80+ | Latest stable |
| Browser (WebGPU) | Chrome 113+, Firefox 141+ (behind flag), Safari 18+ | Chrome latest |
| WebGL version | WebGL 2.0 | WebGL 2.0 |
| JavaScript | ES2015 (ES6) modules | ES2022 |
| Node.js (SSR/build) | 14+ | 18+ LTS |
Warning
WebGL 1 has not been supported since r163. Attempting to create a
WebGLRendererin a WebGL 1-only context will throw:"THREE.WebGLRenderer: WebGL 1 is not supported since r163."There is no fallback.
2.2 Development Requirements
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 18+ | Build, test, dev server |
| npm | 8+ | Package management |
| Rollup | 4.6+ | Module bundling |
| ESLint | 9.0+ | Code linting |
| QUnit | 2.19+ | Unit testing |
| Puppeteer | 24+ | E2E screenshot testing |
| JSDoc | 4.0+ | Documentation generation |
2.3 Hardware Considerations
| Scenario | GPU | Notes |
|---|---|---|
| Basic scenes | Integrated GPU | Adequate for simple geometry, few lights |
| Complex scenes | Dedicated GPU | Required for heavy instancing, post-processing, shadows |
| WebGPU compute | Modern discrete GPU | Compute shaders require WebGPU-capable hardware |
| Mobile | Varies | Reduce draw calls, texture sizes, shadow map resolution |
3. Configuration Reference
three.js has no configuration files. All configuration is programmatic, passed through constructor options and property assignment.
Important
There are zero extractable config keys. Configuration is entirely programmatic through renderer constructor options, material properties, and scene settings. The subsections below document every configurable surface.
3.1 WebGLRenderer Constructor Options
const renderer = new THREE.WebGLRenderer({
canvas, // HTMLCanvasElement — existing canvas to render into
context, // WebGL2RenderingContext — existing GL context
precision, // "highp" | "mediump" | "lowp" — shader precision
alpha, // boolean — transparent canvas (default false)
premultipliedAlpha, // boolean — premultiplied alpha (default true)
antialias, // boolean — MSAA (default false)
stencil, // boolean — stencil buffer (default true)
preserveDrawingBuffer, // boolean — keep frame buffer for screenshots (default false)
powerPreference, // "default" | "high-performance" | "low-power"
failIfMajorPerformanceCaveat, // boolean — fail if software rendering (default false)
logarithmicDepthBuffer // boolean — logarithmic depth (default false)
});
3.2 WebGLRenderer Runtime Properties
| Property | Type | Default | Description |
|---|---|---|---|
shadowMap.enabled |
boolean | false |
Enable shadow mapping |
shadowMap.type |
constant | PCFShadowMap |
BasicShadowMap, PCFShadowMap, PCFSoftShadowMap, VSMShadowMap |
toneMapping |
constant | NoToneMapping |
LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping, AgXToneMapping, NeutralToneMapping |
toneMappingExposure |
number | 1.0 |
Exposure level for tone mapping |
outputColorSpace |
string | SRGBColorSpace |
Output color space |
autoClear |
boolean | true |
Clear before each render |
autoClearColor |
boolean | true |
Clear color buffer |
autoClearDepth |
boolean | true |
Clear depth buffer |
autoClearStencil |
boolean | true |
Clear stencil buffer |
sortObjects |
boolean | true |
Sort transparent objects |
localClippingEnabled |
boolean | false |
Enable per-material clipping planes |
3.3 WebGPURenderer Initialization
import { WebGPURenderer } from 'three/webgpu';
const renderer = new WebGPURenderer();
await renderer.init(); // REQUIRED — async initialization
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
Important
WebGPURendererrequires asynchronous initialization viaawait renderer.init(). Callingrender()beforeinit()completes will throw an error. The WebGPU path also falls back toWebGLBackendautomatically when WebGPU is unavailable.
3.4 Scene Configuration
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000); // Color, Texture, or null
scene.environment = envTexture; // Texture for IBL (image-based lighting)
scene.fog = new THREE.Fog(0xcccccc, 10, 100); // Linear fog: color, near, far
scene.fog = new THREE.FogExp2(0xcccccc, 0.002); // Exponential fog: color, density
scene.backgroundBlurriness = 0.0; // 0-1, blur background texture
scene.backgroundIntensity = 1.0; // Background brightness multiplier
scene.backgroundRotation = new THREE.Euler(); // Rotate background
scene.overrideMaterial = material; // Force all objects to use this material
3.5 Constants and Enumerations
All constants are defined in src/constants.js. Key groups:
| Group | Constants |
|---|---|
| Side | FrontSide, BackSide, DoubleSide |
| Blending | NoBlending, NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending |
| Depth | NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth |
| Texture Wrapping | RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping |
| Texture Filtering | NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter |
| Texture Format | AlphaFormat, RGBAFormat, RGBFormat, RedFormat, RGFormat, RedIntegerFormat, RGIntegerFormat, RGBAIntegerFormat, DepthFormat, DepthStencilFormat |
| Texture Type | UnsignedByteType, FloatType, HalfFloatType, UnsignedIntType, IntType, ByteType, ShortType, UnsignedShortType |
| Encoding | SRGBColorSpace, LinearSRGBColorSpace, DisplayP3ColorSpace, LinearDisplayP3ColorSpace |
| Shadow Map | BasicShadowMap, PCFShadowMap, PCFSoftShadowMap, VSMShadowMap |
| Tone Mapping | NoToneMapping, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping, AgXToneMapping, NeutralToneMapping |
| Coordinate System | WebGLCoordinateSystem, WebGPUCoordinateSystem |
3.6 Material Configuration Matrix
Common properties shared across all material types:
| Property | Type | Default | Description |
|---|---|---|---|
color |
Color | 0xffffff |
Base color |
map |
Texture | null |
Color map texture |
opacity |
number | 1.0 |
Overall opacity (requires transparent: true for <1) |
transparent |
boolean | false |
Enable alpha blending |
side |
constant | FrontSide |
Which face sides to render |
depthTest |
boolean | true |
Enable depth testing |
depthWrite |
boolean | true |
Write to depth buffer |
blending |
constant | NormalBlending |
Blending mode |
wireframe |
boolean | false |
Render as wireframe |
flatShading |
boolean | false |
Flat (faceted) shading |
fog |
boolean | true |
Affected by scene fog |
visible |
boolean | true |
Whether to render |
MeshPhysicalMaterial advanced PBR properties (extends MeshStandardMaterial):
| Property | Type | Default | Description |
|---|---|---|---|
clearcoat |
number | 0.0 |
Clearcoat layer intensity |
clearcoatRoughness |
number | 0.0 |
Clearcoat roughness |
transmission |
number | 0.0 |
Optical transmission (glass) |
ior |
number | 1.5 |
Index of refraction |
thickness |
number | 0.0 |
Volume thickness for transmission |
attenuationColor |
Color | 0xffffff |
Volume absorption color |
attenuationDistance |
number | Infinity |
Volume absorption distance |
sheen |
number | 0.0 |
Sheen layer intensity |
sheenColor |
Color | 0x000000 |
Sheen tint |
sheenRoughness |
number | 1.0 |
Sheen roughness |
iridescence |
number | 0.0 |
Iridescence intensity |
anisotropy |
number | 0.0 |
Anisotropic reflection strength |
dispersion |
number | 0.0 |
Chromatic dispersion |
specularIntensity |
number | 1.0 |
Specular reflection strength |
specularColor |
Color | 0xffffff |
Specular tint |
4. Deployment & Scaling
4.1 Bundling for Production
three.js is published as ES modules. The recommended build approach:
// Import only what you need — tree-shaking removes the rest
import { Scene, PerspectiveCamera, WebGLRenderer, BoxGeometry,
MeshStandardMaterial, Mesh, DirectionalLight } from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
Rollup (used by three.js itself):
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import terser from '@rollup/plugin-terser';
export default {
input: 'src/main.js',
output: { file: 'dist/bundle.js', format: 'es' },
plugins: [resolve(), terser()]
};
Webpack, Vite, esbuild, and Parcel all support three.js out of the box via the module and exports fields in package.json.
4.2 Tree-Shaking
three.js declares "sideEffects": ["./src/nodes/**/*"] in package.json. This tells bundlers that all modules except the node system are side-effect-free and can be safely eliminated when unused.
The tree-shaking test (npm run test-treeshake) verifies that importing individual classes does not pull in the entire library:
npm run test-treeshake # rollup -c test/rollup.treeshake.config.js
Best practices for minimal bundles:
- Import from
three(notthree/src/*) for optimized builds - Import addons individually:
three/addons/controls/OrbitControls.jsrather thanthree/addons - Avoid importing
three/webgpuunless WebGPU features are needed (pulls in the entire node system) - Use
MeshBasicMaterialorMeshLambertMaterialwhen PBR is unnecessary
4.3 CDN Deployment
For quick prototyping or non-bundled deployments:
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.184.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.184.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
</script>
Warning
Always pin a specific version when using CDNs (
@0.184.0). three.js frequently has breaking changes between releases. Thelatesttag can silently break production applications.
4.4 Multiple Instances Detection
three.js detects when multiple copies are loaded and emits a console warning. This commonly occurs when:
- Dependencies bundle their own copy of three.js
- Import maps resolve to a different URL than
node_modules - Both CJS (
three.cjs) and ESM (three.module.js) are loaded
The detection uses the global __THREE_DEVTOOLS__ symbol.
4.5 Performance Scaling Strategies
| Technique | When to Use | Implementation |
|---|---|---|
InstancedMesh |
100+ identical objects | Single draw call for all instances |
BatchedMesh |
Multiple geometries, fewer draw calls | Multi-draw batching API |
LOD |
Large scenes with distant objects | Automatic detail level switching |
WebGPURenderer |
Compute-heavy, modern browsers | Async init, compute shaders |
| Texture atlasing | Many small textures | Combine into single texture, adjust UVs |
| Geometry merging | Static scenes | BufferGeometryUtils.mergeGeometries() |
| Frustum culling | Default (automatic) | object.frustumCulled = true (default) |
| Layers | Selective rendering | 32-layer bitmask on objects and cameras |
5. Operations
5.1 Build System
three.js uses Rollup for building distributable bundles.
# Full build (all formats)
npm run build
# → rollup -c utils/build/rollup.config.js
# Produces: build/three.cjs, build/three.module.js,
# build/three.webgpu.js, build/three.tsl.js
# ES module only (faster)
npm run build-module
# → rollup -c utils/build/rollup.config.js --configOnlyModule
The build configuration lives at utils/build/rollup.config.js and uses:
@rollup/plugin-node-resolvefor module resolution@rollup/plugin-terserfor minification
Tip
During development, use
npm start(alias:npm run dev) which runsnode utils/build/dev.js && node utils/server.js -p 8080. This builds incrementally and starts a local development server on port 8080.
5.2 Development Server
npm start
# Equivalent to: node utils/build/dev.js && node utils/server.js -p 8080
The dev server (utils/server.js) serves the project root, making examples accessible at http://localhost:8080/examples/.
5.3 Testing
three.js has three testing tiers:
Unit Tests (QUnit)
# Core library tests
npm run test-unit
# → qunit test/unit/three.source.unit.js
# Addon tests
npm run test-unit-addons
# → qunit test/unit/three.addons.unit.js
# Both (plus lint)
npm test
# → npm run lint && npm run test-unit && npm run test-unit-addons
Unit tests live in test/unit/ and follow the same directory structure as src/. Each class has a corresponding test file.
E2E Screenshot Tests (Puppeteer)
# Run against WebGL
npm run test-e2e
# → node test/e2e/puppeteer.js
# Run against WebGPU
npm run test-e2e-webgpu
# → node test/e2e/puppeteer.js --webgpu
# Generate new reference screenshots
npm run make-screenshot
# → node test/e2e/puppeteer.js --make
E2E tests render examples with Puppeteer and compare screenshots pixel-by-pixel using pngjs and jpeg-js. Reference images are stored alongside tests.
Tree-Shaking Verification
npm run test-treeshake
# → rollup -c test/rollup.treeshake.config.js
Validates that tree-shaking works correctly and unused code is eliminated.
Note
The full test suite (
npm test) runs linting first, then unit tests. E2E tests are separate because they require a running browser and are slower. CI pipelines should runnpm testfor fast feedback andnpm run test-e2eas a secondary step.
5.4 Linting
# Lint core source
npm run lint
# → npm run lint-core (eslint src)
# Auto-fix all directories
npm run lint-fix
ESLint configuration (eslint.config.js):
- ECMAScript 2022, source type: module
- Browser + Node + ES2017 globals
- Custom globals for three.js ecosystem:
__THREE_DEVTOOLS__,potpack,fflate,Stats, GPU/XR prefixed globals - Key enforced rules:
no-throw-literal: error— must throw Error objectsquotes: single— single quotes everywhereprefer-const— useconstwhen variable is never reassignedno-duplicate-imports— combine imports from same moduleprefer-spread— use spread over.apply()
- JSDoc rules:
check-types: error,require-param-type: error,require-returns-type: error - Ignored directories:
node_modules,build,libs(vendored: acorn, codemirror, ternjs, esprima, jsonlint, signals),3rdparty,vendor
5.5 Documentation Generation
# Build API docs + LLM-friendly docs
npm run build-docs
# → jsdoc -c utils/docs/jsdoc.config.json && npm run build-llms
# LLM docs only
npm run build-llms
# → node utils/llms/build.js
The API documentation site source lives in docs/ and is built with JSDoc using the config at utils/docs/jsdoc.config.json. The build-llms step generates LLM-optimized documentation via utils/llms/build.js.
5.6 Preview Builds
npm run preview
# → node utils/build/preview.js
Generates preview builds for testing changes before a full build.
6. Performance
6.1 Draw Call Optimization
Draw calls are the primary performance bottleneck in WebGL/WebGPU rendering. three.js provides several mechanisms to reduce them:
InstancedMesh — renders many copies of the same geometry+material in a single draw call:
const mesh = new THREE.InstancedMesh(geometry, material, count);
// Set transforms for each instance
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
matrix.setPosition(x, y, z);
mesh.setMatrixAt(i, matrix);
}
mesh.instanceMatrix.needsUpdate = true;
// Optional per-instance color
mesh.setColorAt(i, new THREE.Color(0xff0000));
mesh.instanceColor.needsUpdate = true;
BatchedMesh — multi-draw batching for mixed geometries:
const batchedMesh = new BatchedMesh(maxInstanceCount, maxVertexCount, maxIndexCount);
const geoId = batchedMesh.addGeometry(geometry);
const instanceId = batchedMesh.addInstance(geoId);
batchedMesh.setMatrixAt(instanceId, matrix);
Caution
BatchedMeshenforces strict limits. ExceedingmaxInstanceCount,maxVertexCount, ormaxIndexCountthrows validation errors. Pre-calculate your maximums and add headroom. Error messages include: "THREE.BatchedMesh: Maximum item count reached" and "THREE.BatchedMesh: %.reservedSpace exceeded".
Geometry Merging — for completely static scenes:
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
const merged = mergeGeometries([geo1, geo2, geo3]);
// Results in a single Mesh with one draw call
6.2 Memory Management
three.js does not automatically release GPU resources. Every GPU-backed object must be explicitly disposed:
// Geometry
geometry.dispose();
// Material (and its textures)
material.dispose();
material.map?.dispose();
material.normalMap?.dispose();
material.envMap?.dispose();
// ... all texture properties
// Render targets
renderTarget.dispose();
// Renderer (releases all GPU resources)
renderer.dispose();
Common memory leak pattern:
// BAD: Creates new geometry every frame
function animate() {
const geo = new THREE.BoxGeometry(1, 1, 1);
mesh.geometry = geo; // Old geometry leaked!
}
// GOOD: Reuse or dispose
function animate() {
mesh.geometry.dispose();
mesh.geometry = new THREE.BoxGeometry(1, 1, 1);
}
6.3 Texture Optimization
| Technique | Impact | Implementation |
|---|---|---|
| Power-of-two textures | Avoids GPU padding | Use 256, 512, 1024, 2048 px dimensions |
| Compressed textures | 4-8x memory reduction | Use KTX2Loader with Basis Universal |
| Mipmaps | Better minification quality | Enabled by default for power-of-two |
texture.generateMipmaps |
Save VRAM for UI textures | Set false for non-3D textures |
texture.anisotropy |
Quality vs. performance | Set to renderer.capabilities.getMaxAnisotropy() for best quality |
6.4 Shadow Map Performance
Shadow mapping is one of the most expensive operations. Configuration choices significantly impact performance:
| Shadow Map Type | Quality | Cost | Notes |
|---|---|---|---|
BasicShadowMap |
Low | Lowest | Hard edges, fast |
PCFShadowMap |
Medium | Medium | Default, filtered edges |
PCFSoftShadowMap |
High | High | Softer edges, more samples |
VSMShadowMap |
High | Highest | Variance shadow maps, supports blurring |
Optimization strategies:
- Limit
shadow.mapSize(512 for distant lights, 2048 maximum for hero lights) - Set tight
shadow.camera.near/shadow.camera.farbounds - Disable shadows on objects that don't need them (
castShadow = false,receiveShadow = false) - Use
shadow.autoUpdate = falsefor static scenes, callshadow.needsUpdate = trueonly when the scene changes
6.5 Frustum Culling and Layers
Frustum culling is enabled by default (object.frustumCulled = true). Objects outside the camera's view frustum are skipped during rendering. For objects with expensive bounding sphere calculations or known visibility, disable selectively.
Layers provide a 32-channel bitmask system for selective rendering and raycasting:
// Assign object to layer 1
mesh.layers.set(1);
// Camera sees only layer 1
camera.layers.set(1);
// Enable multiple layers
camera.layers.enable(0);
camera.layers.enable(1);
// Raycaster respects layers
raycaster.layers.set(1);
6.6 Level of Detail (LOD)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0); // Used when distance < 50
lod.addLevel(mediumDetailMesh, 50); // Used when distance < 200
lod.addLevel(lowDetailMesh, 200); // Used when distance >= 200
scene.add(lod);
LOD automatically switches between detail levels based on camera distance, computed each frame during lod.update(camera).
6.7 Animation Performance
- Use
AnimationMixer.update(delta)with the actual frame delta, not a fixed timestep - Disable animations when objects are off-screen
- For large numbers of animated objects, consider skeletal animation with
SkinnedMesh/Bone/Skeletonover per-vertex animation AnimationAction.setEffectiveWeight(0)stops a blended action from consuming CPU without removing it
6.8 WebGPU Performance Advantages
The WebGPU path offers performance improvements over WebGL for specific workloads:
- Compute shaders: Offload particle systems, physics, or mesh operations to the GPU via
renderer.compute() - Reduced CPU overhead: Better command batching and pipeline state caching
- Render bundles:
RenderBundlereduces per-frame JavaScript overhead for static scene portions
7. Security
7.1 WebGL/WebGPU Context Security
Warning
WebGL and WebGPU execute code on the GPU. While browser sandboxing limits direct system access, several attack vectors exist that three.js applications should mitigate.
Context loss: WebGL contexts can be lost due to GPU driver crashes, system resource pressure, or malicious pages. Handle gracefully:
renderer.domElement.addEventListener('webglcontextlost', (event) => {
event.preventDefault();
// Pause rendering, show user message
});
renderer.domElement.addEventListener('webglcontextrestored', () => {
// Reinitialize resources
});
7.2 Cross-Origin Resource Loading
three.js loaders fetch external resources (textures, models, audio). Cross-origin requests are subject to CORS:
const loader = new THREE.TextureLoader();
loader.crossOrigin = 'anonymous'; // Required for cross-origin textures
// For loaders using LoadingManager
const manager = new THREE.LoadingManager();
manager.crossOrigin = 'anonymous';
Security considerations:
- Textures loaded cross-origin without CORS headers will taint the canvas, preventing
toDataURL()andreadPixels() preserveDrawingBuffer: trueis required for screenshots but exposes frame content to JavaScript- Model files (GLTF, OBJ, FBX) can reference external textures — validate URLs in user-submitted content
7.3 User-Generated Content
When loading user-provided 3D models or scenes:
Warning
ObjectLoader.parse()can reconstruct arbitrary three.js scenes from JSON, including custom shader code inShaderMaterial. Never load untrusted scene JSON without sandboxing.
- Shader injection:
ShaderMaterialandRawShaderMaterialaccept arbitrary GLSL/WGSL. Sanitize or reject user-provided shader code - Resource exhaustion: Malicious models can contain extremely high polygon counts or massive textures. Validate geometry vertex counts and texture dimensions before loading
- External references: GLTF files can embed
data:URIs or reference external URLs. Audit loaded resources - Script execution: The three.js Editor supports embedded scripts via its script editor (using esprima for parsing). Never expose the editor to untrusted users without sandboxing
7.4 Timing Attacks
WebGL's readPixels() and timing of draw calls can theoretically leak cross-origin image data. Modern browsers mitigate this, but avoid rendering sensitive cross-origin content on the same canvas as user-controlled shaders.
8. Developer Internals
8.1 Repository Structure
three.js/
├── src/ Core library source (ES modules)
│ ├── Three.Core.js Main barrel export (WebGL path)
│ ├── Three.TSL.js TSL node system re-exports
│ ├── Three.WebGPU.js WebGPU path barrel export
│ ├── constants.js All enumerations
│ ├── utils.js Console helpers
│ ├── core/ Object3D, BufferGeometry, EventDispatcher, etc.
│ ├── renderers/ Rendering engines
│ │ ├── WebGLRenderer.js Legacy WebGL renderer (~3000 lines)
│ │ ├── common/ Shared renderer infrastructure
│ │ │ ├── Renderer.js Base class for modern path
│ │ │ ├── Backend.js GPU backend abstraction
│ │ │ ├── RenderList.js Render list management
│ │ │ ├── RenderObject.js Per-object render state
│ │ │ ├── RenderBundle.js Render bundle support
│ │ │ ├── RenderContext.js Render pass context
│ │ │ ├── DataMap.js Internal state association
│ │ │ └── PostProcessing.js Post-processing support
│ │ ├── webgl-fallback/ WebGL backend for modern path
│ │ │ └── WebGLBackend.js WebGL backend implementation
│ │ └── webgpu/ WebGPU backend
│ │ └── WebGPUBackend.js WebGPU backend implementation
│ ├── scenes/ Scene, Fog, FogExp2
│ ├── cameras/ Camera types
│ ├── lights/ Light types + shadow classes
│ ├── materials/ All material types
│ ├── geometries/ All geometry types
│ ├── objects/ Mesh, Line, Points, Sprite, etc.
│ ├── animation/ AnimationMixer, Clip, Action, Track
│ ├── loaders/ Loader system + LoadingManager
│ ├── audio/ Web Audio integration
│ ├── math/ Vector, Matrix, Quaternion, etc.
│ ├── textures/ Texture types
│ ├── helpers/ Visual debugging aids
│ ├── extras/ Controls, Curves, Path, PMREMGenerator
│ └── nodes/ Node-based shader system (TSL)
│ ├── core/ 40+ node types
│ ├── utils/ Utility nodes
│ └── materialx/ MaterialX integration
├── examples/
│ ├── jsm/ Addon modules (published as three/addons)
│ │ ├── controls/ OrbitControls, FlyControls, etc.
│ │ ├── loaders/ GLTFLoader, DRACOLoader, etc.
│ │ ├── exporters/ GLTFExporter, OBJExporter, etc.
│ │ ├── postprocessing/ EffectComposer, passes
│ │ ├── utils/ BufferGeometryUtils, SceneOptimizer, etc.
│ │ ├── Addons.js Barrel export for all addons
│ │ └── ... Materials, geometries, curves, etc.
│ ├── fonts/ Font data files (published as three/examples/fonts)
│ ├── models/ Example 3D models
│ ├── shaders/ Example shader code
│ └── textures/ Example textures
├── editor/ Web-based 3D editor
│ ├── index.html Editor entry point
│ ├── js/ Editor JavaScript
│ │ ├── Editor.js Core editor state
│ │ ├── commands/ 24 undo/redo command classes
│ │ ├── libs/ UI framework, codemirror, esprima
│ │ └── ... Viewport, sidebar, menubar, etc.
│ ├── css/ Editor styles
│ └── examples/ Editor example scenes
├── devtools/ Browser devtools extension
├── manual/ Tutorial content (multi-language)
├── docs/ API documentation site source
├── test/
│ ├── unit/ QUnit unit tests
│ ├── e2e/ Puppeteer screenshot tests
│ └── treeshake/ Tree-shaking verification
├── utils/
│ ├── build/ Rollup build scripts, dev server
│ │ ├── rollup.config.js Main build configuration
│ │ ├── dev.js Development build script
│ │ └── preview.js Preview build script
│ ├── docs/ JSDoc config + template
│ ├── llms/ LLM-friendly docs builder
│ │ └── build.js LLM docs generation script
│ └── server.js Development server
├── build/ Built output (gitignored)
├── package.json Zero runtime deps, all devDependencies
├── eslint.config.js ESLint flat config (ES2022)
└── LICENSE MIT
8.2 Class Hierarchy
three.js contains 411 classes. Below is the complete inheritance hierarchy for all major subsystems.
EventDispatcher Tree
EventDispatcher is the root mixin for nearly all stateful classes:
EventDispatcher
├── Object3D
│ ├── Scene
│ ├── Camera
│ │ ├── PerspectiveCamera
│ │ ├── OrthographicCamera
│ │ ├── ArrayCamera
│ │ ├── StereoCamera
│ │ └── CubeCamera
│ ├── Mesh
│ │ ├── InstancedMesh
│ │ ├── BatchedMesh
│ │ └── SkinnedMesh
│ ├── Line
│ │ ├── LineSegments
│ │ │ └── LineLoop (conceptual, same level)
│ │ └── LineLoop
│ ├── Points
│ ├── Sprite
│ ├── Group
│ │ └── ClippingGroup
│ ├── LOD
│ ├── Bone
│ ├── Light
│ │ ├── AmbientLight
│ │ ├── DirectionalLight
│ │ ├── PointLight
│ │ ├── SpotLight
│ │ ├── HemisphereLight
│ │ ├── RectAreaLight
│ │ └── LightProbe
│ ├── Audio
│ │ └── PositionalAudio
│ └── AudioListener
├── BufferGeometry
│ ├── BoxGeometry
│ ├── SphereGeometry
│ ├── PlaneGeometry
│ ├── CircleGeometry
│ ├── CylinderGeometry
│ ├── ConeGeometry
│ ├── TorusGeometry
│ ├── TorusKnotGeometry
│ ├── RingGeometry
│ ├── CapsuleGeometry
│ ├── DodecahedronGeometry
│ ├── IcosahedronGeometry
│ ├── OctahedronGeometry
│ ├── TetrahedronGeometry
│ ├── PolyhedronGeometry
│ ├── LatheGeometry
│ ├── ExtrudeGeometry
│ ├── ShapeGeometry
│ ├── TubeGeometry
│ ├── EdgesGeometry
│ └── WireframeGeometry
├── Material
│ ├── MeshBasicMaterial
│ ├── MeshLambertMaterial
│ ├── MeshPhongMaterial
│ ├── MeshStandardMaterial
│ │ └── MeshPhysicalMaterial
│ ├── MeshToonMaterial
│ ├── MeshNormalMaterial
│ ├── MeshDepthMaterial
│ ├── MeshDistanceMaterial
│ ├── MeshMatcapMaterial
│ ├── ShaderMaterial
│ │ └── RawShaderMaterial
│ ├── LineBasicMaterial
│ │ └── LineDashedMaterial
│ ├── PointsMaterial
│ ├── SpriteMaterial
│ └── ShadowMaterial
├── Texture
│ ├── CubeTexture
│ ├── CanvasTexture
│ ├── CompressedTexture
│ ├── CompressedArrayTexture
│ ├── Data3DTexture
│ ├── DataArrayTexture
│ ├── DataTexture
│ ├── DepthTexture
│ ├── FramebufferTexture
│ └── VideoTexture
├── Controls (src/extras/Controls.js)
│ └── [addon controls extend this: OrbitControls, etc.]
├── Node (src/nodes/core/Node.js)
│ ├── ArrayNode
│ ├── AssignNode
│ ├── AttributeNode
│ ├── BypassNode
│ ├── ConstNode
│ ├── ContextNode
│ ├── IndexNode
│ ├── InputNode
│ ├── IsolateNode
│ ├── MRTNode
│ ├── OutputStructNode
│ ├── ParameterNode
│ ├── PropertyNode
│ ├── StackNode
│ ├── StructNode
│ ├── TempNode
│ ├── UniformNode
│ ├── VarNode
│ ├── VaryingNode
│ └── [40+ more node types in nodes/utils/, nodes/materialx/]
└── Renderer (src/renderers/common/Renderer.js)
└── [WebGPURenderer extends this]
Shadow Hierarchy
LightShadow
├── DirectionalLightShadow
├── SpotLightShadow
└── PointLightShadow
Math Classes (standalone, no inheritance)
Vector2, Vector3, Vector4
Matrix2, Matrix3, Matrix4
Quaternion, Euler
Color
Box2, Box3
Sphere, Ray, Plane, Line3, Triangle
Frustum, FrustumArray
Spherical, Cylindrical
SphericalHarmonics3
MathUtils (static utility object)
Interpolant Hierarchy
Interpolant
├── CubicInterpolant
├── LinearInterpolant
├── DiscreteInterpolant
├── QuaternionLinearInterpolant
└── BezierInterpolant
KeyframeTrack Hierarchy
KeyframeTrack
├── BooleanKeyframeTrack
├── ColorKeyframeTrack
├── NumberKeyframeTrack
├── QuaternionKeyframeTrack
├── StringKeyframeTrack
└── VectorKeyframeTrack
BufferAttribute Typed Variants
BufferAttribute
├── Int8BufferAttribute
├── Uint8BufferAttribute
├── Uint8ClampedBufferAttribute
├── Int16BufferAttribute
├── Uint16BufferAttribute
├── Int32BufferAttribute
├── Uint32BufferAttribute
├── Float16BufferAttribute
└── Float32BufferAttribute
Render Target Hierarchy
RenderTarget
├── WebGLRenderTarget
│ ├── WebGL3DRenderTarget
│ ├── WebGLArrayRenderTarget
│ └── WebGLCubeRenderTarget
Curve Hierarchy
Curve
├── CurvePath
│ └── Path
│ └── Shape
├── LineCurve
├── LineCurve3
├── QuadraticBezierCurve
├── QuadraticBezierCurve3
├── CubicBezierCurve
├── CubicBezierCurve3
├── SplineCurve
├── CatmullRomCurve3
├── EllipseCurve
│ └── ArcCurve
Loader Hierarchy
Loader
├── FileLoader
├── TextureLoader
├── ImageLoader
├── ObjectLoader
├── MaterialLoader
├── BufferGeometryLoader
├── AnimationLoader
├── AudioLoader
├── CubeTextureLoader
├── CompressedTextureLoader
├── DataTextureLoader
└── [addon loaders: GLTFLoader, OBJLoader, FBXLoader, etc.]
8.3 Rendering Pipeline
WebGLRenderer Pipeline
The legacy WebGLRenderer.render(scene, camera) executes the following sequence:
render(scene, camera)
│
├── 1. Update scene graph matrices
│ └── scene.updateMatrixWorld()
│ └── Recursively computes world matrices for all children
│
├── 2. Update camera matrices
│ ├── camera.updateMatrixWorld()
│ └── camera.matrixWorldInverse = inverse(camera.matrixWorld)
│
├── 3. Project objects (frustum culling + sorting)
│ ├── Traverse scene graph
│ ├── Test each object against camera frustum (if frustumCulled=true)
│ ├── Check object.layers vs camera.layers
│ ├── Sort opaque objects front-to-back (minimize overdraw)
│ └── Sort transparent objects back-to-front (correct blending)
│
├── 4. Render shadow maps (if shadowMap.enabled)
│ ├── For each shadow-casting light:
│ │ ├── Set up shadow camera
│ │ ├── Render scene from light's perspective
│ │ └── Store depth in shadow map texture
│ └── Uses MeshDepthMaterial/MeshDistanceMaterial internally
│
├── 5. Set render state
│ ├── Clear buffers (if autoClear)
│ ├── Set viewport, scissor
│ └── Bind render target (or default framebuffer)
│
├── 6. Render opaque objects
│ ├── For each object in opaque list:
│ │ ├── Select/compile shader program
│ │ ├── Set uniforms (matrices, lights, material props)
│ │ ├── Bind textures, attributes, index buffer
│ │ └── Issue draw call (drawArrays or drawElements)
│ └── Instanced: drawArraysInstanced / drawElementsInstanced
│
├── 7. Render transparent objects
│ └── Same as step 6, back-to-front order
│
└── 8. Post-render
├── Reset state
├── Update info (render count, triangle count, etc.)
└── Call animation loop callback
WebGPURenderer Pipeline (Modern Path)
The WebGPU path shares the same high-level structure but uses a backend abstraction:
Renderer.render(scene, camera) ← src/renderers/common/Renderer.js
│
├── Backend.beginRender(renderContext) ← WebGPUBackend or WebGLBackend
├── RenderList.sort() ← Opaque + transparent lists
├── For each RenderObject:
│ ├── NodeBuilder.build() ← Compile node graph to shader
│ ├── Backend.draw(renderObject) ← Issue GPU draw call
│ └── RenderBundle (if applicable) ← Reuse encoded commands
└── Backend.finishRender(renderContext)
The DataMap pattern is used extensively in the common renderer to associate internal GPU state (pipelines, bind groups, buffers) with user-facing objects without modifying them:
// Internal pattern used by renderer infrastructure
class SomeManager {
constructor() {
this._dataMap = new DataMap();
}
getInternalState(object) {
return this._dataMap.get(object);
}
}
Coordinate Systems
three.js uses a right-handed coordinate system (Y-up). The renderers handle coordinate system differences:
| System | Clip Space Z | Constant |
|---|---|---|
| WebGL | [-1, 1] | WebGLCoordinateSystem |
| WebGPU | [0, 1] | WebGPUCoordinateSystem |
Attempting to use CubeCamera or certain operations with the wrong coordinate system will throw: "THREE.CubeCamera: Invalid coordinate system." or "THREE.Frustum: Invalid coordinate system.".
8.4 Node System / TSL Architecture
The Three.js Shading Language (TSL) is a node-based shader authoring system that compiles to GLSL or WGSL depending on the renderer backend. It lives in src/nodes/.
Node Graph Architecture
User Code (TSL API)
│
▼
Node Graph (DAG of Node instances)
│
▼
NodeBuilder
│
┌────┴────┐
▼ ▼
GLSL WGSL
(WebGL) (WebGPU)
Node (src/nodes/core/Node.js, extends EventDispatcher) is the base class. Every node represents a value or operation in the shader graph. Key properties:
nodeType— data type (float, vec2, vec3, vec4, mat3, mat4, etc.)updateType— when to update (NONE, FRAME, RENDER, OBJECT)build(builder)— compile this node for the target backend
Core Node Types (src/nodes/core/)
| Node Class | Purpose |
|---|---|
ArrayNode |
Array data in shaders |
AssignNode |
Variable assignment |
AttributeNode |
Vertex attributes (position, normal, uv) |
BypassNode |
Pass-through with side effects |
ConstNode |
Constant values |
ContextNode |
Execution context scoping |
IndexNode |
Vertex/instance index |
InputNode |
External input values |
IsolateNode |
Prevent node sharing optimization |
MRTNode |
Multiple Render Targets |
OutputStructNode |
Structured shader output |
ParameterNode |
Shader parameters |
PropertyNode |
Material properties |
StackNode |
Sequential operation stacking |
StructNode |
Struct definitions |
TempNode |
Temporary variables |
UniformNode |
Uniform values |
VarNode |
Variable declarations |
VaryingNode |
Vertex-to-fragment interpolation |
Utility Nodes (src/nodes/utils/)
| Node Class | Purpose |
|---|---|
ConvertNode |
Type conversion |
CubeMapNode |
Cube map sampling |
FlipNode |
UV/coordinate flipping |
JoinNode |
Combine components (e.g., vec2 → vec4) |
LoopNode |
Shader loops |
ReflectorNode |
Planar reflections |
RotateNode |
Rotation transforms |
RTTNode |
Render-to-texture |
SplitNode |
Extract components (e.g., vec4.xyz) |
NodeBuilder Compilation
NodeBuilder compiles the node graph into shader code:
- Analyze: Traverse the graph, determine types, resolve dependencies
- Build: Generate shader code for each node in dependency order
- Link: Produce final vertex/fragment shader with all declarations
Error conditions during build:
"THREE.NodeBuilder: Invalid chaining."— node graph contains invalid connections- Type mismatch errors when incompatible nodes are connected
- Missing attribute errors when required geometry data is absent
TSL Helper Functions
src/Three.TSL.js re-exports hundreds of helper functions that provide a fluent API for constructing node graphs:
import { color, float, vec3, texture, normalMap,
mix, step, smoothstep, clamp } from 'three/tsl';
// Build a shader graph using TSL
const material = new NodeMaterial();
material.colorNode = mix(color(0xff0000), color(0x0000ff), uv().x);
MaterialX Integration
src/nodes/materialx/lib/ provides MaterialX standard surface node implementations, enabling import of MaterialX-defined materials into the TSL system.
8.5 Module Reference
Core Modules (src/core/)
| Module | Lines | Key Exports | Patterns |
|---|---|---|---|
Object3D.js |
Large | Object3D |
Scene graph node, parent/child, transform stack |
BufferGeometry.js |
Large | BufferGeometry |
Attribute container, bounding volumes |
BufferAttribute.js |
Medium | BufferAttribute + 9 typed variants |
Typed array wrapper, update ranges |
EventDispatcher.js |
Small | EventDispatcher |
Observer pattern, addEventListener/dispatch |
Raycaster.js |
Medium | Raycaster |
Mouse picking, recursive intersection |
Clock.js |
Small | Clock |
Delta time, elapsed time |
Layers.js |
Small | Layers |
32-bit bitmask channel system |
Uniform.js |
Small | Uniform, UniformsGroup |
Shader uniform containers |
RenderTarget.js |
Medium | RenderTarget + WebGL variants |
Off-screen render destinations |
Renderer Modules (src/renderers/)
| Module | Key Exports | Notes |
|---|---|---|
WebGLRenderer.js |
WebGLRenderer |
~3000 lines, legacy path, direct WebGL state management |
common/Renderer.js |
Renderer |
Base class for modern path (WebGPU/WebGL-fallback) |
common/Backend.js |
Backend |
Abstract GPU backend interface |
common/RenderList.js |
RenderList |
Opaque/transparent sorting |
common/RenderObject.js |
RenderObject |
Per-object render state |
common/RenderBundle.js |
RenderBundle |
Command recording for reuse |
common/RenderContext.js |
RenderContext |
Render pass parameters |
common/DataMap.js |
DataMap |
WeakMap-like internal state association |
common/PostProcessing.js |
PostProcessing |
Built-in post-processing for modern path |
webgpu/WebGPUBackend.js |
WebGPUBackend |
WebGPU native backend |
webgl-fallback/WebGLBackend.js |
WebGLBackend |
WebGL backend for modern path |
Animation Modules (src/animation/)
| Module | Key Exports | Purpose |
|---|---|---|
AnimationMixer.js |
AnimationMixer |
Drives all animations on a root object |
AnimationClip.js |
AnimationClip |
Named collection of keyframe tracks |
AnimationAction.js |
AnimationAction |
Playback control (play, stop, crossfade, loop) |
KeyframeTrack.js |
KeyframeTrack + 6 typed variants |
Time→value interpolation |
PropertyBinding.js |
PropertyBinding |
Resolves track name strings to object properties |
AnimationObjectGroup.js |
AnimationObjectGroup |
Groups objects for shared animation |
AnimationUtils.js |
AnimationUtils |
Static helpers (makeClipAdditive, subclip) |
PropertyBinding handles the name resolution syntax: "objectName.propertyName[index]". Parse failures emit descriptive errors.
Loader Modules (src/loaders/)
| Module | Key Exports | Purpose |
|---|---|---|
Loader.js |
Loader |
Base class: path, crossOrigin, headers |
LoadingManager.js |
LoadingManager, DefaultLoadingManager |
Coordination: onStart/onLoad/onProgress/onError |
FileLoader.js |
FileLoader |
XHR-based file loading, responseType support |
Cache.js |
Cache |
Global file-level caching (enabled/disabled) |
TextureLoader.js |
TextureLoader |
Image → Texture |
ImageLoader.js |
ImageLoader |
Image element loading |
ObjectLoader.js |
ObjectLoader |
Full scene deserialization from JSON |
Math Modules (src/math/)
| Module | Key Exports | Notable Methods |
|---|---|---|
Vector3.js |
Vector3 |
add, sub, multiply, dot, cross, normalize, length, lerp, applyMatrix4, applyQuaternion, project, unproject |
Matrix4.js |
Matrix4 |
multiply, invert, decompose, compose, lookAt, makeRotation*, makeTranslation, makePerspective, makeOrthographic |
Quaternion.js |
Quaternion |
setFromAxisAngle, setFromRotationMatrix, setFromEuler, slerp, multiply, invert |
Euler.js |
Euler |
setFromRotationMatrix, setFromQuaternion, reorder; order: 'XYZ'/'YXZ'/etc. |
Color.js |
Color |
set, setHex, setRGB, setHSL, lerp, multiply, convertSRGBToLinear, convertLinearToSRGB |
Box3.js |
Box3 |
setFromObject, expandByPoint, containsPoint, intersectsBox, clampPoint |
Sphere.js |
Sphere |
setFromPoints, containsPoint, intersectsBox, intersectsPlane |
Ray.js |
Ray |
intersectBox, intersectSphere, intersectTriangle, intersectPlane |
Frustum.js |
Frustum |
setFromProjectionMatrix, intersectsObject, containsPoint |
MathUtils.js |
MathUtils |
clamp, degToRad, radToDeg, lerp, generateUUID, randFloat, randInt, isPowerOfTwo |
Extras Modules (src/extras/)
| Module | Key Exports | Purpose |
|---|---|---|
Controls.js |
Controls |
Base class: object, domElement, enabled, connect/disconnect/dispose |
curves/*.js |
Various Curve classes | Parametric curves for paths and extrusions |
core/Path.js |
Path, ShapePath |
2D path operations: moveTo, lineTo, bezierCurveTo, arc |
core/Shape.js |
Shape |
2D shape with holes, for extrusion |
PMREMGenerator.js |
PMREMGenerator |
Prefiltered environment maps for IBL |
DataUtils.js |
DataUtils |
toHalfFloat/fromHalfFloat conversion |
ImageUtils.js |
ImageUtils |
Image data manipulation |
ShapeUtils.js |
ShapeUtils |
Triangulation: area, isClockWise, triangulateShape |
TextureUtils.js |
TextureUtils |
Texture manipulation helpers |
Earcut.js |
Earcut |
Polygon triangulation algorithm |
8.6 Code Patterns & Conventions
Naming Conventions
| Convention | Example | Usage |
|---|---|---|
| PascalCase | MeshStandardMaterial |
Classes, constructors |
| camelCase | updateMatrixWorld |
Methods, properties |
| UPPER_SNAKE | FrontSide, PCFShadowMap |
Constants (in constants.js) |
_ prefix |
_vector, _matrix |
Module-level reusable temp objects |
is prefix |
isMesh, isBufferGeometry |
Type-check booleans |
Type Checking Pattern
three.js uses boolean flags instead of instanceof for type checking throughout the codebase:
// Every class sets a type flag
class Mesh extends Object3D {
constructor() {
super();
this.isMesh = true;
this.type = 'Mesh';
}
}
// Checked via property access (faster than instanceof, survives bundling)
if (object.isMesh) { /* ... */ }
if (material.isMeshStandardMaterial) { /* ... */ }
Reusable Temporary Objects
To avoid garbage collection pressure, modules declare module-scope temporary math objects:
const _vector = new Vector3();
const _matrix = new Matrix4();
const _quaternion = new Quaternion();
class SomeClass {
someMethod() {
// Reuse instead of allocating
_vector.set(1, 2, 3).applyMatrix4(this.matrix);
}
}
Caution
These temporaries are shared within a module. They are not thread-safe and should not be stored or passed to callbacks that may execute asynchronously.
Clone/Copy Pattern
Every data class implements both clone() and copy():
class Material extends EventDispatcher {
copy(source) {
this.name = source.name;
this.color.copy(source.color);
// ... copy all properties
return this;
}
clone() {
return new this.constructor().copy(this);
}
}
Serialization Pattern
toJSON() produces a portable representation; the corresponding Loader reconstructs:
// Serialize
const json = object.toJSON();
// Deserialize
const loader = new THREE.ObjectLoader();
const reconstructed = loader.parse(json);
The JSON format uses metadata.type to identify the class and metadata.version for format evolution.
Dispose Pattern
GPU resources require explicit cleanup:
// Material tracks allocated resources
material.dispose(); // Fires 'dispose' event
// Listeners (e.g., renderer) clean up GPU resources in response
geometry.dispose(); // Fires 'dispose' event
texture.dispose(); // Fires 'dispose' event
renderTarget.dispose();
renderer.dispose(); // Cleans up everything
Console Warning Deduplication
src/utils.js provides warnOnce() functionality to prevent flooding the console with repeated warnings (e.g., deprecated API usage).
8.7 Testing Strategy
Test Organization
test/
├── unit/
│ ├── three.source.unit.js Entry point for core tests
│ ├── three.addons.unit.js Entry point for addon tests
│ └── src/ Mirrors src/ directory structure
│ ├── core/
│ │ ├── Object3D.tests.js
│ │ ├── BufferGeometry.tests.js
│ │ └── ...
│ ├── math/
│ │ ├── Vector3.tests.js
│ │ └── ...
│ └── ...
├── e2e/
│ ├── puppeteer.js E2E test runner
│ └── ... Reference screenshots
└── treeshake/
└── ... Tree-shaking verification
Unit Test Framework
Tests use QUnit and follow a consistent structure:
QUnit.module('Core', () => {
QUnit.module('Object3D', () => {
QUnit.test('add/remove', (assert) => {
const parent = new Object3D();
const child = new Object3D();
parent.add(child);
assert.strictEqual(child.parent, parent);
});
});
});
E2E Screenshot Tests
The Puppeteer-based E2E system:
- Launches a headless browser
- Loads each example page
- Captures a screenshot after rendering stabilizes
- Compares pixel-by-pixel against reference images using
pngjs/jpeg-js - Fails if difference exceeds threshold
# Generate new reference screenshots
npm run make-screenshot
# Run comparison tests (WebGL)
npm run test-e2e
# Run comparison tests (WebGPU)
npm run test-e2e-webgpu
What Gets Tested
| Level | Coverage | Runner |
|---|---|---|
| Unit | Core classes, math, loaders, materials, geometries | QUnit |
| E2E | Visual output of examples (pixel comparison) | Puppeteer |
| Tree-shaking | Bundle size when importing individual classes | Rollup |
| Lint | Code style, JSDoc types | ESLint |
8.8 Editor Architecture
The three.js editor (editor/) is a full web-based 3D scene editor with an undo/redo system.
Command Pattern
All editor operations are encapsulated as command objects in editor/js/commands/:
| Command Class | Operation |
|---|---|
AddObjectCommand |
Add object to scene |
RemoveObjectCommand |
Remove object from scene |
SetPositionCommand |
Change object position |
SetRotationCommand |
Change object rotation |
SetScaleCommand |
Change object scale |
SetMaterialCommand |
Assign material to object |
SetGeometryCommand |
Assign geometry to mesh |
SetValueCommand |
Generic property change |
SetColorCommand |
Change color property |
SetMaterialValueCommand |
Change material property |
SetMaterialColorCommand |
Change material color |
SetMaterialMapCommand |
Change material texture |
MoveObjectCommand |
Reparent in scene graph |
MultiCmdsCommand |
Batch multiple commands |
| ... | 24 command classes total |
Each command implements execute() and undo(), stored in a history stack.
UI System
The editor uses a custom UI framework (editor/js/libs/ui.js) with these widget classes:
UIElement, UIDiv, UIRow, UIPanel, UIInput, UISelect, UICheckbox, UIColor, UINumber, UIText, UITextArea, UIButton, UIBreak, UISpan
Script Editor
The editor includes a code editor (powered by CodeMirror) that allows users to write per-object scripts. Scripts are parsed with esprima for syntax validation.
XR Support
The editor viewport supports VR/XR preview modes for testing immersive experiences.
8.9 Potential Improvements
Based on source analysis and open issues:
Architecture
-
WebGPU XR support: XR is not supported with the WebGPU backend — the error message
"THREE.XRManager: XR is not supported with WebGPU backend."is explicit. This limits adoption of the modern renderer for immersive applications. -
Node system complexity: The TSL node system (
src/nodes/) contains 40+ core node types. TheNodeBuildercompilation path is complex and error-prone, with several known issues around type inference and chaining validation. -
WebGLRenderer size: At ~3,000 lines,
WebGLRenderer.jsis the largest single file. The modern path'sRenderer.js+ backend separation is a better architecture, but the legacy renderer cannot be refactored without breaking changes.
Known Open Issues
| Issue | Area | Description |
|---|---|---|
| #33404 | WebXR | AR crashes Chrome 147+ |
| #33394 | TSL | textureLoad with int(0) produces GLSL2 compile error |
| #33403 | TSL | Return() value support incomplete |
| #33291 | Animation | Reverse playback edge cases |
| #29963 | GLTFLoader | UTF-8 filename encoding |
| #31994 | GLTFLoader | Negative scaling affects lighting |
| #26613 | SVGLoader | Incorrect rendering of certain SVGs |
| #33416 | TSL | String/arrayBuffer method support |
| #33412 | TSL | setName on attributes |
Code Quality Opportunities
- Inconsistent error handling in BatchedMesh: Error messages reference
%placeholder syntax (e.g.,"%.reservedSpace exceeded") which suggests incomplete string formatting - Shadow map disposal: Shadow maps are not always cleaned up when lights are removed from the scene
- Loader error propagation: Some addon loaders swallow errors silently rather than propagating to the
LoadingManager.onErrorcallback - DepthTexture format validation: The renderer validates DepthTexture formats but error messages could be more specific about which formats are supported for which operations
8.10 Contributing
Development Workflow
# Clone
git clone https://github.com/mrdoob/three.js.git
cd three.js
# Install dependencies
npm install
# Start development server
npm start
# → http://localhost:8080
# Run tests before submitting
npm test
# Lint and auto-fix
npm run lint-fix
# Build production bundles
npm run build
Code Style Requirements
- ES2022 module syntax (
import/export) - Single quotes for strings
constby default,letonly when reassignment is needed- No
vardeclarations - Tabs for indentation
- No trailing semicolons in function declarations
- All JSDoc types must be specified (
@param {type},@returns {type}) - Throw
Errorobjects, never literals (no-throw-literal: error) - Use spread syntax over
.apply()(prefer-spread) - No duplicate imports from the same module (
no-duplicate-imports)
File Organization Rules
- One class per file (matching filename)
- Constructor parameters first, then properties, then methods
clone()andcopy()at the end of the class bodytoJSON()after clone/copy- Static methods after instance methods
Addon Development
Addons live in examples/jsm/ and are published under three/addons:
// examples/jsm/controls/MyControl.js
import { Controls } from 'three';
class MyControl extends Controls {
constructor(object, domElement) {
super(object, domElement);
// ...
}
connect() { /* bind events */ }
disconnect() { /* unbind events */ }
update() { /* per-frame logic */ }
dispose() { /* cleanup */ }
}
export { MyControl };
Addons may import from three but never from three/src/* directly. This ensures they work with both source and built versions.
Tip
The
Addons.jsbarrel file atexamples/jsm/Addons.jsre-exports all addons. When adding a new addon, add its export to this file so it is accessible viaimport { MyAddon } from 'three/addons'.
9. Integration Points
9.1 Addon Ecosystem
The examples/jsm/ directory contains the official addon ecosystem, published as three/addons. Major categories:
Controls
| Addon | Import | Purpose |
|---|---|---|
OrbitControls |
three/addons/controls/OrbitControls.js |
Orbit around target (most common) |
MapControls |
three/addons/controls/MapControls.js |
Map-style pan/zoom/rotate |
TrackballControls |
three/addons/controls/TrackballControls.js |
Unconstrained rotation |
FlyControls |
three/addons/controls/FlyControls.js |
Flight simulator-style |
FirstPersonControls |
three/addons/controls/FirstPersonControls.js |
FPS camera |
PointerLockControls |
three/addons/controls/PointerLockControls.js |
Pointer lock FPS |
ArcballControls |
three/addons/controls/ArcballControls.js |
Arcball rotation |
DragControls |
three/addons/controls/DragControls.js |
Drag objects in 3D |
TransformControls |
three/addons/controls/TransformControls.js |
Translate/rotate/scale gizmo |
All controls extend Controls (src/extras/Controls.js) which provides: object, domElement, enabled, state, keys, mouseButtons, touches, and connect()/disconnect()/update()/dispose() lifecycle methods.
Loaders
| Addon | Format | Notes |
|---|---|---|
GLTFLoader |
glTF 2.0 (.gltf, .glb) | Primary 3D format, supports extensions |
DRACOLoader |
Draco compressed | Used with GLTFLoader for compressed meshes |
KTX2Loader |
KTX2/Basis Universal | GPU-compressed textures |
OBJLoader |
Wavefront OBJ | Legacy format, no animation |
FBXLoader |
Autodesk FBX | Animation support |
STLLoader |
Stereolithography | 3D printing format |
PLYLoader |
Stanford PLY | Point clouds, meshes |
PCDLoader |
Point Cloud Data | Point clouds |
ColladaLoader |
COLLADA (.dae) | XML-based, older format |
SVGLoader |
SVG | 2D vector to 3D geometry |
RGBELoader |
Radiance RGBE (.hdr) | HDR environment maps |
EXRLoader |
OpenEXR | HDR images |
HDRCubeTextureLoader |
HDR Cube | HDR cube map textures |
LDrawLoader |
LDraw | LEGO brick models |
3DMLoader |
Rhinoceros 3DM | Rhino models |
3MFLoader |
3MF | 3D manufacturing format |
VRMLLoader |
VRML | Legacy 3D web format |
BVHLoader |
BVH | Motion capture data |
Note
GLTFLoaderis the recommended format for web 3D. It supports PBR materials, animations, morph targets, and numerous extensions. UseDRACOLoaderfor mesh compression andKTX2Loaderfor texture compression to minimize download sizes.
Exporters
| Addon | Format |
|---|---|
GLTFExporter |
glTF 2.0 (.gltf, .glb) |
OBJExporter |
Wavefront OBJ |
STLExporter |
STL (ASCII/binary) |
PLYExporter |
Stanford PLY |
USDZExporter |
Apple USDZ (AR Quick Look) |
ColladaExporter |
COLLADA (.dae) |
Post-Processing
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(new ShaderPass(someShader));
function animate() {
composer.render(); // Instead of renderer.render()
}
Tip
The WebGPU path has built-in post-processing via
PostProcessing(src/renderers/common/PostProcessing.js) that uses TSL nodes instead of the addonEffectComposer. For the modern path, prefer the built-in system.
Utilities
| Addon | Purpose |
|---|---|
BufferGeometryUtils |
mergeGeometries(), computeMikkTSpaceTangents(), interleave attributes |
SceneOptimizer |
Automated scene optimization |
ShadowMapViewer |
Debug visualization for shadow maps |
WorkerPool |
Web Worker thread pool for parallel loading |
LDrawUtils |
LDraw model processing utilities |
9.2 Build Tool Integration
Vite
// vite.config.js
export default {
// No special configuration needed — three.js works out of the box
// Optionally optimize dependency pre-bundling:
optimizeDeps: {
include: ['three']
}
};
Webpack
// webpack.config.js
module.exports = {
resolve: {
// three.js uses package.json exports field (webpack 5+)
// No additional configuration needed
}
};
TypeScript
three.js ships with TypeScript type definitions generated from JSDoc annotations. No @types/three package is needed for recent versions:
import * as THREE from 'three';
const scene: THREE.Scene = new THREE.Scene();
const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
9.3 Framework Integration
React (react-three-fiber)
The React ecosystem uses @react-three/fiber as the standard binding:
import { Canvas } from '@react-three/fiber';
function App() {
return (
<Canvas>
<mesh>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
<ambientLight />
<directionalLight position={[10, 10, 5]} />
</Canvas>
);
}
Web Components
three.js scenes can be encapsulated in web components:
class ThreeScene extends HTMLElement {
connectedCallback() {
const renderer = new THREE.WebGLRenderer();
this.appendChild(renderer.domElement);
// ... setup scene
}
disconnectedCallback() {
this.renderer.dispose();
// ... cleanup
}
}
customElements.define('three-scene', ThreeScene);
9.4 WebXR Integration
three.js includes built-in WebXR support through the renderer's XR manager:
renderer.xr.enabled = true;
// VR
document.body.appendChild(VRButton.createButton(renderer));
// AR
document.body.appendChild(ARButton.createButton(renderer));
renderer.setAnimationLoop(function () {
renderer.render(scene, camera);
});
Warning
XR is only supported with
WebGLRendereror theWebGLBackendofWebGPURenderer. Using the nativeWebGPUBackendwith XR will throw:"THREE.XRManager: XR is not supported with WebGPU backend."Issue #33404 also documents WebXR AR crashes in Chrome 147+.
9.5 Audio Integration
three.js wraps the Web Audio API for spatialized 3D audio:
const listener = new THREE.AudioListener();
camera.add(listener);
// Non-positional (background music)
const sound = new THREE.Audio(listener);
new THREE.AudioLoader().load('music.mp3', (buffer) => {
sound.setBuffer(buffer);
sound.play();
});
// Positional (3D spatialized)
const positionalSound = new THREE.PositionalAudio(listener);
mesh.add(positionalSound); // Sound follows the mesh in 3D space
// Analysis
const analyser = new THREE.AudioAnalyser(sound, 256);
const frequencyData = analyser.getFrequencyData();
AudioListener attaches to the camera and creates/manages the AudioContext singleton. PositionalAudio extends Audio with a PannerNode for 3D spatialization.
9.6 Devtools Integration
The devtools/ directory contains a browser extension panel for inspecting three.js scenes at runtime. It uses DevToolsEventTarget as a bridge between the page and the extension panel.
Applications opt into devtools support via the __THREE_DEVTOOLS__ global:
if (typeof __THREE_DEVTOOLS__ !== 'undefined') {
__THREE_DEVTOOLS__.dispatchEvent(
new CustomEvent('observe', { detail: scene })
);
}
9.7 Helpers for Development
three.js includes visual debugging helpers that render directly in the scene:
| Helper | Visualizes |
|---|---|
AxesHelper |
RGB axes (X=red, Y=green, Z=blue) |
GridHelper |
Ground plane grid |
PolarGridHelper |
Polar coordinate grid |
ArrowHelper |
Direction vector arrow |
BoxHelper |
Object bounding box wireframe |
Box3Helper |
Box3 wireframe |
PlaneHelper |
Plane normal and extent |
CameraHelper |
Camera frustum wireframe |
DirectionalLightHelper |
Directional light direction |
HemisphereLightHelper |
Hemisphere light orientation |
PointLightHelper |
Point light position |
SpotLightHelper |
Spotlight cone |
SkeletonHelper |
Bone structure wireframe |
// Add axes helper to any object
const axes = new THREE.AxesHelper(5);
scene.add(axes);
// Visualize a camera's frustum
const helper = new THREE.CameraHelper(camera);
scene.add(helper);
10. Troubleshooting
10.1 Common Errors and Solutions
Note
Below are the most frequently encountered errors from the 82 error messages in the three.js source. Each includes the exact message text and resolution steps.
WebGL Context Errors
Error: THREE.WebGLRenderer: WebGL 1 is not supported since r163.
Cause: Browser or device only supports WebGL 1.
Solution: Use a modern browser (Chrome 80+, Firefox 78+, Safari 14+) that supports WebGL 2.
Error: WebGL context creation failures
Cause: GPU drivers, browser extensions blocking WebGL, or too many active contexts.
Solution: Check navigator.gpu or canvas.getContext('webgl2') before creating a renderer. Dispose unused renderers.
Renderer Initialization
Error: Methods called before renderer.init() completes
Cause: WebGPURenderer requires async initialization.
Solution: Always await renderer.init() before calling render(), setSize(), or other methods.
Buffer and Attribute Errors
Warning
BufferAttributevalidates that its data is a typed array. Passing a regularArrayor incorrect type will throw an error. Always useFloat32Array,Uint16Array, etc.
Error: BufferAttribute typed array check
Cause: Non-typed array passed to BufferAttribute constructor.
Solution: Use the appropriate typed variant (Float32BufferAttribute, etc.) or pass a typed array.
Depth Texture Errors
Error: WebGLRenderTarget DepthTexture size mismatch Cause: DepthTexture dimensions don't match the RenderTarget. Solution: Ensure DepthTexture width/height matches the RenderTarget's width/height.
Error: DepthTexture format validation
Cause: Invalid format/type combination for DepthTexture.
Solution: Use DepthFormat with UnsignedIntType or DepthStencilFormat with UnsignedInt248Type.
BatchedMesh Errors
Error: THREE.BatchedMesh: Maximum item count reached.
Cause: Attempted to add more instances than maxInstanceCount.
Solution: Increase maxInstanceCount in the constructor, or reuse instances.
Error: THREE.BatchedMesh: %.reservedSpace exceeded.
Cause: Geometry vertex/index data exceeds reserved space.
Solution: Increase maxVertexCount/maxIndexCount in the constructor.
Animation Errors
Error: KeyframeTrack validation errors
Cause: Invalid keyframe data (NaN values, non-monotonic times, mismatched array lengths).
Solution: Validate animation data before creating tracks. Use AnimationClip.validate().
Error: PropertyBinding parse failures
Cause: Invalid binding path string format.
Solution: Use the format "objectName.propertyName[index]". Verify object names match scene hierarchy.
Node System Errors
Error: THREE.NodeBuilder: Invalid chaining.
Cause: Incompatible node connections in TSL graph.
Solution: Check that connected nodes have compatible types. Use explicit type conversion nodes.
Multiple Instances Warning
Warning: Multiple Three.js instances detected
Cause: Application loads more than one copy of three.js.
Solution: Check for duplicate dependencies. Use import maps or bundler aliases to ensure a single copy. Run npm ls three to find duplicates.
Coordinate System Errors
Error: THREE.CubeCamera: Invalid coordinate system.
Cause: CubeCamera used with wrong coordinate system setting.
Solution: Ensure the renderer's coordinate system matches expectations. WebGL uses WebGLCoordinateSystem, WebGPU uses WebGPUCoordinateSystem.
Tip
Enable the three.js devtools browser extension for real-time scene inspection. Many rendering issues (missing objects, incorrect materials, wrong transforms) are immediately visible in the scene graph inspector.
10.2 Performance Debugging
| Symptom | Likely Cause | Diagnostic |
|---|---|---|
| Low FPS, high draw calls | Too many objects | renderer.info.render.calls |
| Low FPS, few draw calls | Complex shaders/geometry | GPU profiler (Chrome DevTools → Performance) |
| Memory growth over time | Missing dispose() calls | renderer.info.memory (geometries, textures) |
| Stuttering | Garbage collection | Avoid allocations in render loop |
| Black screen | Missing lights or wrong material | Add AmbientLight, check material.side |
| Objects invisible | Wrong layer, frustum culled, scale 0 | Check object.layers, object.visible, object.scale |
// Monitor renderer statistics
console.log(renderer.info.render.calls); // Draw calls per frame
console.log(renderer.info.render.triangles); // Triangles per frame
console.log(renderer.info.memory.geometries); // Allocated geometries
console.log(renderer.info.memory.textures); // Allocated textures