references/materials-lights-table.md
# three.js materials, lights & IBL reference (r155+)
Lookup tables and detail behind the materials & lighting skill.
## Material cheat-sheet
| Material | Lit? | Use for | Key params |
|----------|:----:|---------|-----------|
| `MeshBasicMaterial` | no | UI, flat color, billboards, debug | `color`, `map`, `wireframe` |
| `MeshStandardMaterial` | yes (PBR) | most realistic surfaces | `roughness`, `metalness`, `map`, `normalMap`, `roughnessMap`, `metalnessMap`, `aoMap`, `emissive` |
| `MeshPhysicalMaterial` | yes (PBR+) | car paint, glass, coatings | adds `clearcoat`, `transmission`, `ior`, `sheen` |
| `MeshPhongMaterial` | yes (legacy) | cheap shiny look, older tutorials | `shininess`, `specular` |
| `MeshLambertMaterial` | yes (legacy) | cheap matte | `color`, `map` |
| `MeshNormalMaterial` | no | debug orientation (no light needed) | — |
| `MeshDepthMaterial` | no | debug depth / custom depth | — |
| `MeshToonMaterial` | yes | cel-shaded look | `gradientMap` |
Default to `MeshStandardMaterial`. It responds to lights **and** `scene.environment`,
matches glTF's PBR model, and is the safe choice for imported assets.
### Common PBR intuition
- `metalness`: 0 for non-metals (plastic, wood, skin), 1 for bare metal. Avoid
in-between values except for transitions (rust, wear).
- `roughness`: 0 = sharp mirror reflection, 1 = fully diffuse. Most real surfaces
sit 0.3–0.8.
- Metals get their color from the albedo (`color`/`map`); dielectrics reflect the
environment near-white.
## Light types
| Light | Direction/shape | Casts shadows | Notes |
|-------|-----------------|:-------------:|-------|
| `AmbientLight(color, intensity)` | uniform, everywhere | no | flat fill; no form |
| `HemisphereLight(sky, ground, intensity)` | sky-to-ground gradient | no | great cheap outdoor fill |
| `DirectionalLight(color, intensity)` | parallel rays (sun) | yes (orthographic) | the usual key/shadow light |
| `PointLight(color, intensity, distance, decay)` | omni from a point | yes (6× cost) | bulbs, torches |
| `SpotLight(color, intensity, distance, angle, penumbra, decay)` | cone | yes (perspective) | flashlights, stage |
| `RectAreaLight(color, intensity, w, h)` | soft rectangle | no | softboxes, screens (needs init helper) |
Since r155 lighting is physically based and the old `physicallyCorrectLights`
toggle was removed. Practical starting intensities: key `DirectionalLight` 2–3,
`HemisphereLight`/`AmbientLight` fill ≈ 0.5–1.5. `PointLight`/`SpotLight` use
inverse-square `decay` (default 2), so they fall off fast — increase intensity or
reduce `decay` if a lamp seems too dim.
### Shadow camera by light type
- `DirectionalLight` → `OrthographicCamera`; you set `left/right/top/bottom/near/far`.
Fit it tightly; oversized frustums waste shadow-map resolution.
- `SpotLight` → `PerspectiveCamera`; `fov` follows the spot `angle`, aspect is
automatic.
- `PointLight` → 6 renders (cube faces); only `near`/`far` matter, and it's the
most expensive shadow.
Tuning knobs: `light.shadow.mapSize` (default 512×512; raise to 1024/2048 for
crisper edges at memory cost), `light.shadow.bias` (small negative to fix acne),
`light.shadow.normalBias`, and `renderer.shadowMap.type` (`BasicShadowMap`,
`PCFShadowMap`, `PCFSoftShadowMap`, `VSMShadowMap`). Visualise the frustum with
`new THREE.CameraHelper(light.shadow.camera)`.
## Transparency & sorting
- Set `material.transparent = true` and `material.opacity` for blended transparency;
three.js sorts transparent objects back-to-front, which can still mis-order
intersecting geometry.
- For binary cutouts (foliage, chain-link), prefer `material.alphaTest = 0.5` with
`transparent = false` — it writes depth and avoids sort artifacts.
- `material.side` = `FrontSide` (default), `BackSide` (interiors/skyboxes), or
`DoubleSide` (planes, leaves).
- `depthWrite = false` on overlapping additive/transparent effects (particles) so
they don't occlude each other.
## IBL without an HDR file: PMREM + RoomEnvironment
When you don't have an `.hdr`, generate a neutral studio environment procedurally:
```js
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
const pmrem = new THREE.PMREMGenerator(renderer);
scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
// dispose pmrem when done if you only need it once: pmrem.dispose();
```
`PMREMGenerator` pre-filters any environment (HDR, RoomEnvironment, or a rendered
scene) into the mip-mapped format PBR materials sample for reflections at varying
roughness. Assigning the result to `scene.environment` lights every
`MeshStandardMaterial`/`MeshPhysicalMaterial` without adding explicit lights.
Pair IBL with tone mapping for a filmic result:
`renderer.toneMapping = THREE.ACESFilmicToneMapping` and adjust
`renderer.toneMappingExposure`.
SKILL.md
---
name: threejs-materials-lighting
description: >
Light and shade a three.js scene: choose materials (MeshStandardMaterial PBR vs
unlit MeshBasicMaterial), add ambient/hemisphere/directional/point/spot lights,
turn on shadow maps, and use an environment map (IBL) for realistic reflections.
Use when a three.js model looks black, flat, or wrong — when the user mentions
three.js materials, MeshStandardMaterial, lights, shadows, envMap, or PBR. For
renderer/loop setup use threejs-scene-setup; for loading models use
threejs-gltf-loading.
---
# 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
1. **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.
2. **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`.
3. **Mind light intensity.** Since r155, lighting is physically based; modern
intensities are higher than old tutorials (a key `DirectionalLight` ≈ 1–3).
4. **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.
5. **Use an environment map for grounded reflections.** Assign an equirectangular or
PMREM-processed texture to `scene.environment`; PBR materials pick it up
automatically.
6. **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
```js
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)
```js
// 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)
```js
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
```js
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
```js
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).