mcpField
T
threejs-impl-lighting

threejs-impl-lighting

github.com/impertio-studioDevelopment23141 installsUpdated 30w ago
Repository

About threejs-impl-lighting

A reference skill for Three.js developers implementing scene lighting. It compares light types and covers physically based intensities, environment maps, performance, and common setup pitfalls.

Key features

  1. 01Comparison of seven light types
  2. 02Physically based intensity units
  3. 03HDR environment-map workflows
  4. 04Light helpers and setup guidance
  5. 05Performance and shadow-cost advice

Use cases

  1. 01Choosing lights for an indoor or outdoor scene
  2. 02Setting up PBR environment illumination
  3. 03Diagnosing incorrect light or shadow behavior

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/impertio-studio/three.js-claude-skill-package --skill threejs-impl-lighting

After installing, type/threejs-impl-lightingin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

.gitkeep
references/
anti-patterns.md
examples.md
methods.md

Details

From the project README

threejs-impl-lighting

Quick Reference

Light Types at a Glance
LightShadowsDirectionCostIntensity Unit (r160+)
AmbientLightNONone (uniform)NegligibleUnitless multiplier
HemisphereLightNOVertical gradientNegligibleUnitless multiplier
DirectionalLightYESParallel raysModerateLux
PointLightYES (6-pass!)OmnidirectionalHighCandela
SpotLightYESConeHighCandela
RectAreaLightNOPlanar emissionVery highNits (cd/m2)
LightProbeNOSpherical harmonicsLowUnitless 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, default 0xffffff
  • .intensity (number) -- strength multiplier, default 1
  • .isLight (boolean, readonly) -- ALWAYS true
  • .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 TypeIntensity UnitDescription
DirectionalLightLux (lm/m2)Sunlight: 50,000-100,000 lux
PointLightCandela (lm/sr)100W bulb: ~1700 cd
SpotLightCandela (lm/sr)Stage spot: ~10,000 cd
RectAreaLightNits (cd/m2)LED panel: ~500-5000 nits
AmbientLightUnitlessMultiplier, no physical unit
HemisphereLightUnitlessMultiplier, 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
PropertyEffectWhen to Use
scene.environmentPBR reflections + ambient lighting on all Standard/Physical materialsALWAYS for realistic PBR
scene.backgroundVisible skybox/backdropWhen you want the HDR visible
Both set to same textureFull IBL with visible environmentMost common for product shots
scene.backgroundBlurrinessBlur the background (0-1)Depth-of-field effect on backdrop
scene.environmentIntensityScale IBL contributionFine-tune ambient level
scene.environmentRotationRotate the environment mapAdjust 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
MethodInputPurpose
fromEquirectangular(texture)Equirectangular textureConvert HDR panorama to PMREM
fromScene(scene, sigma?)Three.js SceneGenerate PMREM from a 3D scene
fromCubemap(texture)CubeTextureConvert 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

HelperImportConstructor
DirectionalLightHelperthreenew DirectionalLightHelper(light, size?, color?)
SpotLightHelperthreenew SpotLightHelper(light, color?)
PointLightHelperthreenew PointLightHelper(light, sphereSize?, color?)
HemisphereLightHelperthreenew HemisphereLightHelper(light, size, color?)
RectAreaLightHelperthree/addons/helpers/RectAreaLightHelper.jsnew RectAreaLightHelper(light)
LightProbeHelperthree/addons/helpers/LightProbeHelper.jsnew 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

CategoryBudget
Mobile WebGLMax 4-5 real-time lights total
Desktop WebGLMax 8-16 lights depending on scene
Shadow-casting PointLightsMax 1-2 (6 passes each)
RectAreaLightsMax 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

Official Sources

Comments

Markdown supported