
Threejs Impl Lighting
- 18 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-impl-lighting is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-impl-lighting
- AI & Agent Building
- AI-coding skill
Threejs Impl Lighting by the numbers
- 18 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,710 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-impl-lightingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
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 repositioningProperties: .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 repositioningProperties: .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
threejs-impl-lighting — Anti-Patterns Reference
Common lighting mistakes and their fixes. Every anti-pattern includes WHY it fails and the correct approach.
---
AP-1: AmbientLight as Sole Light Source
Wrong:
const ambient = new THREE.AmbientLight(0xffffff, 1.0);
scene.add(ambient);
// No other lights — scene looks flat and dimensionlessWhy it fails: AmbientLight applies uniform color to all surfaces regardless of orientation. Without directional or point lights, there are NO shadows, NO highlights, and NO depth cues. Every surface appears equally lit.
Correct:
const ambient = new THREE.AmbientLight(0x404040, 0.3);
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(5, 10, 7);
scene.add(ambient);
scene.add(dirLight);ALWAYS combine AmbientLight with at least one directional, point, or spot light.
---
AP-2: Forgetting to Add light.target to the Scene
Wrong:
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(10, 20, 10);
dirLight.target.position.set(5, 0, 5); // Target repositioned but NOT in scene
scene.add(dirLight);
// Light still points at (0,0,0) — target position is ignoredWhy it fails: The target is an Object3D whose world matrix must be updated by the renderer. If the target is not added to the scene graph, its world position is NEVER computed, so the light direction remains unchanged.
Correct:
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(10, 20, 10);
scene.add(dirLight);
scene.add(dirLight.target); // MUST add to scene
dirLight.target.position.set(5, 0, 5); // Now this takes effectThis applies to BOTH DirectionalLight.target and SpotLight.target.
---
AP-3: Using RectAreaLight Without Init
Wrong:
const rectLight = new THREE.RectAreaLight(0xffffff, 5, 4, 10);
rectLight.position.set(0, 5, 0);
rectLight.lookAt(0, 0, 0);
scene.add(rectLight);
// RectAreaLight renders incorrectly or not at allWhy it fails: RectAreaLight requires precomputed LTC (Linearly Transformed Cosine) lookup textures that are NOT loaded by default. Without initialization, the shader lacks the data needed to evaluate area light integrals.
Correct:
import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
RectAreaLightUniformsLib.init(); // MUST call once before creating any RectAreaLight
const rectLight = new THREE.RectAreaLight(0xffffff, 5, 4, 10);
rectLight.position.set(0, 5, 0);
rectLight.lookAt(0, 0, 0);
scene.add(rectLight);For WebGPURenderer, use RectAreaLightTexturesLib instead.
---
AP-4: RectAreaLight with Non-PBR Materials
Wrong:
RectAreaLightUniformsLib.init();
const rectLight = new THREE.RectAreaLight(0xffffff, 5, 4, 10);
scene.add(rectLight);
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshPhongMaterial({ color: 0xff0000 }) // NOT PBR
);
scene.add(mesh);
// RectAreaLight has NO effect on MeshPhongMaterialWhy it fails: RectAreaLight ONLY works with MeshStandardMaterial and MeshPhysicalMaterial. The LTC evaluation shader is only injected into PBR material programs.
Correct:
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
);---
AP-5: Too Many Shadow-Casting PointLights
Wrong:
for (let i = 0; i < 5; i++) {
const light = new THREE.PointLight(0xffffff, 500, 15, 2);
light.castShadow = true;
light.position.set(i * 3, 3, 0);
scene.add(light);
}
// 5 PointLights x 6 shadow passes = 30 shadow map renders per frameWhy it fails: Each shadow-casting PointLight renders the scene from 6 directions (cubemap faces). Five such lights produce 30 shadow render passes per frame, causing severe frame rate drops.
Correct:
// Use SpotLights instead — only 1 shadow pass each
for (let i = 0; i < 5; i++) {
const light = new THREE.SpotLight(0xffffff, 500, 15, Math.PI / 4, 0.3, 2);
light.castShadow = true;
light.position.set(i * 3, 3, 0);
scene.add(light);
scene.add(light.target);
}NEVER use more than 1-2 shadow-casting PointLights. Prefer SpotLights with shadows.
---
AP-6: Physically Correct Intensity Without Tone Mapping
Wrong:
const sun = new THREE.DirectionalLight(0xffffff, 50000); // 50k lux
scene.add(sun);
// renderer.toneMapping is NoToneMapping (default)
// Scene is completely blown out to whiteWhy it fails: In r160+, intensity values represent real physical units. A DirectionalLight at 50,000 lux produces values far above the displayable [0,1] range. Without tone mapping, these values clamp to white.
Correct:
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.5;
const sun = new THREE.DirectionalLight(0xffffff, 50000);
scene.add(sun);ALWAYS set renderer.toneMapping when using physically correct intensity values.
---
AP-7: Not Disposing PMREMGenerator
Wrong:
const pmrem = new THREE.PMREMGenerator(renderer);
new RGBELoader().load('env.hdr', (texture) => {
const envMap = pmrem.fromEquirectangular(texture);
scene.environment = envMap.texture;
texture.dispose();
// pmrem is never disposed — GPU memory leak
});Why it fails: PMREMGenerator allocates render targets and framebuffers on the GPU. Without calling .dispose(), these resources are NEVER freed, leading to GPU memory leaks that accumulate when loading multiple environments.
Correct:
const pmrem = new THREE.PMREMGenerator(renderer);
new RGBELoader().load('env.hdr', (texture) => {
const envMap = pmrem.fromEquirectangular(texture);
scene.environment = envMap.texture;
texture.dispose();
pmrem.dispose(); // ALWAYS dispose after generating env maps
});---
AP-8: Setting SpotLight penumbra to 0
Wrong:
const spot = new THREE.SpotLight(0xffffff, 1000);
spot.angle = Math.PI / 6;
spot.penumbra = 0; // Default — produces harsh, unrealistic cone edge
scene.add(spot);Why it fails: A penumbra of 0 creates a perfectly sharp cone boundary, which looks artificial and jarring. Real-world spotlights ALWAYS have some edge softening.
Correct:
spot.penumbra = 0.3; // Soft, realistic edge falloffALWAYS use penumbra >= 0.1 for realistic spotlights.
---
AP-9: Confusing scene.environment and scene.background
Wrong:
scene.background = envMap.texture;
// Expects PBR reflections, but only set background — no IBL lightingWhy it fails: scene.background ONLY controls the visible backdrop. It does NOT affect PBR material reflections or ambient lighting. For IBL, you MUST set scene.environment.
Correct:
scene.environment = envMap.texture; // IBL reflections + ambient
scene.background = envMap.texture; // Visible backdrop (optional)scene.environment drives PBR lighting. scene.background is purely visual.
---
AP-10: Orienting RectAreaLight with rotation Instead of lookAt
Wrong:
const rectLight = new THREE.RectAreaLight(0xffffff, 5, 4, 10);
rectLight.position.set(0, 5, 0);
rectLight.rotation.x = -Math.PI / 2; // Confusing, error-prone
scene.add(rectLight);Why it fails: RectAreaLight emits from its local Z-axis. Setting rotation manually requires understanding the local coordinate system and is error-prone with multiple rotations.
Correct:
const rectLight = new THREE.RectAreaLight(0xffffff, 5, 4, 10);
rectLight.position.set(0, 5, 0);
rectLight.lookAt(0, 0, 0); // Clear, intuitive — light faces the target point
scene.add(rectLight);ALWAYS use .lookAt() to orient RectAreaLights.
---
AP-11: Forgetting to Update Light Helpers
Wrong:
const helper = new THREE.DirectionalLightHelper(dirLight, 5);
scene.add(helper);
// Later, change light color or position...
dirLight.color.set(0xff0000);
dirLight.position.set(10, 20, 10);
// Helper still shows old stateWhy it fails: Some light helpers do NOT auto-update when light properties change. The helper caches its visual representation at creation time.
Correct:
dirLight.color.set(0xff0000);
dirLight.position.set(10, 20, 10);
helper.update(); // ALWAYS call after changing light propertiesthreejs-impl-lighting — Examples Reference
Lighting recipes for common scenarios. All examples use ES module imports and Three.js r160+.
---
Example 1: Outdoor Sunlight Scene
Simulates natural daylight with a sun, sky hemisphere, and HDR environment.
import * as THREE from 'three';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
// Renderer with tone mapping for physically correct values
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.8;
const scene = new THREE.Scene();
// 1. Hemisphere light for sky/ground ambient
const hemi = new THREE.HemisphereLight(0x87ceeb, 0x362d1b, 0.5);
scene.add(hemi);
// 2. Directional light as sun (high lux for physically correct)
const sun = new THREE.DirectionalLight(0xfff4e6, 3);
sun.position.set(50, 100, 75);
scene.add(sun);
scene.add(sun.target); // REQUIRED — target defaults to (0,0,0)
// 3. HDR environment for reflections on PBR materials
const pmrem = new THREE.PMREMGenerator(renderer);
new RGBELoader().load('outdoor_field.hdr', (hdrTexture) => {
const envMap = pmrem.fromEquirectangular(hdrTexture);
scene.environment = envMap.texture;
scene.background = envMap.texture;
scene.backgroundBlurriness = 0.05; // Slight blur for depth
hdrTexture.dispose();
pmrem.dispose();
});Key points:
- HemisphereLight provides gradient ambient (blue sky to brown ground)
- DirectionalLight simulates parallel sunlight rays
- HDR environment handles PBR reflections automatically
- Tone mapping REQUIRED to prevent blown-out whites
---
Example 2: Indoor Room Lighting
Multiple light sources simulating a residential room with ceiling light and table lamp.
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
const scene = new THREE.Scene();
// 1. Low ambient for base illumination (simulates light bounce)
const ambient = new THREE.AmbientLight(0xfff5e6, 0.15);
scene.add(ambient);
// 2. Ceiling light — SpotLight pointing downward
const ceiling = new THREE.SpotLight(0xffeedd, 800);
ceiling.position.set(0, 3.5, 0);
ceiling.angle = Math.PI / 4;
ceiling.penumbra = 0.5; // Soft cone edges
ceiling.decay = 2;
ceiling.distance = 10;
ceiling.target.position.set(0, 0, 0);
scene.add(ceiling);
scene.add(ceiling.target);
// 3. Table lamp — warm PointLight with limited range
const lamp = new THREE.PointLight(0xffaa44, 400, 5, 2);
lamp.position.set(-2, 1.2, 1);
scene.add(lamp);
// 4. Window light (optional) — DirectionalLight for sunlight through window
const windowLight = new THREE.DirectionalLight(0xfff8f0, 1.5);
windowLight.position.set(-5, 4, -2);
scene.add(windowLight);
scene.add(windowLight.target);Key points:
- Low ambient simulates indirect light bounce
- SpotLight for focused overhead fixture with soft penumbra
- PointLight for omnidirectional table lamp with distance falloff
- Warm color temperatures (0xffeedd, 0xffaa44) for cozy interior feel
---
Example 3: Studio / Product Photography
Three-point lighting setup for showcasing 3D models.
import * as THREE from 'three';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
// 1. Key light — main illumination from upper-left
const keyLight = new THREE.DirectionalLight(0xffffff, 4);
keyLight.position.set(-3, 5, 5);
scene.add(keyLight);
scene.add(keyLight.target);
// 2. Fill light — softer, from opposite side to reduce harsh shadows
const fillLight = new THREE.DirectionalLight(0x8888ff, 1.5);
fillLight.position.set(3, 3, 3);
scene.add(fillLight);
scene.add(fillLight.target);
// 3. Rim/back light — highlights edges from behind
const rimLight = new THREE.DirectionalLight(0xffddcc, 2);
rimLight.position.set(0, 3, -5);
scene.add(rimLight);
scene.add(rimLight.target);
// 4. Environment map for PBR reflections (studio HDRI)
const pmrem = new THREE.PMREMGenerator(renderer);
new RGBELoader().load('studio_small.hdr', (hdrTexture) => {
const envMap = pmrem.fromEquirectangular(hdrTexture);
scene.environment = envMap.texture;
scene.environmentIntensity = 0.5; // Subtle IBL, let directional lights dominate
hdrTexture.dispose();
pmrem.dispose();
});Key points:
- Three-point lighting: key, fill, rim
- Key light is brightest, fill is 30-50% of key intensity
- Rim light creates edge highlights for object separation
- Environment map at reduced intensity for subtle reflections
- Dark background for product showcase contrast
---
Example 4: Product Showcase with RectAreaLight
Soft panel lighting for e-commerce or product visualization.
import * as THREE from 'three';
import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// MUST init before creating any RectAreaLight
RectAreaLightUniformsLib.init();
// 1. Large overhead softbox
const topPanel = new THREE.RectAreaLight(0xffffff, 8, 6, 6);
topPanel.position.set(0, 5, 0);
topPanel.lookAt(0, 0, 0);
scene.add(topPanel);
// 2. Left fill panel (slightly warm)
const leftPanel = new THREE.RectAreaLight(0xfff5e6, 4, 3, 4);
leftPanel.position.set(-4, 2, 2);
leftPanel.lookAt(0, 0, 0);
scene.add(leftPanel);
// 3. Right accent panel (slightly cool)
const rightPanel = new THREE.RectAreaLight(0xe6f0ff, 3, 2, 3);
rightPanel.position.set(4, 2, -1);
rightPanel.lookAt(0, 0, 0);
scene.add(rightPanel);
// Debug helpers (remove in production)
scene.add(new RectAreaLightHelper(topPanel));
scene.add(new RectAreaLightHelper(leftPanel));
scene.add(new RectAreaLightHelper(rightPanel));Key points:
RectAreaLightUniformsLib.init()MUST be called before any RectAreaLight creation- RectAreaLights produce soft, diffused illumination like photography softboxes
- Orient using
.lookAt()-- rotation properties are NOT intuitive for area lights - Works ONLY with MeshStandardMaterial and MeshPhysicalMaterial
- RectAreaLight CANNOT cast shadows
---
Example 5: Environment-Only Lighting (Minimal Setup)
The simplest high-quality lighting: HDR environment map only, no analytic lights.
import * as THREE from 'three';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
const scene = new THREE.Scene();
const pmrem = new THREE.PMREMGenerator(renderer);
pmrem.compileEquirectangularShader(); // Pre-compile for faster first use
new RGBELoader()
.setDataType(THREE.HalfFloatType) // Better precision for HDR
.load('environment.hdr', (hdrTexture) => {
const envMap = pmrem.fromEquirectangular(hdrTexture);
scene.environment = envMap.texture; // Drives all PBR lighting
scene.background = envMap.texture; // Visible backdrop
scene.backgroundBlurriness = 0.0; // Sharp environment
scene.environmentIntensity = 1.0; // Full IBL strength
scene.environmentRotation.set(0, Math.PI / 4, 0); // Rotate light direction
hdrTexture.dispose();
pmrem.dispose();
});
// All PBR materials will automatically receive:
// - Diffuse irradiance (ambient lighting)
// - Specular reflections (mirror-like highlights)
// - No additional lights needed for many use casesKey points:
- Environment map alone provides complete PBR lighting
scene.environmentRotationrotates light direction without extra lightsscene.environmentIntensitycontrols ambient contributionHalfFloatTypepreserves HDR dynamic range- Pre-compile shader with
pmrem.compileEquirectangularShader()to avoid first-frame stutter - ALWAYS dispose both the source texture and PMREMGenerator
threejs-impl-lighting — Methods Reference
Complete API signatures for all light types, helpers, and PMREMGenerator in Three.js r160+.
---
Light Base Class
// Abstract base — do NOT instantiate directly
class Light extends Object3D {
color: Color; // Default: 0xffffff
intensity: number; // Default: 1
readonly isLight: boolean; // ALWAYS true
dispose(): void; // Release GPU resources
}---
AmbientLight
class AmbientLight extends Light {
constructor(color?: ColorRepresentation, intensity?: number);
// color default: 0xffffff
// intensity default: 1 (unitless multiplier)
readonly isAmbientLight: boolean; // ALWAYS true
}---
HemisphereLight
class HemisphereLight extends Light {
constructor(
skyColor?: ColorRepresentation, // Default: 0xffffff
groundColor?: ColorRepresentation, // Default: 0xffffff
intensity?: number // Default: 1 (unitless)
);
groundColor: Color;
readonly isHemisphereLight: boolean; // ALWAYS true
}---
DirectionalLight
class DirectionalLight extends Light {
constructor(
color?: ColorRepresentation, // Default: 0xffffff
intensity?: number // Default: 1 (lux in r160+)
);
shadow: DirectionalLightShadow;
target: Object3D; // MUST add to scene for repositioning
readonly isDirectionalLight: boolean; // ALWAYS true
}Intensity unit: Lux (lm/m2). Outdoor sunlight = 50,000-100,000 lux.
---
PointLight
class PointLight extends Light {
constructor(
color?: ColorRepresentation, // Default: 0xffffff
intensity?: number, // Default: 1 (candela in r160+)
distance?: number, // Default: 0 (infinite, inverse-square)
decay?: number // Default: 2 (physically correct)
);
decay: number; // 2 = inverse-square law
distance: number; // 0 = no limit
power: number; // Lumens = intensity * 4 * Math.PI
shadow: PointLightShadow;
readonly isPointLight: boolean; // ALWAYS true
}Distance behavior:
distance === 0: Inverse-square falloff, infinite rangedistance > 0: Smooth attenuation to zero at cutoff (NOT physically correct, artistic control)
---
SpotLight
class SpotLight extends Light {
constructor(
color?: ColorRepresentation, // Default: 0xffffff
intensity?: number, // Default: 1 (candela in r160+)
distance?: number, // Default: 0 (infinite)
angle?: number, // Default: Math.PI / 3, max Math.PI / 2
penumbra?: number, // Default: 0 (sharp edge), range [0, 1]
decay?: number // Default: 2
);
angle: number; // Cone half-angle, capped at Math.PI / 2
decay: number;
distance: number;
map: Texture | null; // Cookie texture, REQUIRES castShadow = true
penumbra: number; // Edge softness [0, 1]
power: number; // Lumens = intensity * Math.PI
shadow: SpotLightShadow;
target: Object3D; // MUST add to scene for repositioning
readonly isSpotLight: boolean; // ALWAYS true
}---
RectAreaLight
class RectAreaLight extends Light {
constructor(
color?: ColorRepresentation, // Default: 0xffffff
intensity?: number, // Default: 1 (nits = cd/m2 in r160+)
width?: number, // Default: 10
height?: number // Default: 10
);
width: number;
height: number;
power: number; // Lumens
readonly isRectAreaLight: boolean; // ALWAYS true
}Requirements:
- WebGLRenderer: MUST call
RectAreaLightUniformsLib.init()before use - WebGPURenderer: MUST use
RectAreaLightTexturesLibinstead - Works ONLY with
MeshStandardMaterialandMeshPhysicalMaterial - Orient using
.position.set()and.lookAt(), NOT rotation - CANNOT cast shadows
---
LightProbe
class LightProbe extends Light {
constructor(
sh?: SphericalHarmonics3, // Spherical harmonics data
intensity?: number // Default: 1
);
readonly isLightProbe: boolean; // ALWAYS true
}LightProbeGenerator (addon)
// Import: 'three/addons/lights/LightProbeGenerator.js'
class LightProbeGenerator {
static fromCubeTexture(cubeTexture: CubeTexture): LightProbe;
static fromCubeRenderTarget(renderer: WebGLRenderer, target: WebGLCubeRenderTarget): LightProbe;
}---
RectAreaLightUniformsLib (addon)
// Import: 'three/addons/lights/RectAreaLightUniformsLib.js'
class RectAreaLightUniformsLib {
static init(): void; // MUST call once before creating any RectAreaLight
}---
PMREMGenerator
class PMREMGenerator {
constructor(renderer: WebGLRenderer);
fromEquirectangular(
equirectangular: Texture,
renderTarget?: WebGLRenderTarget
): WebGLRenderTarget;
// Returns render target with .texture for scene.environment
fromScene(
scene: Scene,
sigma?: number, // Blur sigma, default 0
near?: number, // Default 0.1
far?: number // Default 100
): WebGLRenderTarget;
fromCubemap(
cubemap: CubeTexture,
renderTarget?: WebGLRenderTarget
): WebGLRenderTarget;
compileCubemapShader(): void; // Pre-compile for faster first use
compileEquirectangularShader(): void;
dispose(): void; // ALWAYS call after generating env maps
}---
Light Helpers
DirectionalLightHelper
// Import: 'three'
class DirectionalLightHelper extends Object3D {
constructor(
light: DirectionalLight,
size?: number, // Default: 1
color?: ColorRepresentation // Default: light.color
);
light: DirectionalLight;
update(): void; // Call after changing light properties
dispose(): void;
}SpotLightHelper
// Import: 'three'
class SpotLightHelper extends Object3D {
constructor(
light: SpotLight,
color?: ColorRepresentation // Default: light.color
);
light: SpotLight;
update(): void;
dispose(): void;
}PointLightHelper
// Import: 'three'
class PointLightHelper extends Mesh {
constructor(
light: PointLight,
sphereSize?: number, // Default: 1
color?: ColorRepresentation // Default: light.color
);
light: PointLight;
update(): void;
dispose(): void;
}HemisphereLightHelper
// Import: 'three'
class HemisphereLightHelper extends Object3D {
constructor(
light: HemisphereLight,
size: number, // REQUIRED — no default
color?: ColorRepresentation
);
light: HemisphereLight;
update(): void;
dispose(): void;
}RectAreaLightHelper (addon)
// Import: 'three/addons/helpers/RectAreaLightHelper.js'
class RectAreaLightHelper extends Line {
constructor(light: RectAreaLight);
dispose(): void;
}LightProbeHelper (addon)
// Import: 'three/addons/helpers/LightProbeHelper.js'
class LightProbeHelper extends Mesh {
constructor(
lightProbe: LightProbe,
size: number // REQUIRED — no default
);
dispose(): void;
}---
Scene Environment Properties
class Scene extends Object3D {
environment: Texture | null; // IBL for all PBR materials
background: Color | Texture | null; // Visible backdrop
backgroundBlurriness: number; // 0-1, blur background env map
backgroundIntensity: number; // Background brightness multiplier
environmentIntensity: number; // IBL contribution multiplier
environmentRotation: Euler; // Rotate the environment map
}---
RGBELoader (addon)
// Import: 'three/addons/loaders/RGBELoader.js'
class RGBELoader extends DataTextureLoader {
load(
url: string,
onLoad?: (texture: DataTexture) => void,
onProgress?: (event: ProgressEvent) => void,
onError?: (event: ErrorEvent) => void
): DataTexture;
loadAsync(url: string, onProgress?: Function): Promise<DataTexture>;
setDataType(type: number): this; // HalfFloatType recommended
}EXRLoader (addon)
// Import: 'three/addons/loaders/EXRLoader.js'
class EXRLoader extends DataTextureLoader {
load(
url: string,
onLoad?: (texture: DataTexture) => void,
onProgress?: (event: ProgressEvent) => void,
onError?: (event: ErrorEvent) => void
): DataTexture;
loadAsync(url: string, onProgress?: Function): Promise<DataTexture>;
setDataType(type: number): this;
}