Three.js 3D Development

高级 Advanced 参考型 Reference claude-code
12 min read · 580 lines

Build high-performance 3D web applications and visualizations with Three.js

Three.js 3D Development

Overview

Build high-performance 3D web applications using Three.js -- a cross-browser WebGL/WebGPU library. Covers the full spectrum from basic scene setup to advanced rendering, performance optimization, and specialized features like physics and VR/XR.

When to Use

  • 3D scenes, models, animations, or visualizations
  • WebGL/WebGPU rendering and graphics programming
  • Interactive 3D experiences (games, configurators, data visualization)
  • Camera controls, lighting, materials, or shaders
  • Loading 3D assets (GLTF, FBX, OBJ) or textures
  • Post-processing effects (Bloom, DOF, SSAO)
  • Physics simulation, VR/XR experiences, or spatial audio
  • Performance optimization (instancing, LOD, frustum culling)

Progressive Learning Path

Level 1: Getting Started

  • Fundamentals - Scene setup, basic geometry, materials, lighting, render loop

Level 2: Common Tasks

  • Asset Loading: GLTF, FBX, OBJ, texture loaders
  • Textures: Types, mapping, wrapping, filtering
  • Cameras: Perspective, Orthographic, controllers
  • Lighting: Types, shadows, helpers
  • Animation: Clips, Mixers, keyframes
  • Math: Vector, Matrix, Quaternion, Curves
  • Geometry: Built-in shapes, BufferGeometry, custom geometry, instancing
  • Materials: PBR, Basic, Phong, Lambert, Physical, Toon, Normal, Depth, Raw, Shader

Level 3: Interaction & Effects

  • Interaction: Raycasting, picking, transforms
  • Post-Processing: Render passes, Bloom, SSAO, SSR
  • Controller Add-ons: Orbit, Transform, First Person

Level 4: Advanced Rendering

  • Advanced Materials: PBR, custom shaders
  • Performance: Instancing, LOD, Batching, Culling
  • Node Materials (TSL): Shader graphs, compute shaders

Level 5: Specialized Domains

  • Physics: Ammo, Rapier, Jolt, VR/XR
  • Advanced Loaders: SVG, VRML, domain-specific formats
  • WebGPU: Modern backend, compute shaders
  • Shaders: GLSL, ShaderMaterial, Uniforms, custom effects

Quick Start Pattern

// 1. Scene, Camera, Renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 2. Add Objects
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 3. Add Lighting
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);
scene.add(new THREE.AmbientLight(0x404040));

// 4. Animation Loop
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}
animate();

Fundamentals

Scene

Container for all 3D objects, lights, and cameras.

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000); // Solid color
scene.background = texture;                    // Skybox texture
scene.background = cubeTexture;                // Cube map
scene.environment = envMap;                    // PBR environment map
scene.fog = new THREE.Fog(0xffffff, 1, 100);   // Linear fog
scene.fog = new THREE.FogExp2(0xffffff, 0.02); // Exponential fog

Camera

PerspectiveCamera - Most common, simulates human eye

// PerspectiveCamera(fov, aspect, near, far)
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.set(0, 5, 10);
camera.lookAt(0, 0, 0);
camera.updateProjectionMatrix(); // Required after changing fov, aspect, near, far

OrthographicCamera - No perspective distortion, for 2D/isometric views

const aspect = window.innerWidth / window.innerHeight;
const frustumSize = 10;
const camera = new THREE.OrthographicCamera(
  (frustumSize * aspect) / -2,
  (frustumSize * aspect) / 2,
  frustumSize / 2,
  frustumSize / -2,
  0.1,
  1000,
);

CubeCamera - Renders environment maps for reflections

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

// Use for reflections
material.envMap = cubeRenderTarget.texture;

// Update each frame (expensive!)
cubeCamera.position.copy(reflectiveMesh.position);
cubeCamera.update(renderer, scene);

WebGL Renderer

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector('#canvas'), // Optional existing canvas
  antialias: true,                           // Anti-aliasing
  alpha: true,                               // Transparent background
  powerPreference: 'high-performance',       // GPU preference
  preserveDrawingBuffer: true,               // For screenshots
});

renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

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

// Color space (Three.js r152+)
renderer.outputColorSpace = THREE.SRGBColorSpace;

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

Object3D

Base class for all 3D objects. Mesh, Group, Light, Camera all inherit from Object3D.

const obj = new THREE.Object3D();

// Transforms
obj.position.set(x, y, z);
obj.rotation.set(x, y, z);    // Euler angles (radians)
obj.quaternion.set(x, y, z, w); // Quaternion rotation
obj.scale.set(x, y, z);

// Local vs world transforms
obj.getWorldPosition(targetVector);
obj.getWorldQuaternion(targetQuaternion);
obj.getWorldDirection(targetVector);

// Hierarchy
obj.add(child);
obj.remove(child);
obj.parent;
obj.children;

// Visibility
obj.visible = false;

// Layers (for selective rendering/raycasting)
obj.layers.set(1);
obj.layers.enable(2);

// Traverse hierarchy
obj.traverse((child) => {
  if (child.isMesh) child.material.color.set(0xff0000);
});

Mesh

Combines geometry and material.

const mesh = new THREE.Mesh(geometry, material);

// Multi-material (one per geometry group)
const mesh = new THREE.Mesh(geometry, [material1, material2]);

// Common properties
mesh.castShadow = true;    // Cast shadows
mesh.receiveShadow = true; // Receive shadows
mesh.frustumCulled = true; // Default: skip rendering outside camera view
mesh.renderOrder = 10;     // Higher renders later

Coordinate System

Three.js uses a right-hand coordinate system:

  • +X points right
  • +Y points up
  • +Z points toward the viewer (out of screen)
// Axes helper
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper); // Red=X, Green=Y, Blue=Z

Math Utilities

Vector3

const v = new THREE.Vector3(x, y, z);

// Operations (in-place)
v.add(v2);
v.sub(v2);
v.multiplyScalar(2);
v.normalize();
v.lerp(target, alpha);

// Computations (return values)
v.length();
v.lengthSq();          // Faster than length()
v.distanceTo(v2);
v.dot(v2);
v.cross(v2);

// Transforms
v.applyMatrix4(matrix);
v.applyQuaternion(q);
v.project(camera);     // World -> NDC
v.unproject(camera);   // NDC -> World

Quaternion

const q = new THREE.Quaternion();
q.setFromEuler(euler);
q.setFromAxisAngle(axis, angle);
q.setFromRotationMatrix(matrix);

q.multiply(q2);
q.slerp(target, t);    // Spherical interpolation
q.normalize();
q.invert();

MathUtils

THREE.MathUtils.clamp(value, min, max);
THREE.MathUtils.lerp(start, end, alpha);
THREE.MathUtils.mapLinear(value, inMin, inMax, outMin, outMax);
THREE.MathUtils.degToRad(degrees);
THREE.MathUtils.radToDeg(radians);
THREE.MathUtils.randFloat(min, max);
THREE.MathUtils.smoothstep(x, min, max);

Common Patterns

Proper Resource Disposal

function dispose() {
  // Dispose geometry
  mesh.geometry.dispose();

  // Dispose material
  if (Array.isArray(mesh.material)) {
    mesh.material.forEach((m) => m.dispose());
  } else {
    mesh.material.dispose();
  }

  // Dispose textures
  texture.dispose();

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

  // Dispose renderer
  renderer.dispose();
}

Clock-Based Animation

const clock = new THREE.Clock();

function animate() {
  const delta = clock.getDelta();       // Time since last frame (seconds)
  const elapsed = clock.getElapsedTime(); // Total elapsed time (seconds)

  mesh.rotation.y += delta * 0.5; // Consistent speed regardless of frame rate

  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

Responsive Canvas

function onWindowResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;

  camera.aspect = width / height;
  camera.updateProjectionMatrix();

  renderer.setSize(width, height);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}
window.addEventListener('resize', onWindowResize);

Loading Manager

const manager = new THREE.LoadingManager();

manager.onStart = (url, loaded, total) => console.log('Loading started');
manager.onLoad = () => console.log('All assets loaded');
manager.onProgress = (url, loaded, total) => console.log(`${loaded}/${total}`);
manager.onError = (url) => console.error(`Failed to load: ${url}`);

const textureLoader = new THREE.TextureLoader(manager);
const gltfLoader = new GLTFLoader(manager);

Performance Optimization

Instanced Rendering

Efficiently render many copies of the same geometry:

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.InstancedMesh(geometry, material, 10000);

const matrix = new THREE.Matrix4();
const position = new THREE.Vector3();
const rotation = new THREE.Euler();
const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3(1, 1, 1);

for (let i = 0; i < 10000; i++) {
  position.set(
    Math.random() * 100 - 50,
    Math.random() * 100 - 50,
    Math.random() * 100 - 50
  );
  rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
  quaternion.setFromEuler(rotation);
  matrix.compose(position, quaternion, scale);
  mesh.setMatrixAt(i, matrix);
}

mesh.instanceMatrix.needsUpdate = true;
scene.add(mesh);

Level of Detail (LOD)

Switch model detail based on camera distance:

const lod = new THREE.LOD();

// High detail (close)
const geometryHigh = new THREE.IcosahedronGeometry(10, 4);
lod.addLevel(new THREE.Mesh(geometryHigh, material), 0);

// Medium detail
const geometryMed = new THREE.IcosahedronGeometry(10, 2);
lod.addLevel(new THREE.Mesh(geometryMed, material), 50);

// Low detail (far)
const geometryLow = new THREE.IcosahedronGeometry(10, 0);
lod.addLevel(new THREE.Mesh(geometryLow, material), 100);

scene.add(lod);

Frustum Culling

Automatic feature -- objects outside camera view are not rendered. Enabled by default.

// Force disable culling for specific objects
object.frustumCulled = false;

// Manual visibility test
const frustum = new THREE.Frustum();
const matrix = new THREE.Matrix4();
matrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(matrix);

if (frustum.intersectsObject(object)) {
  // Object is in view
}

Geometry Optimization

// Merge geometries (reduces draw calls)
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';

const geometries = [geom1, geom2, geom3];
const mergedGeometry = mergeGeometries(geometries);
const mesh = new THREE.Mesh(mergedGeometry, material);

// Dispose old geometries
geometries.forEach(g => g.dispose());

Texture Optimization

// Use appropriate sizes (powers of 2)
// 512x512, 1024x1024, 2048x2048

// Mipmaps (auto-generated by default)
texture.generateMipmaps = true;

// Appropriate filtering
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;

// Anisotropic filtering (quality vs performance tradeoff)
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();

// Dispose unused textures
texture.dispose();

Shadow Optimization

// Lower shadow map resolution
light.shadow.mapSize.width = 1024;   // Instead of 2048
light.shadow.mapSize.height = 1024;

// Constrain shadow camera frustum
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 50;

// Reduce shadow-casting objects
object.castShadow = false;    // Far/small objects don't need it
object.receiveShadow = false; // Objects that don't need shadow reception

// Use more efficient shadow type
renderer.shadowMap.type = THREE.PCFShadowMap; // Instead of PCFSoftShadowMap

Material Sharing

// Share materials across meshes (reduces memory)
const sharedMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });

const mesh1 = new THREE.Mesh(geometry1, sharedMaterial);
const mesh2 = new THREE.Mesh(geometry2, sharedMaterial);
const mesh3 = new THREE.Mesh(geometry3, sharedMaterial);

Object Pooling

// Reuse objects instead of frequent create/destroy
class ObjectPool {
  constructor(factory, initialSize) {
    this.factory = factory;
    this.pool = [];
    for (let i = 0; i < initialSize; i++) {
      this.pool.push(factory());
    }
  }

  get() {
    return this.pool.length > 0 ? this.pool.pop() : this.factory();
  }

  release(obj) {
    this.pool.push(obj);
  }
}

const bulletPool = new ObjectPool(() => {
  return new THREE.Mesh(bulletGeometry, bulletMaterial);
}, 100);

Performance Monitoring

// FPS counter
const stats = new Stats();
document.body.appendChild(stats.dom);

// Renderer info
console.log(renderer.info);
// Shows: geometry count, texture count, shader programs, draw calls, triangles, points, lines

General Best Practices

  • Limit draw calls (merge geometry, use instancing)
  • Reduce polygon counts (LOD, simplification)
  • Optimize textures (compression, appropriate sizes)
  • Share materials and geometries
  • Use frustum culling
  • Limit light sources (3-5 max)
  • Avoid transparency when possible
  • Use object pooling for frequent create/destroy
  • Profile with browser DevTools
  • Test on target devices

External Resources

相关技能 Related Skills