Every light has diffuse and specular colors (white by default), an intensity (1) and setEnabled(). The types differ in where light comes from and what it can cast:
| Class | Defined by | Shadows | Typical use |
|---|---|---|---|
| HemisphericLight | Up direction, groundColor | None | Cheap ambient sky fill |
| DirectionalLight | Direction only | Orthographic map, cascades | Sun and moon |
| PointLight | Position | Cube map (six renders) | Bulbs and lamps |
| SpotLight | Position, direction, angle, exponent | Perspective map | Spotlights, torches |
| RectAreaLight | Position, width, height | None yet | Soft panels, windows |
BookNest's HemisphericLight blends diffuse from above with groundColor from below. A material takes four lights by default (maxSimultaneousLights); for dozens of small lights, ClusteredLightContainer (since 8.24) bins point and spot lights into screen tiles and depth slices (Forward+), Babylon's answer to Deferred Rendering's deferred shading.
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
canvas { width: 100%; max-width: 600px; height: 330px; display: block; touch-action: none; outline: none; }
</style>
<canvas id="view"></canvas>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script src="https://cdn.jsdelivr.net/npm/babylonjs-gui@9.28.0/babylon.gui.min.js"></script>
<script>
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color4.FromHexString('#e8e4dcff');
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2, 0.95, 9.2, new BABYLON.Vector3(0, 0.2, 0), scene);
camera.attachControl(canvas, true);
const ui = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('ui');
// Each stage: a floor tile, a book and a ball, lit only by its own light (includedOnlyMeshes)
const COLORS = ['#1f5f8b', '#e09a10', '#3f7d3a', '#b5452f'];
const stageX = (i) => (i - 1.5) * 2.6;
function stage(i, light, text) {
const x = stageX(i);
const tile = BABYLON.MeshBuilder.CreateGround('tile' + i, { width: 2.4, height: 2.4 }, scene);
tile.position.x = x;
const book = BABYLON.MeshBuilder.CreateBox('book' + i, { width: 0.6, height: 0.9, depth: 0.15 }, scene);
book.position.set(x - 0.4, 0.45, 0.3);
book.rotation.y = 0.4;
const ball = BABYLON.MeshBuilder.CreateSphere('ball' + i, { diameter: 0.7 }, scene);
ball.position.set(x + 0.45, 0.35, -0.2);
const material = new BABYLON.StandardMaterial('m' + i, scene);
material.diffuseColor = BABYLON.Color3.FromHexString(COLORS[i]);
[book, ball].forEach((mesh) => { mesh.material = material; });
tile.material = new BABYLON.StandardMaterial('tileMat' + i, scene);
light.includedOnlyMeshes = [tile, book, ball];
const label = new BABYLON.GUI.TextBlock('', text);
Object.assign(label, { color: '#333', fontSize: 12, resizeToFit: true });
ui.addControl(label);
label.linkWithMesh(tile);
label.linkOffsetY = 75;
}
// Hemispheric: diffuse from "up", groundColor from below; no position, no shadows
const hemi = new BABYLON.HemisphericLight('hemi', new BABYLON.Vector3(0, 1, 0), scene);
hemi.groundColor = new BABYLON.Color3(0.3, 0.2, 0.35);
stage(0, hemi, 'HemisphericLight\nup, groundColor');
// Directional: parallel rays from a direction, like the sun
const sun = new BABYLON.DirectionalLight('sun', new BABYLON.Vector3(1, -1, 0.5), scene);
stage(1, sun, 'DirectionalLight\ndirection only');
// Point: every direction from a position, like a bulb
const bulb = new BABYLON.PointLight('bulb', new BABYLON.Vector3(stageX(2), 1.2, -0.6), scene);
stage(2, bulb, 'PointLight\nposition');
// Spot: a cone from a position along a direction
const spot = new BABYLON.SpotLight('spot', new BABYLON.Vector3(stageX(3), 2.2, -0.4),
new BABYLON.Vector3(0, -1, 0.15), Math.PI / 4, 2, scene); // angle, exponent
stage(3, spot, 'SpotLight\ncone angle');
// Small glowing markers where the positional lights are
for (const light of [bulb, spot]) {
const marker = BABYLON.MeshBuilder.CreateSphere('marker', { diameter: 0.15 }, scene);
marker.position = light.position; // shares the vector, so it follows the light
marker.material = new BABYLON.StandardMaterial('glow', scene);
marker.material.emissiveColor = new BABYLON.Color3(1, 0.9, 0.5);
marker.material.disableLighting = true;
}
let t = 0;
scene.onBeforeRenderObservable.add(() => { // move the bulb and swing the sun
t += engine.getDeltaTime() / 1000;
bulb.position.x = stageX(2) + Math.sin(t) * 0.8;
sun.direction.set(Math.cos(t * 0.6), -1, 0.5);
});
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>