Lighting

Lighting system with directional, point, and spot lights, cascaded shadow maps, environment mapping, and light probes.

Web Engine's lighting system provides realistic illumination using a combination of direct lights, shadows, environment maps, and global illumination techniques. Built on Three.js, it supports all standard light types with advanced shadow mapping.

Light Types#

Directional Light

Parallel rays from infinite distance, like the sun

Point Light

Omnidirectional light from a single point

Spot Light

Cone-shaped light with distance falloff

Ambient Light

Uniform light from all directions

Hemisphere Light

Sky/ground color gradient

Area Light

Rectangular light source (rect area only)

Directional Light#

Directional lights simulate the sun or moon, with parallel rays from an infinite distance. Perfect for outdoor scenes.

import * as THREE from 'three';
// Create directional light (sun)
const sunLight = new THREE.DirectionalLight(
0xffffff, // Color (white)
1.0 // Intensity
);
// Position (direction is from position to target)
sunLight.position.set(50, 100, 50);
sunLight.target.position.set(0, 0, 0);
// Enable shadows
sunLight.castShadow = true;
// Configure shadow camera (orthographic frustum)
sunLight.shadow.camera.left = -100;
sunLight.shadow.camera.right = 100;
sunLight.shadow.camera.top = 100;
sunLight.shadow.camera.bottom = -100;
sunLight.shadow.camera.near = 0.5;
sunLight.shadow.camera.far = 500;
// Shadow map resolution
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
// Shadow bias to reduce artifacts
sunLight.shadow.bias = -0.0001;
scene.add(sunLight);
scene.add(sunLight.target);

Shadow Camera Size

The shadow camera bounds (left, right, top, bottom) determine the shadow map coverage. Make them large enough to cover your scene, but not too large or shadow quality will suffer.

Point Light#

Point lights emit light in all directions from a single point, like a light bulb. They support distance falloff and shadows.

// Create point light
const pointLight = new THREE.PointLight(
0xff6600, // Orange color
1.0, // Intensity
100, // Distance (0 = infinite)
2.0 // Decay factor (physically correct = 2)
);
pointLight.position.set(10, 5, 0);
// Enable shadows (uses cubemap)
pointLight.castShadow = true;
pointLight.shadow.mapSize.width = 512;
pointLight.shadow.mapSize.height = 512;
pointLight.shadow.camera.near = 0.5;
pointLight.shadow.camera.far = 100;
scene.add(pointLight);
// Optional: Add visual helper
const helper = new THREE.PointLightHelper(pointLight, 0.5);
scene.add(helper);

Point Light Shadows

Point light shadows require rendering the scene 6 times (one for each cubemap face), making them expensive. Use sparingly or disable shadows for distant point lights.

Spot Light#

Spot lights emit a cone of light, like a flashlight or stage light. They support angle, penumbra, and distance falloff.

// Create spot light
const spotLight = new THREE.SpotLight(
0xffffff, // Color
1.0, // Intensity
100, // Distance
Math.PI / 4, // Angle (45 degrees)
0.5, // Penumbra (soft edge)
2.0 // Decay
);
spotLight.position.set(0, 10, 0);
spotLight.target.position.set(0, 0, 0);
// Enable shadows
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.near = 1;
spotLight.shadow.camera.far = 100;
spotLight.shadow.camera.fov = 45;
scene.add(spotLight);
scene.add(spotLight.target);

Ambient and Hemisphere Lights#

Ambient lights provide uniform illumination from all directions, while hemisphere lights create a sky/ground color gradient.

// Ambient light (uniform)
const ambientLight = new THREE.AmbientLight(
0x404040, // Dark gray
0.5 // Intensity
);
scene.add(ambientLight);
// Hemisphere light (sky/ground gradient)
const hemisphereLight = new THREE.HemisphereLight(
0x87ceeb, // Sky color (light blue)
0x543210, // Ground color (brown)
0.6 // Intensity
);
hemisphereLight.position.set(0, 50, 0);
scene.add(hemisphereLight);

Shadow Mapping#

Web Engine uses shadow mapping to render realistic shadows. Configure shadow properties for quality and performance:

PropertyEffectRecommended
mapSizeShadow resolution1024-2048 for directional, 512 for point/spot
biasReduce shadow acne-0.0001 to -0.001
normalBiasReduce peter panning0 to 0.05
radiusBlur amount (PCF)1-3 for soft shadows
camera.near/farShadow depth rangeMatch scene bounds
// Enable shadows on renderer
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Soft shadows
// Mark objects to cast/receive shadows
mesh.castShadow = true;
mesh.receiveShadow = true;
// Configure light shadows
light.castShadow = true;
light.shadow.bias = -0.0001;
light.shadow.normalBias = 0.02;
light.shadow.radius = 2;
light.shadow.mapSize.set(2048, 2048);

Shadow Map Types#

TypeQualityPerformanceUse Case
BasicShadowMapLow (hard edges)FastMobile/low-end
PCFShadowMapMedium (soft)MediumDefault
PCFSoftShadowMapHigh (very soft)SlowDesktop/high-end
VSMShadowMapHigh (soft + blur)SlowAdvanced effects

Cascaded Shadow Maps (CSM)#

CSM provides high-quality shadows at all distances by splitting the camera frustum into multiple cascades, each with its own shadow map:

import { Environment } from '@web-engine-dev/core/engine/ecs/components';
// Enable CSM in the environment
const envEntity = createEntity(world);
addComponent(world, envEntity, Environment);
// Configure CSM
Environment.csmEnabled[envEntity] = 1; // Enable CSM
Environment.csmIntensity[envEntity] = 1.0; // Shadow strength
// Sun position (light direction)
Environment.sunPosition[envEntity][0] = 50; // x
Environment.sunPosition[envEntity][1] = 100; // y
Environment.sunPosition[envEntity][2] = 50; // z
// CSM automatically:
// - Creates 3 shadow cascades
// - Uses 2048x2048 shadow maps per cascade
// - Updates shadow cameras each frame
// - Handles cascade transitions smoothly

CSM Performance

CSM renders the scene 3 times (once per cascade), but provides excellent shadow quality at all distances. The engine uses "practical" cascade distribution for balanced near/far quality.

Environment Maps#

Environment maps provide reflections and ambient lighting from the surrounding environment:

import * as THREE from 'three';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
// Load HDR environment map
const rgbeLoader = new RGBELoader();
rgbeLoader.load('environment.hdr', (texture) => {
// Convert to cubemap for reflections
const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
// Apply to scene
scene.environment = envMap; // IBL lighting
scene.background = envMap; // Skybox
// Apply to materials
material.envMap = envMap;
material.envMapIntensity = 1.0;
pmremGenerator.dispose();
texture.dispose();
});

Skybox#

Create a skybox using a cubemap or equirectangular texture:

// Cubemap skybox
const cubeTextureLoader = new THREE.CubeTextureLoader();
const skybox = cubeTextureLoader.load([
'px.jpg', 'nx.jpg', // +x, -x
'py.jpg', 'ny.jpg', // +y, -y
'pz.jpg', 'nz.jpg', // +z, -z
]);
scene.background = skybox;
// Equirectangular skybox
const textureLoader = new THREE.TextureLoader();
const equirect = textureLoader.load('sky.jpg');
equirect.mapping = THREE.EquirectangularReflectionMapping;
scene.background = equirect;

Light Probes#

Light probes capture lighting information at specific points for image-based lighting:

import { LightProbeGenerator } from 'three/examples/jsm/lights/LightProbeGenerator.js';
// Create light probe from cubemap
const cubeCamera = new THREE.CubeCamera(0.1, 100, 256);
cubeCamera.position.set(0, 5, 0);
scene.add(cubeCamera);
// Update cubemap
cubeCamera.update(renderer, scene);
// Generate light probe
const lightProbe = LightProbeGenerator.fromCubeRenderTarget(
renderer,
cubeCamera.renderTarget
);
scene.add(lightProbe);
// Influence objects near the probe position
lightProbe.intensity = 1.0;

Global Illumination#

Achieve realistic indirect lighting using environment maps and light probes:

Image-Based Lighting (IBL)#

Use HDR environment maps for realistic ambient lighting:

// HDR environment for IBL
scene.environment = hdrEnvironmentMap;
// Materials automatically use environment for:
// - Ambient lighting (indirect diffuse)
// - Reflections (indirect specular)
// Control intensity per material
material.envMapIntensity = 1.0; // Full strength
material.envMapIntensity = 0.5; // Half strength

Ambient Occlusion#

Bake AO maps or use SSAO post-processing for contact shadows:

// Baked AO map
material.aoMap = aoTexture;
material.aoMapIntensity = 1.0;
// Requires second UV channel
geometry.setAttribute('uv2', geometry.attributes.uv);
// Or use SSAO post-processing (see Post-Processing docs)
const ssaoPass = new SSAOPass(scene, camera);
composer.addPass(ssaoPass);

Lighting Performance#

TechniqueCostNotes
Directional LightLowOne light per scene recommended
Point LightMediumAvoid many overlapping point lights
Spot LightMediumSimilar cost to point lights
Point Light ShadowsHigh6 shadow maps per light
CSM ShadowsMedium3 shadow maps total
Environment MapLowOne-time setup, no per-frame cost

Optimization Tips#

  • Limit dynamic lights to 3-5 per scene (1 directional + 2-4 point/spot)
  • Use baked lighting for static objects (lightmaps, AO)
  • Disable shadows for small or distant lights
  • Reduce shadow map resolution for mobile (512-1024)
  • Use hemisphere light instead of ambient for outdoor scenes
  • Combine multiple lights into environment maps when possible

Light Count Limits

Three.js limits the number of lights per material (default: 16 total, 4 shadows). Exceeding these limits can cause lights to be ignored. Use baked lighting or light probes for static illumination.

Best Practices#

Light Setup#

  • Use one directional light as the sun with CSM shadows
  • Add 2-3 point/spot lights for accents and local illumination
  • Use ambient or hemisphere light for fill lighting
  • Apply environment map for reflections and IBL
  • Position lights to create depth and visual interest

Shadow Quality#

  • Use 2048x2048 for primary directional light shadows
  • Use 512-1024 for secondary light shadows
  • Adjust shadow camera bounds to fit your scene tightly
  • Use shadow bias to eliminate shadow acne
  • Enable PCFSoftShadowMap for smooth shadow edges

Mobile Optimization#

  • Reduce shadow map resolution to 512-1024
  • Use BasicShadowMap instead of PCF on low-end devices
  • Limit to 1-2 dynamic lights with shadows
  • Use baked lightmaps for static geometry
  • Disable shadows entirely on very low-end devices
Rendering | Web Engine Docs | Web Engine Docs