Shadow mapping

섀도 매핑

Rendering once more from the light’s point of view to record what it can’t see, then using that to draw shadows.

Also known as: castShadowreceiveShadowPCFSoftShadowMap
···
js
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x14141f);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(3.4, 2.6, 4.2);
camera.lookAt(0, 0.4, 0);

scene.add(new THREE.AmbientLight(0x404060, 0.8));
const light = new THREE.DirectionalLight(0xffffff, 1.6);
light.position.set(3, 5, 2);
light.castShadow = true;
light.shadow.mapSize.set(1024, 1024);
light.shadow.camera.left = -4;
light.shadow.camera.right = 4;
light.shadow.camera.top = 4;
light.shadow.camera.bottom = -4;
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 12;
light.shadow.radius = 4;
scene.add(light);

const ground = new THREE.Mesh(new THREE.PlaneGeometry(9, 9), new THREE.MeshStandardMaterial({ color: 0x2a2a3c, roughness: 0.9 }));
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);

const ball = new THREE.Mesh(new THREE.SphereGeometry(0.7, 32, 32), new THREE.MeshStandardMaterial({ color: 0xff8a4d, roughness: 0.4 }));
ball.castShadow = true;
ball.receiveShadow = true;
scene.add(ball);

const box = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.9, 0.9), new THREE.MeshStandardMaterial({ color: 0x4dc3ff, roughness: 0.4 }));
box.position.set(-1.8, 0.45, 0);
box.castShadow = true;
box.receiveShadow = true;
scene.add(box);

function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
  const time = t * 0.001;
  ball.position.y = 0.7 + Math.abs(Math.sin(time * 1.4)) * 1.1;
  box.rotation.y = time * 0.7;
  renderer.render(scene, camera);
});

A shadow is simply "a point the light can’t see." Shadow mapping implements that literally: first render the scene from the light’s point of view (not the camera’s), storing each point’s depth in a texture (the shadow map), then when rendering from the actual camera, paint a pixel as shadowed if it sits farther away than what the light "saw" at that spot.

Setup spans three places: renderer.shadowMap.enabled = true turns the feature on; mesh.castShadow = true goes on objects that should cast a shadow, mesh.receiveShadow = true on things like the floor that should show one. On the light itself, set light.castShadow = true along with light.shadow.camera (the range over which shadows get computed) sized to the scene — too wide and shadows go blocky from insufficient resolution, too narrow and objects fall outside the range and get clipped shadows.

Setting renderer.shadowMap.type to PCFSoftShadowMap softens shadow edges slightly. The demo bounces a ball up and down, and its shadow on the floor grows and fades depending on how high it is.

When to use

Whenever the scene needs to visually communicate whether an object is floating or resting on a surface — essentially every 3D scene.