Each light type adds shader code and, with shadows on, a render pass of its own, so a scene usually combines one or two direct lights with cheap fill from a hemisphere light or an environment map (Environment Maps):
| Light | How it shines | Shadows | Notes |
|---|---|---|---|
| AmbientLight | Equally everywhere | No | Flattens form; prefer a hemisphere |
| HemisphereLight | Sky color above, ground color below | No | Cheap outdoor or room fill |
| DirectionalLight | Parallel rays toward a target | Orthographic map | Sun; the chapter's 'sun' |
| PointLight | All directions from a point | Six-face cube map | Candela; decay 2 |
| SpotLight | A cone (angle, penumbra) | Perspective map | Candela; can project a map |
| RectAreaLight | From a rectangle | No | Standard and physical materials only |
| LightProbe | Irradiance as spherical harmonics | No | Baked ambient from an environment |
A DirectionalLight or SpotLight aims at light.target, an Object3D that must be in the scene when you move it. RectAreaLight needs RectAreaLightUniformsLib.init() from the add-ons with WebGLRenderer, and the SunLight add-on, new in r186, is a directional light with cascaded shadow maps for large outdoor scenes.
<!doctype html>
<style>
body { margin: 0; background: #f5f1e8; font: 12px system-ui, sans-serif; color: #263238; }
#wrap { position: relative; width: 600px; max-width: 100%; }
canvas { display: block; }
.tag { position: absolute; width: 200px; text-align: center; font-weight: 600; }
</style>
<script type="importmap">
{ "imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<div id="wrap"></div>
<script type="module">
import * as THREE from 'three';
import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js';
import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';
RectAreaLightUniformsLib.init(); // RectAreaLight needs this with WebGLRenderer
const wrap = document.getElementById('wrap');
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(600, 360);
renderer.setScissorTest(true);
wrap.appendChild(renderer.domElement);
const camera = new THREE.PerspectiveCamera(40, 200 / 180, 0.1, 30);
camera.position.set(0, 1.4, 3.4);
camera.lookAt(0, 0.2, 0);
// Each viewport gets its own scene: the same books and floor, a different light
function booksScene() {
const scene = new THREE.Scene();
scene.background = new THREE.Color('#1d2327');
const floor = new THREE.Mesh(new THREE.PlaneGeometry(6, 6), new THREE.MeshStandardMaterial({ color: '#cfc6b5' }));
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
['#1f5f8b', '#e09a10', '#b5452f'].forEach((color, i) => {
const book = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.9, 0.16), new THREE.MeshStandardMaterial({ color, roughness: 0.5 }));
book.position.set((i - 1) * 0.7, 0.45, 0);
book.rotation.y = (i - 1) * 0.4;
scene.add(book);
});
return scene;
}
const spot = new THREE.SpotLight('#ffffff', 25, 0, 0.45, 0.4); // candela; cone angle; penumbra
spot.position.set(0, 3, 1.5);
const point = new THREE.PointLight('#fff3e0', 6); // decay 2: physically correct falloff
point.position.set(0.3, 1.2, 0.8);
const sun = new THREE.DirectionalLight('#ffffff', 3);
sun.position.set(-2, 3, 2);
const area = new THREE.RectAreaLight('#ffe0b0', 6, 1.6, 0.5); // standard/physical materials only
area.position.set(0, 1.6, 1.4);
area.lookAt(0, 0.4, 0);
area.add(new RectAreaLightHelper(area));
const views = [
['AmbientLight', new THREE.AmbientLight('#ffffff', 1.2)],
['HemisphereLight', new THREE.HemisphereLight('#bfe3ff', '#8d6e63', 2)],
['DirectionalLight', sun],
['PointLight', point],
['SpotLight', spot],
['RectAreaLight', area],
].map(([name, light], i) => {
const scene = booksScene();
scene.add(light);
if (light.isSpotLight || light.isDirectionalLight) scene.add(light.target); // the aim point
if (light.isPointLight) {
const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.04), new THREE.MeshBasicMaterial({ color: '#fff3e0' }));
bulb.position.copy(light.position);
scene.add(bulb);
}
const tag = wrap.appendChild(document.createElement('div'));
tag.className = 'tag';
tag.textContent = name;
tag.style.cssText = `left:${(i % 3) * 200}px; top:${Math.floor(i / 3) * 180 + 4}px; color:#fff`;
return { scene, light, x: (i % 3) * 200, y: Math.floor(i / 3) * 180 };
});
renderer.setAnimationLoop((time) => {
point.position.x = Math.sin(time / 700) * 0.9;
views[3].scene.children.at(-1).position.copy(point.position);
spot.target.position.x = Math.sin(time / 900) * 0.8;
for (const v of views) {
// viewport y counts from the bottom of the canvas
renderer.setViewport(v.x, 360 - v.y - 180, 200, 180);
renderer.setScissor(v.x, 360 - v.y - 180, 199, 179);
renderer.render(v.scene, camera);
}
});
</script>