# three.js User Guide

three.js is a JavaScript 3D graphics library that creates and renders interactive 3D scenes in the browser using WebGL and WebGPU. It provides a complete scene graph, physically-based materials, dozens of geometry primitives, animation, audio, model loading, and post-processing — all with zero runtime dependencies.

This guide covers three.js **v0.184.0** (revision r184).

---

## 1. Quick Start

Get a spinning cube on screen in under two minutes.

### 1.1 Minimal HTML Setup

Create a single HTML file:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>three.js Quick Start</title>
  <style>
    body { margin: 0; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script type="importmap">
    {
      "imports": {
        "three": "https://unpkg.com/three@0.184.0/build/three.module.js",
        "three/addons/": "https://unpkg.com/three@0.184.0/examples/jsm/"
      }
    }
  </script>
  <script type="module">
    import * as THREE from 'three';

    // Scene
    const scene = new THREE.Scene();

    // Camera
    const camera = new THREE.PerspectiveCamera(
      75,                                    // field of view (degrees)
      window.innerWidth / window.innerHeight, // aspect ratio
      0.1,                                   // near clipping plane
      1000                                   // far clipping plane
    );
    camera.position.z = 5;

    // Renderer
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // Mesh = Geometry + Material
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);

    // Animation loop
    function animate() {
      cube.rotation.x += 0.01;
      cube.rotation.y += 0.01;
      renderer.render(scene, camera);
    }
    renderer.setAnimationLoop(animate);
  </script>
</body>
</html>
```

Open this file in any modern browser. You will see a rotating green cube.

> [!TIP]
> Use `renderer.setAnimationLoop(callback)` instead of manual `requestAnimationFrame` calls. It handles the loop lifecycle automatically and is required for WebXR compatibility.

### 1.2 The Three Pillars

Every three.js application requires exactly three objects:

| Object | Class | Purpose |
|--------|-------|---------|
| **Scene** | `Scene` | Container for all 3D objects, lights, and cameras |
| **Camera** | `PerspectiveCamera` or `OrthographicCamera` | Defines what portion of the scene is visible |
| **Renderer** | `WebGLRenderer` or `WebGPURenderer` | Draws the scene to a `<canvas>` element |

The render call brings them together: `renderer.render(scene, camera)`.

### 1.3 What Goes Into a Scene

```text
Scene
├── Mesh (geometry + material)
├── Light (ambient, directional, point, spot, ...)
├── Group
│   ├── Mesh
│   └── Mesh
├── Camera
├── Sprite
├── Line / LineSegments
├── Points
└── Audio / PositionalAudio
```

All visible objects extend `Object3D`, which provides `position`, `rotation`, `scale`, `visible`, `parent`, `children`, and the `add()` / `remove()` methods that form the scene graph hierarchy.

---

## 2. Installation

### 2.1 npm (Recommended)

```bash
npm install three
```

Then import in your JavaScript or TypeScript modules:

```javascript
import * as THREE from 'three';
```

> [!IMPORTANT]
> three.js is an ES module package (`"type": "module"` in its `package.json`). Use `import` syntax, not `require()`. If your bundler or Node.js configuration expects CommonJS, you will need to configure it for ESM interop.

### 2.2 Package Exports

The `three` package exposes several entry points:

| Import path | What you get |
|---|---|
| `three` | Core library with `WebGLRenderer` |
| `three/webgpu` | `WebGPURenderer` + the full node/TSL system |
| `three/tsl` | Three Shading Language functions only |
| `three/addons` | All addons (controls, loaders, exporters, etc.) |
| `three/addons/controls/OrbitControls.js` | Individual addon files |
| `three/src/*` | Raw unbuilt source files |

```javascript
// Import core + WebGPURenderer
import * as THREE from 'three/webgpu';

// Import a specific addon
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
```

### 2.3 CDN / Import Maps

For prototyping without a build tool, use an import map in your HTML:

```html
<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@0.184.0/build/three.module.js",
      "three/addons/": "https://unpkg.com/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]
> Never load three.js from multiple sources (e.g., mixing a CDN copy with an npm copy). The library detects this and warns: `"WARNING: Multiple instances of Three.js being imported."` Duplicate instances cause type checks to fail silently — materials, geometries, and textures from one instance are incompatible with renderers from another.

### 2.4 Development Server

If you clone the three.js repository for development or to run examples:

```bash
git clone https://github.com/mrdoob/three.js.git
cd three.js
npm install
npm start
```

This starts a dev server on **port 8080** with live reloading. Other npm scripts:

| Command | Purpose |
|---|---|
| `npm run dev` | Same as `npm start` — build + dev server |
| `npm run build` | Rollup production build |
| `npm test` | Lint + unit tests |
| `npm run build-docs` | Generate JSDoc + LLM-friendly docs |

### 2.5 Browser Requirements

three.js requires **WebGL 2** at minimum (WebGL 1 support was removed in r163).

| Feature | Browser support |
|---|---|
| WebGL 2 | Chrome 56+, Firefox 51+, Safari 15+, Edge 79+ |
| WebGPU | Chrome 113+, Firefox (behind flag), Safari (experimental) |

---

## 3. Configuration

three.js has no configuration files. All setup is programmatic through constructor parameters and object properties.

### 3.1 WebGLRenderer Options

```javascript
const renderer = new THREE.WebGLRenderer({
  canvas: document.getElementById('myCanvas'),  // existing canvas element
  antialias: true,           // smooth edges (default: false)
  alpha: true,               // transparent background (default: false)
  premultipliedAlpha: true,  // (default: true)
  preserveDrawingBuffer: false, // set true for screenshots (default: false)
  powerPreference: 'high-performance', // GPU hint: 'default', 'high-performance', 'low-power'
  stencil: true,             // stencil buffer (default: true)
  depth: true,               // depth buffer (default: true)
  logarithmicDepthBuffer: false, // reduces z-fighting for large scenes
});
```

After construction, configure rendering behavior:

```javascript
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);

// Tone mapping for HDR content
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;

// Color management
renderer.outputColorSpace = THREE.SRGBColorSpace;

// Shadows
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
```

### 3.2 WebGPURenderer Setup

```javascript
import * as THREE from 'three/webgpu';

const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);

// WebGPURenderer requires explicit initialization
await renderer.init();

renderer.setAnimationLoop(animate);
```

> [!IMPORTANT]
> `WebGPURenderer` requires calling `await renderer.init()` before the first render. Calling `.render()` before `.init()` throws: `"Renderer: .render() called before the backend is initialized. Use .init() first."`

### 3.3 Scene Defaults

```javascript
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x222222);      // solid color
// OR
scene.background = cubeTexture;                     // skybox (CubeTexture)
// OR
scene.background = equirectTexture;                 // equirectangular HDR

scene.fog = new THREE.Fog(0xcccccc, 10, 100);      // linear fog: color, near, far
// OR
scene.fog = new THREE.FogExp2(0xcccccc, 0.02);     // exponential fog: color, density

scene.environment = pmremTexture;                    // IBL for all PBR materials
```

---

## 4. Core Usage

### 4.1 Creating Scenes and Adding Objects

#### The Scene Graph

`Scene` extends `Object3D`. Every object added to the scene (or to a child of the scene) becomes part of a hierarchical tree. Transforms cascade: a child inherits its parent's position, rotation, and scale.

```javascript
const scene = new THREE.Scene();

// Group objects for collective transforms
const carGroup = new THREE.Group();
carGroup.position.set(0, 0.5, 0);
scene.add(carGroup);

const body = new THREE.Mesh(
  new THREE.BoxGeometry(2, 0.5, 1),
  new THREE.MeshStandardMaterial({ color: 0x2244aa })
);
carGroup.add(body);

const wheel = new THREE.Mesh(
  new THREE.CylinderGeometry(0.2, 0.2, 0.1, 16),
  new THREE.MeshStandardMaterial({ color: 0x111111 })
);
wheel.position.set(-0.6, -0.3, 0.5);
wheel.rotation.z = Math.PI / 2;
carGroup.add(wheel);

// Rotating carGroup rotates both body and wheel together
carGroup.rotation.y = Math.PI / 4;
```

#### Object3D Properties

Every scene object inherits these from `Object3D`:

| Property | Type | Description |
|----------|------|-------------|
| `position` | `Vector3` | Local position |
| `rotation` | `Euler` | Local rotation in radians (order: `'XYZ'` default) |
| `quaternion` | `Quaternion` | Rotation as quaternion (synced with `rotation`) |
| `scale` | `Vector3` | Local scale |
| `visible` | `boolean` | Whether to render this object and its children |
| `name` | `string` | Human-readable name for lookups |
| `userData` | `object` | Store custom data without subclassing |
| `layers` | `Layers` | Bitmask layers for selective rendering/raycasting |
| `castShadow` | `boolean` | Whether this object casts shadows |
| `receiveShadow` | `boolean` | Whether this object receives shadows |
| `frustumCulled` | `boolean` | Whether to skip rendering when outside camera frustum |
| `renderOrder` | `number` | Override rendering order |

Key methods:

```javascript
object.add(child);                     // add child to scene graph
object.remove(child);                  // remove child
object.getObjectByName('wheel');       // find descendant by name
object.traverse(callback);            // walk all descendants
object.lookAt(x, y, z);               // orient toward a point
object.localToWorld(vec3);             // convert local → world coords
object.worldToLocal(vec3);             // convert world → local coords
object.getWorldPosition(targetVec3);   // get world-space position
object.getWorldQuaternion(targetQuat); // get world-space rotation
```

> [!NOTE]
> `lookAt()` uses the object's `up` property (default `Vector3(0, 1, 0)`) to determine which direction is "up." For cameras, this usually works as expected. For meshes, you may need to adjust `up` if the model's local orientation differs.

### 4.2 Geometries

Geometries define the shape of 3D objects as vertices, normals, and UVs stored in `BufferAttribute` arrays. All built-in geometries extend `BufferGeometry`.

#### Primitive Geometries

```javascript
// Box: width, height, depth, widthSegments, heightSegments, depthSegments
new THREE.BoxGeometry(1, 1, 1, 1, 1, 1);

// Sphere: radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength
new THREE.SphereGeometry(1, 32, 16);

// Plane: width, height, widthSegments, heightSegments
new THREE.PlaneGeometry(10, 10, 1, 1);

// Circle: radius, segments, thetaStart, thetaLength
new THREE.CircleGeometry(1, 32);

// Cylinder: radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded
new THREE.CylinderGeometry(0.5, 0.5, 2, 32);

// Cone: radius, height, radialSegments, heightSegments, openEnded
new THREE.ConeGeometry(0.5, 2, 32);

// Torus: radius, tube, radialSegments, tubularSegments, arc
new THREE.TorusGeometry(1, 0.3, 16, 48);

// TorusKnot: radius, tube, tubularSegments, radialSegments, p, q
new THREE.TorusKnotGeometry(1, 0.3, 100, 16);

// Capsule: radius, length, capSegments, radialSegments
new THREE.CapsuleGeometry(0.5, 1, 10, 20);

// Ring: innerRadius, outerRadius, thetaSegments, phiSegments
new THREE.RingGeometry(0.5, 1, 32);

// Platonic solids: radius, detail
new THREE.DodecahedronGeometry(1, 0);
new THREE.IcosahedronGeometry(1, 0);
new THREE.OctahedronGeometry(1, 0);
new THREE.TetrahedronGeometry(1, 0);
```

#### Extruded and Lathed Geometries

```javascript
// Extrude a 2D shape into 3D
const shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.lineTo(1, 0);
shape.lineTo(1, 1);
shape.lineTo(0, 1);
shape.lineTo(0, 0);

const extrudeGeometry = new THREE.ExtrudeGeometry(shape, {
  depth: 0.5,
  bevelEnabled: true,
  bevelThickness: 0.1,
  bevelSize: 0.1,
  bevelSegments: 3,
});

// Lathe: revolve a 2D profile around the Y axis
const points = [];
for (let i = 0; i < 10; i++) {
  points.push(new THREE.Vector2(Math.sin(i * 0.2) * 0.5 + 0.5, i * 0.2));
}
const latheGeometry = new THREE.LatheGeometry(points, 32);

// Tube: extrude along a 3D curve
const curve = new THREE.CatmullRomCurve3([
  new THREE.Vector3(-1, 0, 0),
  new THREE.Vector3(0, 1, 0),
  new THREE.Vector3(1, 0, 0),
]);
const tubeGeometry = new THREE.TubeGeometry(curve, 64, 0.1, 8, false);
```

#### Custom BufferGeometry

```javascript
const geometry = new THREE.BufferGeometry();

const vertices = new Float32Array([
  -1, -1, 0,
   1, -1, 0,
   0,  1, 0,
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

// Optional: add normals, UVs, colors
const normals = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1]);
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));

// Indexed geometry (shared vertices)
const indices = new Uint16Array([0, 1, 2]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

geometry.computeBoundingSphere();
```

> [!WARNING]
> `BufferAttribute` requires typed arrays (`Float32Array`, `Uint16Array`, etc.). Passing a regular JavaScript array triggers: `"THREE.BufferAttribute: array should be a Typed Array."`

#### Wireframe and Edge Views

```javascript
// Show wireframe of any geometry
const wireframe = new THREE.WireframeGeometry(boxGeometry);
const line = new THREE.LineSegments(wireframe);
scene.add(line);

// Show only hard edges (angle threshold)
const edges = new THREE.EdgesGeometry(boxGeometry, 15); // 15-degree threshold
const edgeLine = new THREE.LineSegments(
  edges,
  new THREE.LineBasicMaterial({ color: 0x000000 })
);
scene.add(edgeLine);
```

### 4.3 Materials

Materials define how surfaces appear. All materials extend the `Material` base class.

#### Material Comparison

| Material | Lighting | Use case | Performance |
|----------|----------|----------|-------------|
| `MeshBasicMaterial` | No | Unlit/flat color, wireframes, debugging | Fastest |
| `MeshLambertMaterial` | Diffuse only | Matte surfaces, low-poly styles | Fast |
| `MeshPhongMaterial` | Diffuse + specular | Shiny non-metallic surfaces | Medium |
| `MeshStandardMaterial` | PBR | Realistic surfaces (recommended default) | Medium-high |
| `MeshPhysicalMaterial` | Extended PBR | Glass, car paint, fabric, skin | Highest |
| `MeshToonMaterial` | Cel-shaded | Cartoon/stylized rendering | Medium |
| `MeshNormalMaterial` | Normal-colored | Debugging normals | Fast |
| `MeshDepthMaterial` | Depth-colored | Depth visualization | Fast |
| `MeshMatcapMaterial` | Baked matcap | Quick stylized look, no lights needed | Fast |

#### PBR Materials (MeshStandardMaterial)

```javascript
const material = new THREE.MeshStandardMaterial({
  color: 0xffffff,          // base color (albedo)
  metalness: 0.0,           // 0 = dielectric, 1 = metal
  roughness: 0.5,           // 0 = mirror, 1 = fully rough
  map: colorTexture,        // albedo texture
  normalMap: normalTexture,  // surface detail via normals
  roughnessMap: roughTex,    // per-pixel roughness
  metalnessMap: metalTex,    // per-pixel metalness
  aoMap: aoTexture,          // ambient occlusion
  aoMapIntensity: 1.0,
  emissive: 0x000000,        // self-illumination color
  emissiveMap: emissiveTex,
  emissiveIntensity: 1.0,
  envMap: envTexture,         // environment reflection (or use scene.environment)
  envMapIntensity: 1.0,
  displacementMap: dispTex,   // vertex displacement
  displacementScale: 0.1,
  alphaMap: alphaTex,         // per-pixel opacity
  transparent: true,
  opacity: 1.0,
  side: THREE.DoubleSide,     // FrontSide, BackSide, DoubleSide
  flatShading: false,
  wireframe: false,
});
```

#### MeshPhysicalMaterial (Advanced PBR)

Extends `MeshStandardMaterial` with additional properties:

```javascript
const glassMaterial = new THREE.MeshPhysicalMaterial({
  color: 0xffffff,
  metalness: 0,
  roughness: 0,
  transmission: 1.0,          // 0 = opaque, 1 = fully transparent glass
  thickness: 0.5,             // thickness for refraction
  ior: 1.5,                   // index of refraction
  clearcoat: 0.0,             // clear lacquer layer (car paint)
  clearcoatRoughness: 0.0,
  sheen: 0.0,                 // fabric-like sheen
  sheenRoughness: 0.5,
  sheenColor: new THREE.Color(0xffffff),
  iridescence: 0.0,           // thin-film iridescence (soap bubbles)
  iridescenceIOR: 1.3,
});
```

#### Line and Point Materials

```javascript
// Lines
new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 1 });
new THREE.LineDashedMaterial({ color: 0xff0000, dashSize: 0.3, gapSize: 0.1 });

// Points
new THREE.PointsMaterial({ color: 0x00ff00, size: 0.1, sizeAttenuation: true });

// Sprites (always face camera)
new THREE.SpriteMaterial({ map: spriteTexture, color: 0xffffff });
```

#### Common Material Properties

All materials share these properties:

```javascript
material.transparent = true;   // enable alpha blending
material.opacity = 0.5;        // global opacity (requires transparent: true)
material.side = THREE.DoubleSide; // render both faces
material.depthWrite = true;    // write to depth buffer
material.depthTest = true;     // test against depth buffer
material.visible = true;       // skip rendering entirely
material.blending = THREE.NormalBlending; // blending mode
material.needsUpdate = true;   // force shader recompilation after changing defines
```

#### ShaderMaterial (Custom Shaders)

```javascript
const material = new THREE.ShaderMaterial({
  uniforms: {
    uTime: { value: 0 },
    uColor: { value: new THREE.Color(0xff0000) },
    uTexture: { value: texture },
  },
  vertexShader: `
    uniform float uTime;
    varying vec2 vUv;
    void main() {
      vUv = uv;
      vec3 pos = position;
      pos.z += sin(pos.x * 5.0 + uTime) * 0.1;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
  `,
  fragmentShader: `
    uniform vec3 uColor;
    uniform sampler2D uTexture;
    varying vec2 vUv;
    void main() {
      vec4 texColor = texture2D(uTexture, vUv);
      gl_FragColor = vec4(uColor * texColor.rgb, 1.0);
    }
  `,
  transparent: false,
  side: THREE.DoubleSide,
});

// Update in animation loop
material.uniforms.uTime.value = elapsedTime;
```

### 4.4 Textures

#### Loading Textures

```javascript
const textureLoader = new THREE.TextureLoader();

// Basic loading
const texture = textureLoader.load('/textures/wood.jpg');

// With callbacks
const texture = textureLoader.load(
  '/textures/wood.jpg',
  (texture) => { console.log('Loaded'); },       // onLoad
  (xhr) => { console.log(xhr.loaded / xhr.total * 100 + '%'); }, // onProgress
  (err) => { console.error('Error:', err); }      // onError
);
```

#### Texture Properties

```javascript
texture.wrapS = THREE.RepeatWrapping;    // horizontal: ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping
texture.wrapT = THREE.RepeatWrapping;    // vertical
texture.repeat.set(4, 4);               // tile 4x4
texture.offset.set(0.5, 0.5);           // UV offset
texture.rotation = Math.PI / 4;         // rotate UVs
texture.center.set(0.5, 0.5);           // rotation center

texture.magFilter = THREE.LinearFilter;  // magnification filter
texture.minFilter = THREE.LinearMipmapLinearFilter; // minification filter
texture.generateMipmaps = true;

texture.colorSpace = THREE.SRGBColorSpace; // for color maps (albedo, emissive)
// Leave as LinearSRGBColorSpace for data maps (normal, roughness, metalness, ao)

texture.flipY = true;                    // flip vertically (default true for image loads)
```

#### Cube Textures (Skybox / Environment)

```javascript
const cubeLoader = new THREE.CubeTextureLoader();
const envMap = cubeLoader.load([
  'px.jpg', 'nx.jpg',  // positive-x, negative-x
  'py.jpg', 'ny.jpg',  // positive-y, negative-y
  'pz.jpg', 'nz.jpg',  // positive-z, negative-z
]);

scene.background = envMap;
scene.environment = envMap;  // applies as IBL to all PBR materials
```

#### LoadingManager

Track loading progress across multiple assets:

```javascript
const manager = new THREE.LoadingManager();
manager.onStart = (url, loaded, total) => {
  console.log(`Started loading: ${url}. ${loaded}/${total} files.`);
};
manager.onProgress = (url, loaded, total) => {
  console.log(`Loading: ${loaded}/${total}`);
};
manager.onLoad = () => {
  console.log('All assets loaded');
};
manager.onError = (url) => {
  console.error('Error loading:', url);
};

const textureLoader = new THREE.TextureLoader(manager);
const gltfLoader = new GLTFLoader(manager);
// All loaders sharing this manager contribute to progress
```

### 4.5 Lighting

Without lights, only `MeshBasicMaterial`, `MeshNormalMaterial`, and `MeshMatcapMaterial` are visible. All other materials require at least one light.

#### Light Types

```javascript
// Ambient: uniform illumination from all directions (no shadows)
const ambient = new THREE.AmbientLight(0x404040, 1.0); // color, intensity
scene.add(ambient);

// Hemisphere: sky color from above, ground color from below (no shadows)
const hemi = new THREE.HemisphereLight(0x87ceeb, 0x362907, 1.0);
scene.add(hemi);

// Directional: parallel rays (sun-like, casts shadows)
const dir = new THREE.DirectionalLight(0xffffff, 1.5);
dir.position.set(5, 10, 7);
scene.add(dir);

// Point: emits in all directions from a position (casts shadows)
const point = new THREE.PointLight(0xff9900, 2, 50, 2); // color, intensity, distance, decay
point.position.set(0, 3, 0);
scene.add(point);

// Spot: cone-shaped (casts shadows)
const spot = new THREE.SpotLight(0xffffff, 2);
spot.position.set(0, 10, 0);
spot.angle = Math.PI / 6;    // cone angle
spot.penumbra = 0.3;          // soft edge (0-1)
spot.decay = 2;
spot.distance = 50;
spot.target.position.set(0, 0, 0); // aim point
scene.add(spot);
scene.add(spot.target);

// RectArea: rectangular light (area light, no shadows, PhysicalMaterial only)
const rect = new THREE.RectAreaLight(0xffffff, 5, 4, 2); // color, intensity, width, height
rect.position.set(0, 5, 0);
rect.lookAt(0, 0, 0);
scene.add(rect);
```

#### Shadow Setup

Shadows require configuration on the renderer, the light, and each object:

```javascript
// 1. Enable on renderer
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

// 2. Enable on light
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;   // resolution
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.near = 0.5;
dirLight.shadow.camera.far = 50;
// For DirectionalLight, shadow camera is orthographic:
dirLight.shadow.camera.left = -10;
dirLight.shadow.camera.right = 10;
dirLight.shadow.camera.top = 10;
dirLight.shadow.camera.bottom = -10;
dirLight.shadow.bias = -0.001;          // reduce shadow acne

// 3. Enable on objects
mesh.castShadow = true;
floor.receiveShadow = true;
```

> [!TIP]
> Use `CameraHelper` to visualize a shadow camera's frustum and debug shadow coverage: `scene.add(new THREE.CameraHelper(dirLight.shadow.camera));`

### 4.6 Cameras

#### PerspectiveCamera

```javascript
const camera = new THREE.PerspectiveCamera(
  75,    // fov: vertical field of view in degrees
  window.innerWidth / window.innerHeight, // aspect ratio
  0.1,   // near plane
  1000   // far plane
);
camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
```

#### OrthographicCamera

```javascript
const frustumSize = 10;
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.OrthographicCamera(
  -frustumSize * aspect / 2,  // left
   frustumSize * aspect / 2,  // right
   frustumSize / 2,           // top
  -frustumSize / 2,           // bottom
  0.1,                        // near
  1000                        // far
);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
```

#### CubeCamera (for dynamic reflections)

```javascript
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
const cubeCamera = new THREE.CubeCamera(0.1, 100, cubeRenderTarget);
scene.add(cubeCamera);

const reflectiveMaterial = new THREE.MeshStandardMaterial({
  envMap: cubeRenderTarget.texture,
  metalness: 1,
  roughness: 0,
});

// In animation loop: update before main render
cubeCamera.position.copy(reflectiveMesh.position);
cubeCamera.update(renderer, scene);
renderer.render(scene, camera);
```

### 4.7 Animation

#### AnimationMixer

three.js uses a mixer-based animation system. Models loaded from GLTF/FBX files typically include `AnimationClip` data.

```javascript
let mixer;

const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
  const model = gltf.scene;
  scene.add(model);

  mixer = new THREE.AnimationMixer(model);

  // Play the first animation clip
  const clip = gltf.animations[0];
  const action = mixer.clipAction(clip);
  action.play();

  // Play all animations
  gltf.animations.forEach((clip) => {
    mixer.clipAction(clip).play();
  });
});

// In animation loop:
const clock = new THREE.Clock();
function animate() {
  const delta = clock.getDelta();
  if (mixer) mixer.update(delta);
  renderer.render(scene, camera);
}
```

#### AnimationAction Controls

```javascript
const action = mixer.clipAction(clip);

// Playback control
action.play();
action.stop();
action.reset();
action.paused = true;

// Speed and direction
action.timeScale = 1.0;      // 1 = normal, 2 = double speed, -1 = reverse
action.setEffectiveTimeScale(0.5);

// Weight (for blending)
action.setEffectiveWeight(1.0);

// Loop modes
action.loop = THREE.LoopRepeat;     // default: loops forever
action.loop = THREE.LoopOnce;       // play once
action.loop = THREE.LoopPingPong;   // forward then backward
action.clampWhenFinished = true;     // hold last frame (with LoopOnce)
action.repetitions = 3;             // play 3 times

// Crossfade between animations
const walkAction = mixer.clipAction(walkClip);
const runAction = mixer.clipAction(runClip);
walkAction.play();
walkAction.crossFadeTo(runAction, 0.5, true); // target, duration, warp
runAction.play();
```

#### AnimationClip Events

```javascript
mixer.addEventListener('finished', (event) => {
  console.log('Animation finished:', event.action.getClip().name);
});

mixer.addEventListener('loop', (event) => {
  console.log('Animation looped:', event.action.getClip().name);
});
```

#### Programmatic Animation

For simple animations without clips, manipulate properties directly:

```javascript
const clock = new THREE.Clock();

function animate() {
  const elapsed = clock.getElapsedTime();

  // Orbit
  mesh.position.x = Math.cos(elapsed) * 3;
  mesh.position.z = Math.sin(elapsed) * 3;

  // Bob up and down
  mesh.position.y = Math.sin(elapsed * 2) * 0.5 + 1;

  // Continuous rotation
  mesh.rotation.y += 0.01;

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
```

### 4.8 Loading 3D Models

#### GLTFLoader (Recommended Format)

glTF (.glb / .gltf) is the recommended format for three.js.

```javascript
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.7/');

const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);

gltfLoader.load(
  'model.glb',
  (gltf) => {
    const model = gltf.scene;
    scene.add(model);

    // Access animations
    console.log('Animations:', gltf.animations);

    // Access cameras embedded in the file
    console.log('Cameras:', gltf.cameras);
  },
  (xhr) => {
    console.log((xhr.loaded / xhr.total * 100) + '% loaded');
  },
  (error) => {
    console.error('Error loading model:', error);
  }
);
```

#### Other Loaders

```javascript
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
import { STLLoader } from 'three/addons/loaders/STLLoader.js';
import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';
import { ColladaLoader } from 'three/addons/loaders/ColladaLoader.js';

// OBJ
const objLoader = new OBJLoader();
objLoader.load('model.obj', (obj) => scene.add(obj));

// FBX
const fbxLoader = new FBXLoader();
fbxLoader.load('model.fbx', (fbx) => {
  scene.add(fbx);
  // FBX animations
  if (fbx.animations.length > 0) {
    const mixer = new THREE.AnimationMixer(fbx);
    mixer.clipAction(fbx.animations[0]).play();
  }
});

// STL (3D printing format, geometry only)
const stlLoader = new STLLoader();
stlLoader.load('model.stl', (geometry) => {
  const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial());
  scene.add(mesh);
});
```

#### KTX2Loader (Compressed Textures)

```javascript
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';

const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath('https://unpkg.com/three@0.184.0/examples/jsm/libs/basis/');
ktx2Loader.detectSupport(renderer);

ktx2Loader.load('texture.ktx2', (texture) => {
  material.map = texture;
  material.needsUpdate = true;
});
```

### 4.9 Audio

three.js integrates the Web Audio API for 3D spatial audio.

```javascript
// Listener attached to the camera
const listener = new THREE.AudioListener();
camera.add(listener);

// Non-positional (ambient) audio
const bgMusic = new THREE.Audio(listener);
const audioLoader = new THREE.AudioLoader();
audioLoader.load('music.mp3', (buffer) => {
  bgMusic.setBuffer(buffer);
  bgMusic.setLoop(true);
  bgMusic.setVolume(0.5);
  bgMusic.play();
});

// Positional (3D spatialized) audio
const sound = new THREE.PositionalAudio(listener);
audioLoader.load('engine.mp3', (buffer) => {
  sound.setBuffer(buffer);
  sound.setRefDistance(1);
  sound.setLoop(true);
  sound.play();
});
carMesh.add(sound); // attach to a mesh — sound follows the mesh in 3D space

// Audio analysis (frequency data)
const analyser = new THREE.AudioAnalyser(bgMusic, 256); // fftSize
// In animation loop:
const data = analyser.getAverageFrequency(); // returns 0-255
```

> [!NOTE]
> Browsers require a user gesture (click, tap) before audio can play. Start audio playback inside a click event handler, not on page load.

### 4.10 Interaction and Raycasting

#### Raycaster

`Raycaster` projects a ray from one point in a direction and reports which objects it intersects.

```javascript
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

window.addEventListener('click', (event) => {
  // Convert mouse position to normalized device coordinates (-1 to +1)
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;

  raycaster.setFromCamera(pointer, camera);

  const intersects = raycaster.intersectObjects(scene.children, true); // recursive

  if (intersects.length > 0) {
    const hit = intersects[0];
    console.log('Hit object:', hit.object.name);
    console.log('Hit point:', hit.point);          // Vector3 in world coords
    console.log('Hit face normal:', hit.face.normal); // face normal
    console.log('Distance:', hit.distance);

    // Highlight the clicked object
    hit.object.material.color.set(0xff0000);
  }
});
```

#### Using Layers for Selective Raycasting

```javascript
// Set objects to specific layers
clickableObject.layers.set(1);      // only on layer 1
decorObject.layers.set(2);          // only on layer 2

// Raycaster respects its own layers setting
raycaster.layers.set(1);            // only test against layer-1 objects
```

#### OrbitControls

The most common way to let users navigate a 3D scene:

```javascript
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;       // smooth camera movement
controls.dampingFactor = 0.05;
controls.enableZoom = true;
controls.enablePan = true;
controls.enableRotate = true;
controls.minDistance = 2;            // min zoom
controls.maxDistance = 50;           // max zoom
controls.maxPolarAngle = Math.PI / 2; // prevent going below ground

// In animation loop:
controls.update();
```

Other available controls:

| Control | Behavior |
|---------|----------|
| `OrbitControls` | Orbit around a target point |
| `MapControls` | Pan-centric (like a map editor) |
| `TrackballControls` | Unconstrained rotation (no gimbal lock) |
| `FlyControls` | Fly through the scene (6DOF) |
| `FirstPersonControls` | First-person camera |
| `PointerLockControls` | FPS-style mouse lock |
| `ArcballControls` | Arcball rotation |
| `DragControls` | Drag objects with the mouse |
| `TransformControls` | Move/rotate/scale gizmos |

---

## 5. Common Tasks

### 5.1 Responsive Resize

Handle window resize to prevent stretching:

```javascript
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
```

For `OrthographicCamera`:

```javascript
window.addEventListener('resize', () => {
  const aspect = window.innerWidth / window.innerHeight;
  camera.left = -frustumSize * aspect / 2;
  camera.right = frustumSize * aspect / 2;
  camera.top = frustumSize / 2;
  camera.bottom = -frustumSize / 2;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
```

### 5.2 Loading and Displaying a GLTF Model

Complete example with lighting, controls, and animation:

```javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xdddddd);

const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 2, 5);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
dirLight.position.set(5, 10, 5);
dirLight.castShadow = true;
dirLight.shadow.mapSize.set(2048, 2048);
scene.add(dirLight);

// Floor
const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(20, 20),
  new THREE.MeshStandardMaterial({ color: 0x999999 })
);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);

// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// Load model
let mixer;
const clock = new THREE.Clock();

const loader = new GLTFLoader();
loader.load('character.glb', (gltf) => {
  const model = gltf.scene;
  model.traverse((child) => {
    if (child.isMesh) {
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
  scene.add(model);

  // Play animations if present
  if (gltf.animations.length > 0) {
    mixer = new THREE.AnimationMixer(model);
    gltf.animations.forEach((clip) => {
      mixer.clipAction(clip).play();
    });
  }
});

// Render loop
function animate() {
  const delta = clock.getDelta();
  if (mixer) mixer.update(delta);
  controls.update();
  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

// Resize handling
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
```

### 5.3 Shadows

Complete shadow setup with multiple light types:

```javascript
// Renderer
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

// Directional light shadows
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
dirLight.position.set(10, 20, 10);
dirLight.castShadow = true;
dirLight.shadow.mapSize.set(2048, 2048);
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 50;
dirLight.shadow.camera.left = -15;
dirLight.shadow.camera.right = 15;
dirLight.shadow.camera.top = 15;
dirLight.shadow.camera.bottom = -15;
dirLight.shadow.bias = -0.0001;
scene.add(dirLight);

// Spot light shadows
const spotLight = new THREE.SpotLight(0xffa500, 5);
spotLight.position.set(-5, 10, 0);
spotLight.angle = Math.PI / 6;
spotLight.penumbra = 0.3;
spotLight.castShadow = true;
spotLight.shadow.mapSize.set(1024, 1024);
scene.add(spotLight);
scene.add(spotLight.target);

// Objects: cast and receive
mesh.castShadow = true;
ground.receiveShadow = true;
```

### 5.4 Environment Maps and IBL

Use HDR environment maps for realistic lighting and reflections:

```javascript
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

const pmremGenerator = new THREE.PMREMGenerator(renderer);

new RGBELoader().load('environment.hdr', (hdrTexture) => {
  const envMap = pmremGenerator.fromEquirectangular(hdrTexture).texture;

  scene.background = envMap;       // visible background
  scene.environment = envMap;      // IBL for all PBR materials

  hdrTexture.dispose();
  pmremGenerator.dispose();
});
```

### 5.5 Instanced Rendering

Render many copies of the same geometry efficiently with `InstancedMesh`:

```javascript
const count = 10000;
const geometry = new THREE.SphereGeometry(0.1, 16, 8);
const material = new THREE.MeshStandardMaterial({ color: 0xff8800 });
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);

const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
  dummy.position.set(
    (Math.random() - 0.5) * 20,
    (Math.random() - 0.5) * 20,
    (Math.random() - 0.5) * 20
  );
  dummy.rotation.set(
    Math.random() * Math.PI,
    Math.random() * Math.PI,
    0
  );
  dummy.scale.setScalar(0.5 + Math.random());
  dummy.updateMatrix();
  instancedMesh.setMatrixAt(i, dummy.matrix);
}

instancedMesh.instanceMatrix.needsUpdate = true;
scene.add(instancedMesh);
```

For per-instance colors:

```javascript
const color = new THREE.Color();
for (let i = 0; i < count; i++) {
  color.setHSL(Math.random(), 0.8, 0.5);
  instancedMesh.setColorAt(i, color);
}
instancedMesh.instanceColor.needsUpdate = true;
```

### 5.6 BatchedMesh (Dynamic Batching)

`BatchedMesh` allows combining different geometries into a single draw call with dynamic updates:

```javascript
const batchedMesh = new THREE.BatchedMesh(
  100,      // max geometry count
  5000,     // max vertex count
  10000,    // max index count
  material
);

const boxGeoId = batchedMesh.addGeometry(new THREE.BoxGeometry(1, 1, 1));
const sphereGeoId = batchedMesh.addGeometry(new THREE.SphereGeometry(0.5, 16, 8));

// Add instances of those geometries
const inst1 = batchedMesh.addInstance(boxGeoId);
const inst2 = batchedMesh.addInstance(sphereGeoId);

const matrix = new THREE.Matrix4();
matrix.makeTranslation(2, 0, 0);
batchedMesh.setMatrixAt(inst1, matrix);

matrix.makeTranslation(-2, 0, 0);
batchedMesh.setMatrixAt(inst2, matrix);

scene.add(batchedMesh);
```

### 5.7 Post-Processing

Apply screen-space effects after rendering:

```javascript
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';

const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));

const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  1.5,   // strength
  0.4,   // radius
  0.85   // threshold
);
composer.addPass(bloomPass);

// Replace renderer.render() with:
function animate() {
  composer.render();
}
renderer.setAnimationLoop(animate);
```

The `PostProcessing` class (available with `WebGPURenderer`) provides a node-based alternative:

```javascript
import * as THREE from 'three/webgpu';

const postProcessing = new THREE.PostProcessing(renderer);
// Configure passes via the node system
```

### 5.8 WebXR (VR/AR)

```javascript
import { VRButton } from 'three/addons/webxr/VRButton.js';

renderer.xr.enabled = true;
document.body.appendChild(VRButton.createButton(renderer));

// Use setAnimationLoop — required for XR
renderer.setAnimationLoop((timestamp, frame) => {
  renderer.render(scene, camera);
});
```

> [!CAUTION]
> WebXR is currently supported only with `WebGLRenderer`. Using `WebGPURenderer` with XR triggers: `"THREE.XRManager: XR is currently not supported with a WebGPU backend. Use WebGL."`

For AR:

```javascript
import { ARButton } from 'three/addons/webxr/ARButton.js';

renderer.xr.enabled = true;
document.body.appendChild(ARButton.createButton(renderer));
```

### 5.9 Exporting Scenes

```javascript
import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';

const exporter = new GLTFExporter();

// Export as JSON (.gltf)
exporter.parse(
  scene,
  (result) => {
    const json = JSON.stringify(result, null, 2);
    downloadFile(json, 'scene.gltf', 'application/json');
  },
  (error) => { console.error(error); },
  { binary: false }
);

// Export as binary (.glb)
exporter.parse(
  scene,
  (result) => {
    downloadFile(result, 'scene.glb', 'application/octet-stream');
  },
  (error) => { console.error(error); },
  { binary: true }
);
```

Other exporters: `OBJExporter`, `STLExporter`, `PLYExporter`, `USDZExporter`.

### 5.10 Particles

Create particle systems with `Points`:

```javascript
const particleCount = 5000;
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);

for (let i = 0; i < particleCount; i++) {
  positions[i * 3] = (Math.random() - 0.5) * 20;
  positions[i * 3 + 1] = (Math.random() - 0.5) * 20;
  positions[i * 3 + 2] = (Math.random() - 0.5) * 20;

  colors[i * 3] = Math.random();
  colors[i * 3 + 1] = Math.random();
  colors[i * 3 + 2] = Math.random();
}

const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

const material = new THREE.PointsMaterial({
  size: 0.1,
  sizeAttenuation: true,
  vertexColors: true,         // use per-vertex colors
  transparent: true,
  alphaMap: particleTexture,
  depthWrite: false,          // prevent particles occluding each other
});

const particles = new THREE.Points(geometry, material);
scene.add(particles);
```

### 5.11 Text Rendering

three.js does not include a built-in text geometry in core, but provides solutions via addons:

```javascript
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';

const fontLoader = new FontLoader();
fontLoader.load('fonts/helvetiker_regular.typeface.json', (font) => {
  const geometry = new TextGeometry('Hello three.js!', {
    font: font,
    size: 1,
    depth: 0.2,           // formerly called "height" in older versions
    curveSegments: 12,
    bevelEnabled: true,
    bevelThickness: 0.03,
    bevelSize: 0.02,
    bevelSegments: 5,
  });
  geometry.computeBoundingBox();
  geometry.center();

  const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: 0x2244aa }));
  scene.add(mesh);
});
```

### 5.12 Helpers for Debugging

```javascript
// Axes: red = X, green = Y, blue = Z
scene.add(new THREE.AxesHelper(5)); // length 5

// Grid on the XZ plane
scene.add(new THREE.GridHelper(10, 10)); // size, divisions

// Polar grid
scene.add(new THREE.PolarGridHelper(5, 8, 4, 64));

// Arrow
const arrow = new THREE.ArrowHelper(
  new THREE.Vector3(0, 1, 0),   // direction
  new THREE.Vector3(0, 0, 0),   // origin
  2,                             // length
  0xff0000                       // color
);
scene.add(arrow);

// Bounding box visualization
const boxHelper = new THREE.BoxHelper(mesh, 0xffff00);
scene.add(boxHelper);

// Light helpers
scene.add(new THREE.DirectionalLightHelper(dirLight, 1));
scene.add(new THREE.PointLightHelper(pointLight, 0.5));
scene.add(new THREE.SpotLightHelper(spotLight));
scene.add(new THREE.HemisphereLightHelper(hemiLight, 1));

// Camera helper (shows frustum)
scene.add(new THREE.CameraHelper(otherCamera));

// Skeleton helper (for skinned meshes)
scene.add(new THREE.SkeletonHelper(skinnedMesh));
```

### 5.13 LOD (Level of Detail)

Swap meshes based on distance from camera for performance:

```javascript
const lod = new THREE.LOD();

lod.addLevel(highDetailMesh, 0);    // show when distance < 10
lod.addLevel(medDetailMesh, 10);    // show when distance 10-50
lod.addLevel(lowDetailMesh, 50);    // show when distance > 50

scene.add(lod);

// Must call update each frame
function animate() {
  lod.update(camera);
  renderer.render(scene, camera);
}
```

### 5.14 Skinned Meshes and Skeletons

For animated characters loaded from GLTF/FBX, the skeletal structure is set up automatically. For manual creation:

```javascript
const bones = [];
const rootBone = new THREE.Bone();
rootBone.position.y = 0;
bones.push(rootBone);

const childBone = new THREE.Bone();
childBone.position.y = 1;
rootBone.add(childBone);
bones.push(childBone);

const skeleton = new THREE.Skeleton(bones);
const skinnedMesh = new THREE.SkinnedMesh(geometry, material);
skinnedMesh.add(rootBone);
skinnedMesh.bind(skeleton);
scene.add(skinnedMesh);
```

### 5.15 Clipping Planes

Cut away parts of objects to reveal interiors:

```javascript
const clippingPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 1);

const material = new THREE.MeshStandardMaterial({
  color: 0xff0000,
  clippingPlanes: [clippingPlane],
  clipShadows: true,
  side: THREE.DoubleSide,
});

renderer.localClippingEnabled = true;
```

### 5.16 Curves and Paths

three.js provides a complete curve system for paths, extrusions, and camera tracks:

```javascript
// Catmull-Rom spline through points
const curve = new THREE.CatmullRomCurve3([
  new THREE.Vector3(-5, 0, 0),
  new THREE.Vector3(-2, 3, 2),
  new THREE.Vector3(2, -1, -3),
  new THREE.Vector3(5, 0, 0),
]);
curve.closed = false;

// Sample points along the curve
const points = curve.getPoints(50);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0xff0000 }));
scene.add(line);

// Use as camera path
let t = 0;
function animate() {
  t += 0.001;
  if (t > 1) t = 0;
  const point = curve.getPointAt(t);
  const tangent = curve.getTangentAt(t);
  camera.position.copy(point);
  camera.lookAt(point.clone().add(tangent));
  renderer.render(scene, camera);
}
```

Other curve types: `EllipseCurve` / `ArcCurve`, `CubicBezierCurve3`, `QuadraticBezierCurve3`, `LineCurve3`, `SplineCurve`.

### 5.17 Render Targets (Render to Texture)

Render a scene to a texture for effects like portals, monitors, or mirrors:

```javascript
const renderTarget = new THREE.WebGLRenderTarget(512, 512);

const portalCamera = new THREE.PerspectiveCamera(75, 1, 0.1, 100);

function animate() {
  // Render the portal scene to the render target
  renderer.setRenderTarget(renderTarget);
  renderer.render(portalScene, portalCamera);

  // Apply as texture on a mesh
  screenMaterial.map = renderTarget.texture;

  // Render the main scene normally
  renderer.setRenderTarget(null);
  renderer.render(scene, camera);
}
```

### 5.18 Disposing Resources

three.js does not garbage-collect GPU resources automatically. You must dispose of objects when removing them:

```javascript
// Dispose geometry
geometry.dispose();

// Dispose material (and its textures)
material.dispose();
if (material.map) material.map.dispose();
if (material.normalMap) material.normalMap.dispose();
if (material.roughnessMap) material.roughnessMap.dispose();

// Dispose render targets
renderTarget.dispose();

// Remove from scene
scene.remove(mesh);

// Recursive disposal helper
function disposeHierarchy(object) {
  object.traverse((child) => {
    if (child.isMesh) {
      child.geometry.dispose();
      if (Array.isArray(child.material)) {
        child.material.forEach((m) => m.dispose());
      } else {
        child.material.dispose();
      }
    }
  });
}

// Dispose renderer on app shutdown
renderer.dispose();
```

> [!CAUTION]
> Failing to dispose of removed objects causes GPU memory leaks. This is especially critical in single-page applications where scenes are created and destroyed repeatedly without page reloads.

---

## 6. Troubleshooting

### Error: `"Error creating WebGL context."`

**Cause:** The browser cannot create a WebGL 2 context. This happens when:
- The GPU or driver does not support WebGL 2
- Hardware acceleration is disabled in browser settings
- Too many active WebGL contexts (browsers limit to ~8-16 concurrent contexts)
- The GPU process has crashed

**Solution:**
1. Check `chrome://gpu` (or equivalent) for WebGL status
2. Enable hardware acceleration in browser settings
3. Update GPU drivers
4. Dispose unused renderers to free context slots
5. Test with a known working WebGL site (e.g., `get.webgl.org`)

**Prevention:** Call `renderer.dispose()` when destroying three.js instances, especially in SPAs.

### Error: `"Error creating WebGL context with your selected attributes."`

**Cause:** The requested WebGL context attributes (antialias, stencil, depth, alpha, etc.) are not supported together on this GPU.

**Solution:**
1. Remove `antialias: true` as a first test
2. Try disabling `stencil` or `alpha`
3. Test with default attributes: `new THREE.WebGLRenderer()`

**Prevention:** Only request attributes you actually need.

### Error: `"THREE.WebGLRenderer: WebGL 1 is not supported since r163."`

**Cause:** The browser is falling back to WebGL 1, which three.js no longer supports as of r163.

**Solution:**
1. Update the browser to a version that supports WebGL 2
2. On Safari, ensure macOS 12+ / iOS 15+ is used
3. For legacy hardware, use three.js r162 or earlier

**Prevention:** Check WebGL 2 support before initializing:
```javascript
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl2');
if (!gl) {
  alert('WebGL 2 is not available.');
}
```

### Error: `"Renderer: .render() called before the backend is initialized. Use .init() first."`

**Cause:** `WebGPURenderer.render()` was called before the async `init()` completed.

**Solution:**
```javascript
const renderer = new THREE.WebGPURenderer();
await renderer.init();  // must await before first render
renderer.setAnimationLoop(animate);
```

**Prevention:** Always `await renderer.init()` immediately after constructing `WebGPURenderer`.

### Error: `"THREE.XRManager: XR is currently not supported with a WebGPU backend. Use WebGL."`

**Cause:** Attempting to use WebXR features with `WebGPURenderer`.

**Solution:** Use `WebGLRenderer` for all WebXR (VR/AR) applications:
```javascript
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.xr.enabled = true;
```

**Prevention:** Do not use `three/webgpu` import for XR applications.

> [!WARNING]
> WebXR and WebGPU interop is not yet available. This is a known limitation. See GitHub issue [#33404](https://github.com/mrdoob/three.js/issues/33404) for related WebXR tracking.

### Error: `"THREE.BufferAttribute: array should be a Typed Array."`

**Cause:** A regular JavaScript array was passed to `BufferAttribute` instead of a typed array.

**Solution:**
```javascript
// Wrong
const positions = [0, 0, 0, 1, 1, 1];
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

// Correct
const positions = new Float32Array([0, 0, 0, 1, 1, 1]);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
```

**Prevention:** Always use `Float32Array` for positions/normals/UVs, `Uint16Array` or `Uint32Array` for indices.

### Error: `"THREE.KeyframeTrack: track name is undefined"`

**Cause:** A `KeyframeTrack` was created with an undefined or empty `name` parameter.

**Solution:** Ensure every keyframe track has a valid name in the format `objectName.property`:
```javascript
const track = new THREE.NumberKeyframeTrack(
  'cube.position[x]',    // must be a valid, non-empty string
  [0, 1, 2],             // times
  [0, 5, 0]              // values
);
```

**Prevention:** Validate track names when constructing `AnimationClip` objects manually.

### Error: `"PropertyBinding: Cannot parse trackName:"`

**Cause:** An animation track name in a loaded model or manually created clip uses a format the `PropertyBinding` parser cannot interpret.

**Solution:**
1. Check the track name format — it should follow `objectName.property` or `objectName.property[index]`
2. If loading from a file, re-export the model with correct animation target names
3. Use `AnimationUtils.makeClipAdditive()` or manually remap track names

**Prevention:** When creating clips programmatically, follow the naming convention: `meshName.position`, `meshName.quaternion`, `meshName.scale`, `meshName.morphTargetInfluences[0]`.

### Error: `"WARNING: Multiple instances of Three.js being imported."`

**Cause:** Two or more copies of the three.js library are loaded on the page. This typically happens with:
- Mixed CDN and npm imports
- Multiple bundled copies from different packages
- Both `build/three.module.js` and `src/Three.js` imported

**Solution:**
1. Check your bundle for duplicate three.js copies: `npm ls three`
2. Use your bundler's alias/dedupe feature to force a single copy
3. Ensure addons import from the same three.js instance

**Prevention:** Import three.js from exactly one source. Pin the version in `package.json` and use `npm dedupe`.

> [!IMPORTANT]
> Duplicate three.js instances cause subtle, hard-to-debug failures. Materials from one instance silently fail when used with a renderer from another. Type checks like `object.isMesh` return `false` across instances.

### Error: `"DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat"`

**Cause:** A `DepthTexture` was created with an unsupported format (e.g., `THREE.RGBAFormat`).

**Solution:**
```javascript
const depthTexture = new THREE.DepthTexture(width, height);
depthTexture.format = THREE.DepthFormat;  // or THREE.DepthStencilFormat
```

**Prevention:** Only use `DepthFormat` or `DepthStencilFormat` when constructing `DepthTexture`.

### Error: `"WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size."`

**Cause:** The `DepthTexture` attached to a `WebGLRenderTarget` has dimensions that do not match the render target's size.

**Solution:**
```javascript
const width = 1024;
const height = 1024;
const depthTexture = new THREE.DepthTexture(width, height);
const renderTarget = new THREE.WebGLRenderTarget(width, height, {
  depthTexture: depthTexture,
});
```

**Prevention:** Always create the `DepthTexture` with the same width and height as the `WebGLRenderTarget` it is attached to. Update both when resizing.

### Problem: Black Screen (Nothing Renders)

**Cause:** Multiple possible reasons — no lights, camera inside object, wrong camera direction, objects too far away, or material not visible.

**Solution (checklist):**
1. Add an `AmbientLight` to rule out lighting: `scene.add(new THREE.AmbientLight(0xffffff, 1))`
2. Verify camera position is outside objects and `near`/`far` range covers the scene
3. Use `MeshBasicMaterial` temporarily (does not require lights)
4. Add `AxesHelper` to verify orientation: `scene.add(new THREE.AxesHelper(5))`
5. Check `renderer.domElement` is in the DOM and has non-zero dimensions
6. Confirm objects are added to the scene (not just created)
7. Check `material.visible` and `object.visible` are both `true`

**Prevention:** Start with the minimal Quick Start template and add complexity incrementally.

### Problem: Z-Fighting (Flickering Surfaces)

**Cause:** Two surfaces at the same depth produce alternating rendering between frames.

**Solution:**
1. Offset one surface slightly: `mesh.position.z += 0.001`
2. Use `material.polygonOffset`, `material.polygonOffsetFactor`, `material.polygonOffsetUnits`
3. Enable logarithmic depth buffer: `new THREE.WebGLRenderer({ logarithmicDepthBuffer: true })`
4. Narrow the camera's near/far range to improve depth precision

**Prevention:** Avoid placing coplanar surfaces at exactly the same position. Keep `camera.near` as large as practical and `camera.far` as small as practical.

### Problem: Textures Appear Black or Wrong Color

**Cause:** Color space mismatch. By default, loaded textures are in `LinearSRGBColorSpace`, but color/albedo maps should be in `SRGBColorSpace`.

**Solution:**
```javascript
// For color maps (albedo, emissive):
texture.colorSpace = THREE.SRGBColorSpace;

// For data maps (normal, roughness, metalness, ao, displacement):
// Leave as default (LinearSRGBColorSpace) — do NOT set SRGBColorSpace

// Set renderer output color space
renderer.outputColorSpace = THREE.SRGBColorSpace;
```

**Prevention:** Set `colorSpace = THREE.SRGBColorSpace` on every color/albedo/emissive texture immediately after loading.

### Problem: GLTFLoader Fails with Non-ASCII Filenames

GitHub issue [#29963](https://github.com/mrdoob/three.js/issues/29963).

**Cause:** GLTF files referencing textures or buffers with UTF-8 encoded filenames may fail to resolve paths correctly.

**Solution:**
1. Rename referenced files to use ASCII-only characters
2. Re-export the GLTF file with ASCII filenames
3. Use `.glb` format instead (all resources embedded in a single binary)

**Prevention:** Use `.glb` for distribution. If using `.gltf`, ensure all referenced filenames use ASCII characters only.

### Problem: Lighting Incorrect on Negatively Scaled Objects

GitHub issue [#31994](https://github.com/mrdoob/three.js/issues/31994).

**Cause:** When a GLTF model node has negative scaling (e.g., mirroring via `scale.x = -1`), lighting calculations can produce incorrect results because the normal matrix is not properly adjusted.

**Solution:**
1. Avoid negative scaling on the mesh; instead, mirror the geometry data
2. If negative scaling is required, manually flip normals on the affected geometry

**Prevention:** Mirror geometry at export time rather than using negative scale values at runtime.

---

## 7. FAQ

**Q: What is the difference between WebGLRenderer and WebGPURenderer?**

`WebGLRenderer` is the mature, battle-tested renderer that works in all modern browsers. `WebGPURenderer` is the newer renderer built on the WebGPU API, offering better performance for complex scenes and access to compute shaders via the node/TSL system. Use `WebGLRenderer` for production applications that need broad browser support. Use `WebGPURenderer` for experimental work, compute-heavy applications, or when targeting Chrome 113+. Note that WebXR currently only works with `WebGLRenderer`.

**Q: What is TSL (Three Shading Language)?**

TSL is three.js's node-based shader authoring system. Instead of writing raw GLSL, you compose shader logic from JavaScript function calls (`float()`, `vec3()`, `color()`, `texture()`, etc.) that compile to the appropriate shader code for the active backend (WebGL GLSL or WebGPU WGSL). TSL is available via `import { ... } from 'three/tsl'` or included automatically when importing from `three/webgpu`.

**Q: Do I need a build tool to use three.js?**

No. You can use native ES modules with an import map directly in a `<script type="module">` tag (see Section 2.3). For production, a bundler like Vite, Webpack, or Rollup is recommended for tree-shaking, minification, and asset handling.

**Q: How do I handle transparency properly?**

Set `material.transparent = true` and configure `material.opacity` (0-1). For textured transparency, use `material.alphaMap` or `material.alphaTest`. For order-independent transparency on particles, set `material.depthWrite = false`. For complex transparent scenes, you may need to manually set `renderOrder` on objects or use separate render passes.

**Q: What 3D model format should I use?**

glTF/GLB (`.gltf` / `.glb`) is the recommended format. It is compact, well-specified, supports PBR materials, animations, and morph targets, and is loaded via `GLTFLoader`. For Draco-compressed meshes, add `DRACOLoader`. For KTX2 compressed textures, add `KTX2Loader`.

**Q: How do I get the world position of a child object?**

```javascript
const worldPosition = new THREE.Vector3();
childObject.getWorldPosition(worldPosition);
```

This accounts for all parent transforms in the hierarchy. Similarly, use `getWorldQuaternion()` for rotation and `getWorldScale()` for scale.

**Q: Why is my model too big / too small / not visible?**

Models from different 3D applications use different unit scales. After loading, check the model's bounding box and rescale:
```javascript
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
const scale = 2 / maxDim; // fit into a 2-unit box
model.scale.setScalar(scale);
```

**Q: How do I pick objects with the mouse?**

Use `Raycaster` with normalized device coordinates. See Section 4.10 for the complete pattern. For performance with many objects, use `Layers` to limit which objects the raycaster tests against.

**Q: Can I use three.js with React / Vue / Angular?**

Yes. three.js is framework-agnostic. Create the renderer, scene, and camera in a component's lifecycle methods and mount `renderer.domElement` to a container ref. Libraries like `react-three-fiber` (R3F) provide React-specific bindings. Ensure you `dispose()` all resources when the component unmounts.

**Q: How do I reduce draw calls?**

- **Merge static geometries** into a single `BufferGeometry` using `BufferGeometryUtils.mergeGeometries()`
- **Use `InstancedMesh`** for many copies of the same geometry (see Section 5.5)
- **Use `BatchedMesh`** for multiple different geometries in one draw call (see Section 5.6)
- **Share materials** — each unique material typically produces an additional draw call
- **Use texture atlases** to combine multiple textures into one

**Q: How do I render to a specific HTML element instead of the full page?**

Pass your container element or an existing `<canvas>` to the renderer:
```javascript
// Option A: provide a canvas
const canvas = document.getElementById('myCanvas');
const renderer = new THREE.WebGLRenderer({ canvas });

// Option B: append the auto-created canvas to a container
const container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer.domElement);
```

**Q: How do I take a screenshot of the scene?**

Enable `preserveDrawingBuffer` on the renderer, then use `toDataURL()`:
```javascript
const renderer = new THREE.WebGLRenderer({ preserveDrawingBuffer: true });
renderer.render(scene, camera);
const dataUrl = renderer.domElement.toDataURL('image/png');
```

**Q: What is the difference between `Mesh`, `InstancedMesh`, and `BatchedMesh`?**

`Mesh` renders one object with one draw call. `InstancedMesh` renders many copies of the *same* geometry with unique transforms/colors in a *single* draw call. `BatchedMesh` renders instances of *different* geometries in a single draw call with dynamic add/remove support. Use `Mesh` for unique objects, `InstancedMesh` for many identical objects (trees, particles, crowds), and `BatchedMesh` when you need mixed geometries with dynamic updates.

**Q: How do I animate morph targets?**

Morph targets (blend shapes) are stored in the geometry and controlled via `mesh.morphTargetInfluences`:
```javascript
// If loaded from GLTF, morph targets are auto-attached
// Manual control:
mesh.morphTargetInfluences[0] = 0.5; // 0 = base shape, 1 = fully morphed
// Or animate via AnimationMixer — GLTF files often include morph target animations
```

**Q: How do I use three.js with TypeScript?**

three.js ships its own TypeScript type definitions. Install `three` and import normally — types are included:
```typescript
import * as THREE from 'three';
const scene: THREE.Scene = new THREE.Scene();
const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
```

**Q: How do I debug performance issues?**

Use `renderer.info` to check draw calls, triangles, textures, and geometries in memory:
```javascript
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); // geometries in GPU memory
console.log(renderer.info.memory.textures);   // textures in GPU memory
```
Also use the browser's DevTools Performance tab and the three.js Devtools browser extension for scene inspection.

For architecture details, internal subsystem design, deployment, and advanced configuration, see the [three.js Admin Guide](mrdoob__three.js__AdminGuide.md).

---

_Generated May 2026 from mrdoob/three.js at 477da61. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
