three.js Materials & Lighting
Make three.js surfaces look right: pick the correct material, light the scene,
enable shadows, and add image-based lighting. Patterns target r184, verified
against r184 (lighting is physically based by default since r155).
When to use
- Use when a mesh renders black or flat, when choosing a material, adding lights,
enabling shadows, or setting up environment-map reflections (IBL).
- Use when code constructs
MeshStandardMaterial, DirectionalLight, etc., or sets
renderer.shadowMap.enabled or scene.environment.
When not to use: the renderer/camera/loop → threejs-scene-setup. Loading
models (whose PBR materials this complements) → threejs-gltf-loading. Custom
GLSL/ShaderMaterial is its own topic; for the portable concept see
shader-programming.
Core workflow
- Pick a material by need.
MeshStandardMaterial (PBR: roughness,
metalness, reacts to lights/IBL) for realism; MeshPhysicalMaterial for
clearcoat/transmission; MeshBasicMaterial (unlit, ignores lights) for UI/flat;
MeshNormalMaterial/MeshDepthMaterial for debugging.
- Add light, or nothing shows. Lit materials need a light source and/or
scene.environment. Combine a soft fill (AmbientLight/HemisphereLight) with a
key DirectionalLight.
- Mind light intensity. Since r155, lighting is physically based; modern
intensities are higher than old tutorials (a key
DirectionalLight ≈ 1–3).
- Enable shadows in three places.
renderer.shadowMap.enabled = true, the
light's castShadow = true, and each mesh's castShadow/receiveShadow. Then
fit the light's shadow camera to the scene.
- Use an environment map for grounded reflections. Assign an equirectangular or
PMREM-processed texture to
scene.environment; PBR materials pick it up
automatically.
- Verify under real lighting — confirm the surface responds to the key light
(highlights move), shadows land where expected, and reflections look plausible.
Patterns
1. PBR material under a 3-light rig
import * as THREE from 'three';
const material = new THREE.MeshStandardMaterial({
color: 0xcc4444,
roughness: 0.5, // 0 = mirror, 1 = fully matte
metalness: 0.0, // 0 = dielectric (plastic/wood), 1 = metal
});
const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 16), material);
scene.add(mesh);
// Soft sky/ground fill + a directional key light.
scene.add(new THREE.HemisphereLight(0xbbddff, 0x443322, 1.0)); // sky, ground, intensity
const key = new THREE.DirectionalLight(0xffffff, 2.5);
key.position.set(5, 10, 7);
scene.add(key);
2. Unlit material (no light needed)
// MeshBasicMaterial ignores lights — for flat color, UI, or sprites/labels.
const flat = new THREE.MeshBasicMaterial({ color: 0x44aa88 });
// A textured color map should be tagged sRGB so colors aren't washed out:
const tex = new THREE.TextureLoader().load('assets/logo.png');
tex.colorSpace = THREE.SRGBColorSpace;
const logo = new THREE.MeshBasicMaterial({ map: tex, transparent: true });
3. Shadows (the three required switches + camera fit)
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // softer edges
const sun = new THREE.DirectionalLight(0xffffff, 3);
sun.position.set(8, 12, 6);
sun.castShadow = true;
sun.shadow.mapSize.set(2048, 2048); // default 512; raise for crisp
// DirectionalLight uses an OrthographicCamera — fit it tightly to the scene:
const cam = sun.shadow.camera;
cam.near = 1; cam.far = 40;
cam.left = -15; cam.right = 15; cam.top = 15; cam.bottom = -15;
scene.add(sun);
mesh.castShadow = true;
ground.receiveShadow = true; // a plane to catch the shadow
4. PBR textures on a material
const loader = new THREE.TextureLoader();
const colorMap = loader.load('assets/brick_color.jpg');
colorMap.colorSpace = THREE.SRGBColorSpace; // color maps are sRGB
const normalMap = loader.load('assets/brick_normal.jpg'); // data maps stay linear
const roughMap = loader.load('assets/brick_rough.jpg');
const brick = new THREE.MeshStandardMaterial({
map: colorMap,
normalMap,
roughnessMap: roughMap,
metalness: 0,
});
5. Image-based lighting from an HDR environment
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
new RGBELoader().load('assets/studio.hdr', (hdr) => {
hdr.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = hdr; // lights + reflects all PBR materials
scene.background = hdr; // optional: show it as the backdrop
});
// Optional cinematic tone curve:
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
Pitfalls
- Mesh is pure black → a lit material with no light and no
scene.environment.
Add a light or an environment map; to confirm geometry, temporarily swap to
MeshBasicMaterial/MeshNormalMaterial.
- Scene too dark even with lights → old tutorial intensities. r155+ is physically
based; raise intensities (key light ≈ 2–3) or add an environment map.
- Shadows don't appear → you missed one of the three switches
(
renderer.shadowMap.enabled, light.castShadow, mesh castShadow/
receiveShadow).
- Shadows are cut off or blocky → the
DirectionalLight's orthographic
shadow.camera frustum is too big/small or doesn't cover the scene; tighten
left/right/top/bottom/near/far and raise shadow.mapSize. Visualise it with
new THREE.CameraHelper(light.shadow.camera).
- Shadow acne / peter-panning → adjust
light.shadow.bias (small negative) and
light.shadow.normalBias.
- Colors look washed out / too bright → color (albedo) textures need
texture.colorSpace = THREE.SRGBColorSpace; normal/roughness/metalness maps must
stay linear (leave them as NoColorSpace).
- PointLight shadows tank performance → a point light renders the scene 6 times
(cube map). Prefer one shadow-casting
DirectionalLight; use cheaper fakes
elsewhere.
References
- For the material cheat-sheet (which
Mesh*Material for which look), light types
and their parameters/units, transparency vs alphaTest ordering, and the
PMREMGenerator/RoomEnvironment route to IBL without an HDR file, read
references/materials-lights-table.md.
Related skills
threejs-scene-setup — renderer, camera, and loop (set shadowMap, tone mapping).
threejs-gltf-loading — models arrive with PBR materials this skill tunes.
shader-programming — custom shader effects (engine-agnostic concept).
1---
2name: threejs-materials-lighting
3description: >
4 Light and shade a three.js scene: choose materials (MeshStandardMaterial PBR vs
5 unlit MeshBasicMaterial), add ambient/hemisphere/directional/point/spot lights,
6 turn on shadow maps, and use an environment map (IBL) for realistic reflections.
7 Use when a three.js model looks black, flat, or wrong — when the user mentions
8 three.js materials, MeshStandardMaterial, lights, shadows, envMap, or PBR. For
9 renderer/loop setup use threejs-scene-setup; for loading models use
10 threejs-gltf-loading.
11---
12
13# three.js Materials & Lighting
14
15Make three.js surfaces look right: pick the correct material, light the scene,
16enable shadows, and add image-based lighting. Patterns target **r184**, verified
17against **r184** (lighting is physically based by default since r155).
18
19## When to use
20
21- Use when a mesh renders black or flat, when choosing a material, adding lights,
22 enabling shadows, or setting up environment-map reflections (IBL).
23- Use when code constructs `MeshStandardMaterial`, `DirectionalLight`, etc., or sets
24 `renderer.shadowMap.enabled` or `scene.environment`.
25
26**When *not* to use:** the renderer/camera/loop → `threejs-scene-setup`. Loading
27models (whose PBR materials this complements) → `threejs-gltf-loading`. Custom
28GLSL/`ShaderMaterial` is its own topic; for the portable concept see
29`shader-programming`.
30
31## Core workflow
32
331. **Pick a material by need.** `MeshStandardMaterial` (PBR: `roughness`,
34 `metalness`, reacts to lights/IBL) for realism; `MeshPhysicalMaterial` for
35 clearcoat/transmission; `MeshBasicMaterial` (unlit, ignores lights) for UI/flat;
36 `MeshNormalMaterial`/`MeshDepthMaterial` for debugging.
372. **Add light, or nothing shows.** Lit materials need a light source and/or
38 `scene.environment`. Combine a soft fill (`AmbientLight`/`HemisphereLight`) with a
39 key `DirectionalLight`.
403. **Mind light intensity.** Since r155, lighting is physically based; modern
41 intensities are higher than old tutorials (a key `DirectionalLight` ≈ 1–3).
424. **Enable shadows in three places.** `renderer.shadowMap.enabled = true`, the
43 light's `castShadow = true`, and each mesh's `castShadow`/`receiveShadow`. Then
44 fit the light's shadow camera to the scene.
455. **Use an environment map for grounded reflections.** Assign an equirectangular or
46 PMREM-processed texture to `scene.environment`; PBR materials pick it up
47 automatically.
486. **Verify under real lighting** — confirm the surface responds to the key light
49 (highlights move), shadows land where expected, and reflections look plausible.
50
51## Patterns
52
53### 1. PBR material under a 3-light rig
54
55```js
56import * as THREE from 'three';
57
58const material = new THREE.MeshStandardMaterial({
59 color: 0xcc4444,
60 roughness: 0.5, // 0 = mirror, 1 = fully matte
61 metalness: 0.0, // 0 = dielectric (plastic/wood), 1 = metal
62});
63const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 16), material);
64scene.add(mesh);
65
66// Soft sky/ground fill + a directional key light.
67scene.add(new THREE.HemisphereLight(0xbbddff, 0x443322, 1.0)); // sky, ground, intensity
68const key = new THREE.DirectionalLight(0xffffff, 2.5);
69key.position.set(5, 10, 7);
70scene.add(key);
71```
72
73### 2. Unlit material (no light needed)
74
75```js
76// MeshBasicMaterial ignores lights — for flat color, UI, or sprites/labels.
77const flat = new THREE.MeshBasicMaterial({ color: 0x44aa88 });
78// A textured color map should be tagged sRGB so colors aren't washed out:
79const tex = new THREE.TextureLoader().load('assets/logo.png');
80tex.colorSpace = THREE.SRGBColorSpace;
81const logo = new THREE.MeshBasicMaterial({ map: tex, transparent: true });
82```
83
84### 3. Shadows (the three required switches + camera fit)
85
86```js
87renderer.shadowMap.enabled = true;
88renderer.shadowMap.type = THREE.PCFSoftShadowMap; // softer edges
89
90const sun = new THREE.DirectionalLight(0xffffff, 3);
91sun.position.set(8, 12, 6);
92sun.castShadow = true;
93sun.shadow.mapSize.set(2048, 2048); // default 512; raise for crisp
94// DirectionalLight uses an OrthographicCamera — fit it tightly to the scene:
95const cam = sun.shadow.camera;
96cam.near = 1; cam.far = 40;
97cam.left = -15; cam.right = 15; cam.top = 15; cam.bottom = -15;
98scene.add(sun);
99
100mesh.castShadow = true;
101ground.receiveShadow = true; // a plane to catch the shadow
102```
103
104### 4. PBR textures on a material
105
106```js
107const loader = new THREE.TextureLoader();
108const colorMap = loader.load('assets/brick_color.jpg');
109colorMap.colorSpace = THREE.SRGBColorSpace; // color maps are sRGB
110const normalMap = loader.load('assets/brick_normal.jpg'); // data maps stay linear
111const roughMap = loader.load('assets/brick_rough.jpg');
112
113const brick = new THREE.MeshStandardMaterial({
114 map: colorMap,
115 normalMap,
116 roughnessMap: roughMap,
117 metalness: 0,
118});
119```
120
121### 5. Image-based lighting from an HDR environment
122
123```js
124import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
125
126new RGBELoader().load('assets/studio.hdr', (hdr) => {
127 hdr.mapping = THREE.EquirectangularReflectionMapping;
128 scene.environment = hdr; // lights + reflects all PBR materials
129 scene.background = hdr; // optional: show it as the backdrop
130});
131// Optional cinematic tone curve:
132renderer.toneMapping = THREE.ACESFilmicToneMapping;
133renderer.toneMappingExposure = 1.0;
134```
135
136## Pitfalls
137
138- **Mesh is pure black** → a lit material with no light and no `scene.environment`.
139 Add a light or an environment map; to confirm geometry, temporarily swap to
140 `MeshBasicMaterial`/`MeshNormalMaterial`.
141- **Scene too dark even with lights** → old tutorial intensities. r155+ is physically
142 based; raise intensities (key light ≈ 2–3) or add an environment map.
143- **Shadows don't appear** → you missed one of the three switches
144 (`renderer.shadowMap.enabled`, `light.castShadow`, mesh `castShadow`/
145 `receiveShadow`).
146- **Shadows are cut off or blocky** → the `DirectionalLight`'s orthographic
147 `shadow.camera` frustum is too big/small or doesn't cover the scene; tighten
148 `left/right/top/bottom/near/far` and raise `shadow.mapSize`. Visualise it with
149 `new THREE.CameraHelper(light.shadow.camera)`.
150- **Shadow acne / peter-panning** → adjust `light.shadow.bias` (small negative) and
151 `light.shadow.normalBias`.
152- **Colors look washed out / too bright** → color (albedo) textures need
153 `texture.colorSpace = THREE.SRGBColorSpace`; normal/roughness/metalness maps must
154 stay linear (leave them as `NoColorSpace`).
155- **PointLight shadows tank performance** → a point light renders the scene 6 times
156 (cube map). Prefer one shadow-casting `DirectionalLight`; use cheaper fakes
157 elsewhere.
158
159## References
160
161- For the material cheat-sheet (which `Mesh*Material` for which look), light types
162 and their parameters/units, transparency vs `alphaTest` ordering, and the
163 `PMREMGenerator`/`RoomEnvironment` route to IBL without an HDR file, read
164 `references/materials-lights-table.md`.
165
166## Related skills
167
168- `threejs-scene-setup` — renderer, camera, and loop (set `shadowMap`, tone mapping).
169- `threejs-gltf-loading` — models arrive with PBR materials this skill tunes.
170- `shader-programming` — custom shader effects (engine-agnostic concept).