Getting started
Installation
npm install three
Hello World
import * as THREE from "three";
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);
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);
camera.position.z = 5;
function animate() {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
Creates a rotating green cube with perspective camera and WebGL renderer.
Animation Loop
// Modern (preferred)
renderer.setAnimationLoop(animate);
// Legacy
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
Delta Time
const clock = new THREE.Clock();
function animate() {
const delta = clock.getDelta();
const elapsed = clock.getElapsedTime();
mesh.rotation.x += delta * speed;
renderer.render(scene, camera);
}
Cameras
PerspectiveCamera
// PerspectiveCamera(fov, aspect, near, far)
const camera = new THREE.PerspectiveCamera(
75, // FOV 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);
Creates a perspective camera with realistic depth perception.
OrthographicCamera
// OrthographicCamera(left, right, top, bottom, near, far)
const camera = new THREE.OrthographicCamera(
-10,
10, // left, right
10,
-10, // top, bottom
0.1,
1000, // near, far
);
Creates an orthographic camera without perspective distortion.
Camera Methods
camera.position.set(x, y, z);
camera.lookAt(targetVector);
camera.updateProjectionMatrix(); // After changing properties
Geometries
Basic Shapes
new THREE.BoxGeometry(w, h, d);
new THREE.SphereGeometry(r, wSeg, hSeg);
new THREE.PlaneGeometry(w, h);
new THREE.CylinderGeometry(rT, rB, h);
new THREE.ConeGeometry(r, h, seg);
new THREE.TorusGeometry(r, tube, rSeg);
Advanced Shapes
new THREE.CircleGeometry(r, seg);
new THREE.RingGeometry(inner, outer);
new THREE.TorusKnotGeometry(r, tube);
new THREE.IcosahedronGeometry(r, detail);
new THREE.OctahedronGeometry(r, detail);
new THREE.TetrahedronGeometry(r, detail);
Custom BufferGeometry
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));
const mesh = new THREE.Mesh(geometry, material);
Creates custom geometry from vertex data.
Materials
Material Types
| Material | Lights | Use case |
|---|---|---|
MeshBasicMaterial |
No | Unlit, flat color |
MeshLambertMaterial |
Yes | Matte, non-shiny |
MeshPhongMaterial |
Yes | Shiny, specular |
MeshStandardMaterial |
Yes | PBR, realistic |
MeshPhysicalMaterial |
Yes | PBR+, glass/coat |
Material Properties
const material = new THREE.MeshStandardMaterial({
color: 0xff0000,
map: texture,
normalMap: normalTex,
roughness: 0.5, // 0=mirror, 1=matte
metalness: 0.5, // 0=plastic, 1=metal
transparent: true,
opacity: 0.8, // 0-1
side: THREE.DoubleSide,
wireframe: false,
});
Common Properties
material.color.set(0xff0000);
material.needsUpdate = true; // After changing properties
material.dispose(); // Free memory
Lights
Ambient Light
const ambient = new THREE.AmbientLight(
0x404040, // color
1, // intensity
);
scene.add(ambient);
Global illumination, no shadows.
Directional Light
const dir = new THREE.DirectionalLight(
0xffffff, // color
1, // intensity
);
dir.position.set(5, 10, 7);
scene.add(dir);
Sun-like light with parallel rays.
Point Light
const point = new THREE.PointLight(
0xffffff, // color
1, // intensity
100, // distance
);
point.position.set(0, 10, 0);
scene.add(point);
Bulb-like light radiating in all directions.
Spot Light
const spot = new THREE.SpotLight(0xffffff, 1);
spot.position.set(0, 10, 0);
spot.angle = Math.PI / 6;
spot.penumbra = 0.1;
scene.add(spot);
Hemisphere Light
const hemi = new THREE.HemisphereLight(
0xffffbb, // sky color
0x080820, // ground color
1, // intensity
);
scene.add(hemi);
Shadows
renderer.shadowMap.enabled = true;
light.castShadow = true;
mesh.castShadow = true;
floor.receiveShadow = true;
// Shadow quality
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 500;
Textures
Loading Textures
import { TextureLoader } from "three";
const loader = new TextureLoader();
// Async
const texture = await loader.loadAsync("texture.jpg");
// Callback
loader.load("texture.jpg", (texture) => {
mesh.material.map = texture;
mesh.material.needsUpdate = true;
});
Applying Textures
const material = new THREE.MeshStandardMaterial({
map: loader.load("diffuse.jpg"),
normalMap: loader.load("normal.jpg"),
roughnessMap: loader.load("rough.jpg"),
metalnessMap: loader.load("metal.jpg"),
aoMap: loader.load("ao.jpg"),
});
Texture Settings
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(2, 2);
texture.offset.set(0.5, 0.5);
texture.rotation = Math.PI / 4;
texture.colorSpace = THREE.SRGBColorSpace;
// Filtering
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;
Wrapping Modes
| Mode | Description |
|---|---|
RepeatWrapping |
Tile texture |
ClampToEdgeWrapping |
Stretch edge pixels |
MirroredRepeatWrapping |
Mirror and repeat |
Objects & Transforms
Mesh
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Position
mesh.position.set(x, y, z);
mesh.position.x = 5;
// Rotation (radians)
mesh.rotation.set(x, y, z);
mesh.rotation.y = Math.PI / 4;
// Scale
mesh.scale.set(2, 2, 2);
mesh.scale.x = 1.5;
// Look at point
mesh.lookAt(new THREE.Vector3(0, 0, 0));
Groups
const group = new THREE.Group();
group.add(mesh1);
group.add(mesh2);
group.add(mesh3);
scene.add(group);
// Transform group moves all children
group.position.set(0, 5, 0);
group.rotation.y = Math.PI;
group.scale.set(2, 2, 2);
Object Hierarchy
parent.add(child);
parent.remove(child);
scene.getObjectByName("meshName");
scene.getObjectById(id);
// Traverse all descendants
scene.traverse((child) => {
if (child.isMesh) {
child.material.wireframe = true;
}
});
Controls
OrbitControls
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 2;
controls.maxDistance = 50;
controls.maxPolarAngle = Math.PI / 2;
// Update in animation loop
function animate() {
controls.update();
renderer.render(scene, camera);
}
Control Options
controls.enabled = true;
controls.target.set(0, 0, 0);
controls.enableZoom = true;
controls.enableRotate = true;
controls.enablePan = true;
controls.autoRotate = true;
controls.autoRotateSpeed = 2.0;
Other Controls
import { FlyControls } from "three/addons/controls/FlyControls.js";
import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.js";
import { PointerLockControls } from "three/addons/controls/PointerLockControls.js";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
Raycasting
Mouse Picking
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
window.addEventListener("click", (event) => {
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);
if (intersects.length > 0) {
const obj = intersects[0].object;
const point = intersects[0].point;
const dist = intersects[0].distance;
const face = intersects[0].face;
}
});
Raycaster Methods
raycaster.set(origin, direction);
raycaster.setFromCamera(pointer, camera);
// Intersect specific objects
raycaster.intersectObject(object);
raycaster.intersectObjects(objects, recursive);
Loading Models
GLTFLoader
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
loader.load("model.glb", (gltf) => {
scene.add(gltf.scene);
// Access animations
const mixer = new THREE.AnimationMixer(gltf.scene);
gltf.animations.forEach((clip) => {
mixer.clipAction(clip).play();
});
// Update in animation loop
const clock = new THREE.Clock();
function animate() {
mixer.update(clock.getDelta());
}
});
DRACOLoader
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/");
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
Enables loading of Draco-compressed models.
Other Loaders
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
import { ColladaLoader } from "three/addons/loaders/ColladaLoader.js";
Scene
Scene Setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.environment = cubeTexture;
// Fog
scene.fog = new THREE.Fog(0xcccccc, 10, 100);
scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
Scene Methods
scene.add(object);
scene.remove(object);
scene.clear();
scene.getObjectByName("meshName");
scene.getObjectById(id);
scene.traverse((child) => {
if (child.isMesh) {
// Process mesh
}
});
Renderer
WebGLRenderer
const renderer = new THREE.WebGLRenderer({
canvas: canvasElement,
antialias: true,
alpha: true,
});
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x000000, 1);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.render(scene, camera);
Renderer Properties
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.physicallyCorrectLights = true;
Responsive Resize
Window Resize Handler
window.addEventListener("resize", () => {
// Update camera
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
// Update renderer
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
Handles window resizing and updates camera aspect ratio and renderer size.
Helpers
Debug Helpers
// Axes (RGB = XYZ)
scene.add(new THREE.AxesHelper(5));
// Grid
scene.add(new THREE.GridHelper(10, 10));
// Camera frustum
scene.add(new THREE.CameraHelper(camera));
// Lights
scene.add(new THREE.DirectionalLightHelper(light, 5));
scene.add(new THREE.PointLightHelper(pointLight, 1));
scene.add(new THREE.SpotLightHelper(spotLight));
// Bounding box
scene.add(new THREE.BoxHelper(mesh, 0xff0000));
Skeleton Helper
const helper = new THREE.SkeletonHelper(skinnedMesh);
scene.add(helper);
Vectors & Math
Vector3
const v = new THREE.Vector3(x, y, z);
v.set(x, y, z);
v.add(otherVector);
v.sub(otherVector);
v.multiply(otherVector);
v.multiplyScalar(scalar);
v.normalize();
v.length();
v.distanceTo(otherVector);
v.dot(otherVector);
v.cross(otherVector);
v.lerp(otherVector, alpha);
Common Math
THREE.MathUtils.degToRad(90);
THREE.MathUtils.radToDeg(Math.PI / 2);
THREE.MathUtils.clamp(value, min, max);
THREE.MathUtils.lerp(start, end, alpha);
THREE.MathUtils.randFloat(low, high);
THREE.MathUtils.randInt(low, high);
Post-Processing
EffectComposer
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(width, height),
1.5, // strength
0.4, // radius
0.85, // threshold
);
composer.addPass(bloomPass);
// Render with composer
composer.render();
Common Passes
import { GlitchPass } from "three/addons/postprocessing/GlitchPass.js";
import { FilmPass } from "three/addons/postprocessing/FilmPass.js";
import { DotScreenPass } from "three/addons/postprocessing/DotScreenPass.js";
import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js";
Gotchas
⚠️ Color space: Set texture.colorSpace = THREE.SRGBColorSpace for diffuse/color textures, or colors will look washed out. Normal maps and other data textures should stay in linear space.
⚠️ Dispose resources: Call geometry.dispose(), material.dispose(), texture.dispose() when removing objects to prevent memory leaks. Three.js doesn't auto-cleanup.
⚠️ Camera aspect ratio: Always update camera.aspect and call camera.updateProjectionMatrix() after resizing the window.
⚠️ Material needs lights: MeshStandardMaterial, MeshPhongMaterial, and MeshLambertMaterial require lights in the scene. Use MeshBasicMaterial for unlit objects.
⚠️ Controls update: When using OrbitControls with damping enabled, you must call controls.update() in your animation loop.
⚠️ Pixel ratio: Cap devicePixelRatio with Math.min(window.devicePixelRatio, 2) to avoid performance issues on high-DPI displays.
Also see
- Three.js Documentation (threejs.org)
- Three.js Manual (threejs.org)
- Three.js Examples (threejs.org)
- Three.js GitHub (github.com)
- Three.js Journey (threejs-journey.com)