threejs-impl-lighting
threejs-impl-lighting
Quick Reference
Light Types at a Glance
| Light | Shadows | Direction | Cost | Intensity Unit (r160+) |
|---|---|---|---|---|
AmbientLight |
NO | None (uniform) | Negligible | Unitless multiplier |
HemisphereLight |
NO | Vertical gradient | Negligible | Unitless multiplier |
DirectionalLight |
YES | Parallel rays | Moderate | Lux |
PointLight |
YES (6-pass!) | Omnidirectional | High | Candela |
SpotLight |
YES | Cone | High | Candela |
RectAreaLight |
NO | Planar emission | Very high | Nits (cd/m2) |
LightProbe |
NO | Spherical harmonics | Low | Unitless multiplier |
Critical Warnings
NEVER use AmbientLight as the sole light source -- it produces flat, dimensionless rendering. ALWAYS combine it with at least one directional or point light.
NEVER forget to add light.target to the scene when repositioning a DirectionalLight or SpotLight target -- the direction vector will NOT update without it.
NEVER use RectAreaLight without calling RectAreaLightUniformsLib.init() first (WebGLRenderer) or RectAreaLightTexturesLib (WebGPURenderer) -- the light will render incorrectly or not at all.
NEVER use more than 1-2 shadow-casting PointLight instances -- each requires 6 shadow map render passes. Use SpotLight with shadows as a cheaper alternative.
ALWAYS call pmremGenerator.dispose() after generating environment maps -- PMREMGenerator allocates significant GPU memory.
ALWAYS set renderer.toneMapping when using physically correct intensity values -- without tone mapping, high lux/candela values produce blown-out white scenes.
Light Class Hierarchy
All lights inherit from Light, which extends Object3D:
Object3D
└── Light (abstract: .color, .intensity, .dispose())
├── AmbientLight — uniform fill
├── HemisphereLight — sky/ground gradient
├── DirectionalLight — parallel rays (sun)
├── PointLight — omnidirectional (bulb)
├── SpotLight — cone (flashlight)
├── RectAreaLight — planar (window/panel)
└── LightProbe — spherical harmonics (IBL)
The Light base class provides:
.color(Color) -- light color, default0xffffff.intensity(number) -- strength multiplier, default1.isLight(boolean, readonly) -- ALWAYStrue.dispose()-- releases GPU resources
Physically Correct Lighting (r160+)
As of r160+, ALL lighting uses physically based units by default. The legacy renderer.useLegacyLights property was removed.
| Light Type | Intensity Unit | Description |
|---|---|---|
| DirectionalLight | Lux (lm/m2) | Sunlight: 50,000-100,000 lux |
| PointLight | Candela (lm/sr) | 100W bulb: ~1700 cd |
| SpotLight | Candela (lm/sr) | Stage spot: ~10,000 cd |
| RectAreaLight | Nits (cd/m2) | LED panel: ~500-5000 nits |
| AmbientLight | Unitless | Multiplier, no physical unit |
| HemisphereLight | Unitless | Multiplier, no physical unit |
The .power property on PointLight, SpotLight, and RectAreaLight gives luminous power in lumens:
- PointLight:
power = intensity * 4 * Math.PI - SpotLight:
power = intensity * Math.PI
ALWAYS pair physically correct intensities with tone mapping:
import * as THREE from 'three';
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
The 7 Light Types
AmbientLight
Uniform fill light. NO direction, NO shadows. Use for base illumination only.
const ambient = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambient);
HemisphereLight
Sky-to-ground gradient. NO shadows. Simulates outdoor ambient with sky and ground bounce.
const hemi = new THREE.HemisphereLight(0xffffbb, 0x080820, 1.0);
scene.add(hemi);
Properties: .groundColor (Color) -- lower hemisphere color.
DirectionalLight
Parallel rays simulating distant light (sun). Direction = light.position to light.target.position.
const sun = new THREE.DirectionalLight(0xffffff, 3);
sun.position.set(5, 10, 7.5);
scene.add(sun);
scene.add(sun.target); // REQUIRED for target repositioning
Properties: .target (Object3D), .shadow (DirectionalLightShadow).
PointLight
Omnidirectional light from a single point. Shadows cost 6 render passes.
const bulb = new THREE.PointLight(0xffaa44, 800, 20, 2);
bulb.position.set(0, 3, 0);
scene.add(bulb);
Properties: .distance (number, 0=infinite), .decay (number, 2=physically correct), .power (lumens).
SpotLight
Cone-shaped light. ALWAYS set penumbra >= 0.1 for realistic soft edges.
const spot = new THREE.SpotLight(0xffffff, 1000);
spot.position.set(0, 10, 0);
spot.angle = Math.PI / 6;
spot.penumbra = 0.3;
spot.decay = 2;
scene.add(spot);
scene.add(spot.target); // REQUIRED for target repositioning
Properties: .angle (max Math.PI/2), .penumbra (0-1), .distance, .decay, .power, .map (cookie texture, REQUIRES castShadow = true), .target, .shadow.
RectAreaLight
Planar emitter for windows, panels, strip lights. Works ONLY with MeshStandardMaterial and MeshPhysicalMaterial. CANNOT cast shadows.
import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
RectAreaLightUniformsLib.init(); // MUST call before creating any RectAreaLight
const panel = new THREE.RectAreaLight(0xffffff, 5, 4, 10);
panel.position.set(5, 5, 0);
panel.lookAt(0, 0, 0); // Orient using lookAt, NOT rotation
scene.add(panel);
Properties: .width, .height, .power (lumens).
LightProbe
Spherical harmonics-based ambient lighting. Useful for AR and baked environment lighting.
import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js';
const probe = LightProbeGenerator.fromCubeTexture(cubeTexture);
scene.add(probe);
Environment Maps (IBL)
Image-Based Lighting (IBL) provides the most realistic ambient illumination for PBR materials. The standard workflow uses RGBELoader + PMREMGenerator.
Standard HDR Environment Workflow
import * as THREE from 'three';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
const pmremGenerator = new THREE.PMREMGenerator(renderer);
const rgbeLoader = new RGBELoader();
rgbeLoader.load('environment.hdr', (hdrTexture) => {
const envMap = pmremGenerator.fromEquirectangular(hdrTexture);
scene.environment = envMap.texture; // ALL PBR materials auto-use this
scene.background = envMap.texture; // Optional: visible HDR background
hdrTexture.dispose(); // Free source texture
pmremGenerator.dispose(); // ALWAYS dispose after use
});
scene.environment vs scene.background
| Property | Effect | When to Use |
|---|---|---|
scene.environment |
PBR reflections + ambient lighting on all Standard/Physical materials | ALWAYS for realistic PBR |
scene.background |
Visible skybox/backdrop | When you want the HDR visible |
| Both set to same texture | Full IBL with visible environment | Most common for product shots |
scene.backgroundBlurriness |
Blur the background (0-1) | Depth-of-field effect on backdrop |
scene.environmentIntensity |
Scale IBL contribution | Fine-tune ambient level |
scene.environmentRotation |
Rotate the environment map | Adjust light direction without moving lights |
When scene.environment is set, ALL MeshStandardMaterial and MeshPhysicalMaterial instances AUTOMATICALLY use it for reflections and ambient lighting -- no per-material configuration needed.
PMREMGenerator Methods
| Method | Input | Purpose |
|---|---|---|
fromEquirectangular(texture) |
Equirectangular texture | Convert HDR panorama to PMREM |
fromScene(scene, sigma?) |
Three.js Scene | Generate PMREM from a 3D scene |
fromCubemap(texture) |
CubeTexture | Convert cubemap to PMREM |
dispose() |
-- | Free GPU memory (ALWAYS call) |
EXR Alternative
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
new EXRLoader().load('environment.exr', (exrTexture) => {
const envMap = pmremGenerator.fromEquirectangular(exrTexture);
scene.environment = envMap.texture;
exrTexture.dispose();
pmremGenerator.dispose();
});
Light Helpers
| Helper | Import | Constructor |
|---|---|---|
DirectionalLightHelper |
three |
new DirectionalLightHelper(light, size?, color?) |
SpotLightHelper |
three |
new SpotLightHelper(light, color?) |
PointLightHelper |
three |
new PointLightHelper(light, sphereSize?, color?) |
HemisphereLightHelper |
three |
new HemisphereLightHelper(light, size, color?) |
RectAreaLightHelper |
three/addons/helpers/RectAreaLightHelper.js |
new RectAreaLightHelper(light) |
LightProbeHelper |
three/addons/helpers/LightProbeHelper.js |
new LightProbeHelper(probe, size) |
ALWAYS call helper.update() after changing light properties if the helper does not auto-update.
const helper = new THREE.DirectionalLightHelper(dirLight, 5);
scene.add(helper);
// After changing dirLight properties:
helper.update();
Performance Budget
| Category | Budget |
|---|---|
| Mobile WebGL | Max 4-5 real-time lights total |
| Desktop WebGL | Max 8-16 lights depending on scene |
| Shadow-casting PointLights | Max 1-2 (6 passes each) |
| RectAreaLights | Max 2-3 (expensive LTC evaluation) |
ALWAYS prefer baked lighting for static environments over real-time lights.
ALWAYS use environment maps (IBL) as the primary ambient source instead of multiple fill lights.
Reference Links
- references/methods.md -- Complete API signatures for all light types and PMREMGenerator
- references/examples.md -- Lighting recipes: outdoor, indoor, studio, product
- references/anti-patterns.md -- Common lighting mistakes and fixes
Official Sources
- https://threejs.org/docs/#api/en/lights/AmbientLight
- https://threejs.org/docs/#api/en/lights/DirectionalLight
- https://threejs.org/docs/#api/en/lights/PointLight
- https://threejs.org/docs/#api/en/lights/SpotLight
- https://threejs.org/docs/#api/en/lights/RectAreaLight
- https://threejs.org/docs/#api/en/lights/HemisphereLight
- https://threejs.org/docs/#api/en/lights/LightProbe
- https://threejs.org/docs/#api/en/extras/PMREMGenerator