Shadows are a fundamental aspect of realism in 3D graphics. They add depth, volume, and a sense of the environment. In Three.js, enabling and configuring shadows involves a few key steps. It's not as simple as flipping a switch; it requires careful consideration of your scene's lights and the objects that cast and receive shadows.
The first crucial step is to enable shadows for the renderer. This tells Three.js that we intend to render shadows. You do this by accessing the shadowMap property of your WebGLRenderer and setting its enabled property to true.
const renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;Not all lights in Three.js can cast shadows. The most common light types that support shadows are DirectionalLight and SpotLight. PointLight can also cast shadows, but it's often more computationally expensive. To make a light cast shadows, you need to explicitly enable this capability for that specific light.
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.castShadow = true;Similarly, not all objects will automatically cast or receive shadows. You need to tell each mesh whether it should participate in the shadow rendering process. You do this by setting the castShadow and receiveShadow properties on the Mesh object itself.
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
cube.receiveShadow = true;
const groundGeometry = new THREE.PlaneGeometry(10, 10);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.receiveShadow = true;
scene.add(cube, ground);When a light casts shadows, it needs to 'render' the scene from its perspective to generate a shadow map. This process has parameters that can significantly impact the quality and performance of your shadows. The most important are related to the shadow property of the light.
The shadow.camera is a crucial component. It's an orthographic camera that defines the frustum for rendering the shadow map. Its left, right, top, bottom, near, and far properties determine the area and resolution of the shadow. Incorrectly configured camera bounds can lead to shadows being cut off or appearing distorted. It's essential to ensure that this camera encompasses all objects that should be considered for shadow casting from the light's viewpoint.
// Example for DirectionalLight
directionalLight.shadow.camera.left = -5;
directionalLight.shadow.camera.right = 5;
directionalLight.shadow.camera.top = 5;
directionalLight.shadow.camera.bottom = -5;
directionalLight.shadow.camera.near = 0.1;
directionalLight.shadow.camera.far = 50;
// You might need to adjust these based on your scene's scale and light positionThe shadow.mapSize property controls the resolution of the shadow map. A higher resolution means sharper shadows but also uses more memory and can impact performance. It's a trade-off between visual quality and efficiency. The values are specified as Vector2 objects, representing width and height in pixels.
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;You might notice that shadows can appear jagged or aliased, especially on surfaces. Three.js offers methods to improve shadow quality, such as PCSSShadowMap (Percentage-Closer Soft Shadows) or VSMShadowMap (Variance Shadow Maps), which can produce softer and more realistic shadows. These are configured within the renderer.shadowMap.type property.
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Or THREE.VSMShadowMap, THREE.BasicShadowMap, THREE.PCFShadowMapHere's a simplified flow of how shadow rendering generally works in Three.js:
- The renderer is initialized with shadow mapping enabled.
- Lights that are configured to cast shadows are identified.
- For each shadow-casting light, a shadow map is generated by rendering the scene from the light's perspective, only considering objects that cast shadows.
- This shadow map is then used during the main scene rendering to determine which parts of other objects should be in shadow.
graph TD;
A[Renderer: shadowMap.enabled = true] --> B{Identify Shadow Casting Lights};
B --> C{For Each Shadow Casting Light};
C --> D[Render Scene from Light's POV (Shadow Map Generation)];
D --> E[Apply Shadow Map to Main Scene Rendering];
E --> F[Final Rendered Image with Shadows];
Configuring shadow cameras and map sizes often requires experimentation. Start with reasonable values and adjust them based on your specific scene and desired visual fidelity. Remember that shadow rendering can be performance-intensive, so finding the right balance is key to creating smooth and responsive 3D experiences.