Capítulo 847 de 859

Chapter 847: Shadows (Manual)

Core Idea

Three.js's default shadow maps work by re-rendering all shadow-casting objects from each shadow-casting light's point of view, which gets expensive fast (a point light alone costs 6 extra scene renders) — cheap "fake" gradient-texture shadows are a common lighter-weight alternative, and real shadow maps need their light's shadow camera frustum sized correctly or shadows get clipped.

Key Concepts

  • How shadow maps work: for every shadow-casting light, every shadow-casting object is rendered from that light's viewpoint into a shadow map; with L shadow-casting lights the scene is effectively drawn L+1 times, and a shadow-casting PointLight alone costs 6 renders (once per cube-map direction).
  • Cheaper alternatives: use only one shadow-casting light (commonly a DirectionalLight) even if the scene has several lights, or fake shadows with a soft gradient-blob texture on a plane beneath each object, fading its opacity as the object rises — cheap and used in real shipped games for exactly this reason.
  • Enabling real shadows: requires renderer.shadowMap.enabled = true, light.castShadow = true on the light, and explicit mesh.castShadow/mesh.receiveShadow flags per object (e.g. a ground plane typically only needs receiveShadow).
  • The shadow camera and clipping: a shadow-casting light has its own internal shadow camera (an OrthographicCamera for DirectionalLight, matching how directional light rays are parallel) whose frustum bounds where shadows are computed at all — content outside that box casts or receives no shadow; visualize it with a CameraHelper on light.shadow.camera.
  • Sizing the shadow frustum: adjusting the directional light's shadow camera left/right/top/bottom (and near/far) to fit the scene's actual shadow-relevant area is necessary — the default box is often too small.

Code Examples

renderer.shadowMap.enabled = true;

const light = new THREE.DirectionalLight(0xffffff, 1);
light.castShadow = true;
scene.add(light);
scene.add(light.target);

groundMesh.receiveShadow = true;
cubeMesh.castShadow = true;
cubeMesh.receiveShadow = true;

const cameraHelper = new THREE.CameraHelper(light.shadow.camera);
scene.add(cameraHelper); // visualize the shadow camera's frustum
  • What it demonstrates: the minimum setup for real directional-light shadows, plus visualizing the shadow camera's frustum to debug clipped shadows.

Key Takeaways

  1. Every shadow-casting light re-renders all shadow-casting objects from its own point of view — costs scale directly with light count, and a PointLight costs 6x on its own.
  2. A common optimization is limiting shadow-casting to one directional light even in a multi-light scene, or using cheap gradient-texture fake shadows instead of real shadow maps.
  3. Shadows require renderer.shadowMap.enabled, light.castShadow, and per-mesh castShadow/receiveShadow flags — all four pieces are needed.
  4. A directional light's shadow camera has its own frustum (visualize with CameraHelper) that must be sized to cover the actual shadow-relevant scene area, or parts of shadows will be silently clipped.

Connects To

  • DirectionalLight / PointLight / SpotLight: the three light types capable of casting shadows.
  • CameraHelper: used here to visualize a light's shadow camera frustum.