Course outline
Shadows
In one line
A shadow is a second picture, drawn from the light's point of view, that stores how far the nearest surface is. Then every pixel asks: am I farther than that? You learn the five switches that must all be on, and fix the three classic faults: no shadow, a cut-off shadow and acne.
Think of it like this
Stand where the lamp is and look at the room. Everything you can see is lit; everything hidden behind something is in shadow. Write down, for each direction, how far the nearest thing is. To check whether a spot on the floor is shadowed, compare its distance from the lamp with your list. That list is a shadow map.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- shadow map
- A texture, drawn from the light's point of view, that stores the distance to the nearest surface in each direction.
- shadow camera
- The box or cone of space a light's shadow map covers. Objects outside it cast no shadow.
- acne
- Stripes of false shadow on a surface that shadows itself because of limited depth precision.
- bias
- A small shift of the stored depth that stops acne. Too much makes shadows float.
Step by step
01Set up a shadow in five lines
The first example turns on the renderer's shadow map, the light, the ball (caster) and the floor (receiver). It also sizes the shadow camera to ±2 m.
02Find where the shadow falls
The code follows the light's ray from the ball to the floor to find the middle of the shadow and reads that pixel. A lit floor pixel far from the ball reads 219; the shadow reads 101.
03Switch things off one by one
Each row turns one thing off, reads the pixel, and turns it back on. Every row after the first prints 219 NO SHADOW: all five switches matter, and so does a shadow camera that is too small (±0.05).
04A gotcha: toggling the renderer switch
renderer.shadowMap.enabled is baked into the compiled materials. After the first frame, changing it does nothing until the materials are rebuilt with material.needsUpdate = true (the example does this). Without it the old shadow map is still read, and the shadow stays.
Try it yourself
- 1
Make the shadow maps bigger
In the third example change
1024to2048. The memory column prints16 MiB,16 MiBand96 MiB: four times the texels per map, and six maps for the point light. - 2
Make the shadow camera tight
In the first example change the four
sun.shadow.cameralines (left,right,top,bottom) from-2and2to-0.05and0.05. The first row,all five on, now prints219 NO SHADOWtoo. Look at the picture: the ball is almost outside the box, so only a tiny square of shadow is left. - 3
Make the shadow map tiny
In the first example add
sun.shadow.mapSize.set(16, 16);aftersun.castShadow = true;. The ball-shaped shadow becomes a soft, wide smudge with faint fine lines at its edge (seen in the checker's software renderer). A 4 m box over 16 texels gives 25 cm per texel, too coarse for a 0.8 m ball. The table is unchanged.
Code & diagrams
The picture is the working setup: a ball, its shadow and a floor. The table is a table of pixel readings, not of what you see.
Console
Run the scene to see its console.Expected console output
lit floor, far from the ball: 219
all five on pixel in the shadow's centre: 101 shadow
renderer.shadowMap.enabled = false pixel in the shadow's centre: 219 NO SHADOW
light.castShadow = false pixel in the shadow's centre: 219 NO SHADOW
ball.castShadow = false pixel in the shadow's centre: 219 NO SHADOW
floor.receiveShadow = false pixel in the shadow's centre: 219 NO SHADOW
shadow camera too small (±0.05) pixel in the shadow's centre: 219 NO SHADOWA double-sided floor that casts and receives shadow, a low light and a small shadow map: stripes on the first run, then the numbers fall. The measured block is 100 × 40 pixels of a 300 × 150 frame.
Console
Run the scene to see its console.Expected console output
wrongly dark pixels, out of 4000:
bias 0, normalBias 0 (the default) 3918
bias -0.005 2148
bias -0.02 0
normalBias 0.05 45
bias -0.0005 and normalBias 0.05 9The script counts how many times three.js draws into a shadow map. The point light does it six times. Draw calls stay 3 because the ball is in only one face of the cube.
Console
Run the scene to see its console.Expected console output
light shadow passes draw calls memory (about 4 bytes a texel)
DirectionalLight 1 3 4 MiB
SpotLight 1 3 4 MiB
PointLight 6 3 24 MiBBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Take the biases away
In the acne example remove the sun.shadow.bias and sun.shadow.normalBias lines (or set both to 0).
Myth vs fact
Myth
If there is no shadow, three.js is broken.
Fact
Almost always one of the five switches is off, or the object is outside the shadow camera. Go through them in order, as the first example does.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
In
src/renderers/webgl/WebGLShadowMap.js, a point light's map is aWebGLCubeRenderTargetand the render loop runsfaceCount = shadow.map.isWebGLCubeRenderTarget ? 6 : .... Each face has its own 90° camera along ±x, ±y, ±z. The example counts the sixsetRenderTargetcalls. - ▸
For closed meshes with
FrontSidematerials, three.js draws the back faces into the shadow map (shadowSide), which avoids most acne for free. That is why acne showed in the example only with aDoubleSidefloor. Settingmaterial.shadowSidechanges this.
Remember this
- 1
The problem this solves: a bike without a shadow floats; a shadow shows where it touches the ground. But shadows fail most often in three.js, usually from one missing switch and with no error message.
- 2
The method. Before the main picture, three.js renders the scene again from each shadow-casting light, into a texture holding only depth. This is the shadow map. In the main picture each pixel is moved into the light's view, and compared with the stored depth. If it is farther, something is in front of it: it is in shadow.
- 3
Five switches, all on.
renderer.shadowMap.enabled = true;light.castShadow = true;mesh.castShadow = trueon the object that makes the shadow;mesh.receiveShadow = trueon the surface that shows it; and the object must be inside the light's shadow camera, the box (for a directional light) or cone of space the shadow map covers. - 4
A cut-off shadow. The shadow camera of a
DirectionalLightis a box (left,right,top,bottom,near,far) around the target. Things outside it cast no shadow. Make it as small as the scene allows: a small box also makes every shadow texel smaller, and a sharper shadow. A blocky or smudged shadow meansshadow.mapSizeis too small for the box. - 5
Acne and bias. The depth map has limited precision, so a surface can find itself slightly 'behind' its own stored depth and shadow itself, in stripes called acne.
shadow.biasmoves the stored depth by a small number;shadow.normalBiaspushes the lookup out along the surface normal by a distance in metres. Too much of either makes the shadow float away from the object (peter-panning) or lose its thin parts. - 6
Cost. A shadow-casting light draws the casting meshes an extra time and needs a texture: a 1024 × 1024 map is about 4 MiB. A
PointLightshines in all directions, so three.js renders six maps (a cube): six passes, six times the memory. The third example prints it. A directional or spot light needs one. - 7
Soft edges.
renderer.shadowMap.typeisPCFShadowMapby default: each pixel averages several nearby depth tests, so edges are soft.BasicShadowMapis hard-edged and faster.VSMShadowMapis softer and can leak; point lights are not supported (three.js warns and skips them).PCFSoftShadowMapis gone in r186 and falls back to PCF with a warning.
Explain it without notes
A mesh casts no shadow. Give the five checks in the order you would do them.
Why does a point light cost six times the memory of a directional light at the same mapSize?
Practice
A shadow looks blocky. Name two fixes and the cost of each.
Write the lines for a directional key light at (2, 4, 3) that shadows a scene about 3 m across.
Done when you can
Name the five switches and check them in order
Size a directional light's shadow camera to a scene
Fix acne with
normalBiasandbias, and say what too much doesSay why a point light renders six shadow maps and what it costs