In this section, we'll delve into some more advanced lighting and shadow techniques that can significantly enhance the realism and atmosphere of your Three.js scenes. While basic lighting can get you started, mastering these methods will elevate your creations.
Often, a single light source isn't enough to convincingly illuminate a scene. Employing multiple lights allows for more nuanced lighting scenarios, such as simulating ambient light alongside a directional key light, or adding subtle accent lighting. Be mindful of how lights interact, as they can add up their intensities, potentially over-illuminating areas if not managed carefully.
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);While we've touched on shadows, let's explore the underlying mechanism: shadow mapping. Three.js typically uses a technique where the scene is rendered from the light's perspective to create a depth map (the shadow map). This map is then used during the main scene rendering to determine if a pixel is in shadow. Higher resolution shadow maps lead to sharper shadows but come with a performance cost.
To enable shadows for a light, you need to cast shadows and to receive shadows, you need to enable it on the mesh.
// For lights that cast shadows
directionalLight.castShadow = true;
// For meshes that cast and receive shadows
mesh.castShadow = true;
mesh.receiveShadow = true;Shadow mapping isn't perfect and can suffer from artifacts like 'peter-panning' (shadows detaching from the object) or 'acne' (false shadows appearing on surfaces). These can be mitigated by adjusting the shadow map's resolution and applying a bias. The shadow.mapSize property controls the resolution, and shadow.bias helps to push the shadow boundary away from the surface.
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.bias = -0.005;The default shadow map filtering can result in aliased, blocky shadows. Three.js offers different shadow map types for smoother results. Percentage Closer Filtering (PCF) is a popular technique that samples multiple points in the shadow map and averages the results, leading to softer, more realistic shadows.
directionalLight.shadow.type = THREE.PCFShadowMap;Other options include THREE.VSM (Variance Shadow Maps) which can provide very soft shadows, and THREE.BasicShadowMap for the fastest but least realistic option.
Spotlights and Point Lights can also cast shadows, but their shadow maps are typically smaller and more localized, reflecting their limited range. You'll need to configure their shadow properties similarly to directional lights, paying close attention to the shadow.camera's frustum to encompass the area where shadows are expected.
const spotLight = new THREE.SpotLight(0xffffff, 1, 100, Math.PI / 4, 0.1);
spotLight.position.set(0, 5, 0);
spotLight.castShadow = true;
scene.add(spotLight);graph TD
A[Light Source] --> B{Shadow Map Generation}
B --> C[Depth Buffer from Light's Perspective]
C --> D[Shadow Map Texture]
D --> E{Scene Rendering}
E --> F[Fragment Shader Checks Shadow Map]
F --> G{Pixel in Shadow?}
G -- Yes --> H[Apply Shadow Color/Darkness]
G -- No --> I[Apply Full Light Color]
H --> J[Final Pixel Color]
I --> J
Advanced lighting and shadows, especially high-resolution shadow maps and complex filtering, can significantly impact performance. Always profile your application and find a balance between visual fidelity and frame rate. Consider disabling shadows for objects that are far away or not critical to the scene's visual narrative. For static scenes, pre-baking shadows can be a highly performant alternative.