Three.js Shadow Bias for Directional Lights

In Three.js, achieving realistic shadows with directional lights often requires fine-tuning the shadow.bias property. This article explains what shadow.bias is, how it resolves common rendering artifacts like “shadow acne” and “peter panning,” and how to find the optimal values for your 3D scenes.

What is Shadow Bias?

Three.js uses shadow mapping to render shadows. The engine renders the scene’s depth from the perspective of the directional light, storing this data in a texture called a shadow map. When rendering the final scene, Three.js compares the depth of each pixel with the shadow map to determine if it is in shadow.

Due to the limited resolution of shadow maps and precision limits in floating-point calculations, surfaces can incorrectly shadow themselves. This results in an unwanted visual artifact known as shadow acne, which appears as ugly, dark patterns or stripes across surfaces that should be lit.

The shadow.bias property solves this by adding a tiny offset to the depth comparison. It slightly pushes the depth calculation away from the light source, preventing surfaces from self-shadowing.

Tuning Shadow Bias: Finding the Balance

Adjusting shadow.bias is a balancing act between two common artifacts:

1. Shadow Acne (Bias is too low)

If your shadow.bias is set to 0 (the default) or is too low, you will see shadow acne. To fix this, you gradually increase the bias value.

// Example of applying shadow bias to a directional light
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.castShadow = true;

// Start with a very small positive value
directionalLight.shadow.bias = -0.0005; 

2. Peter Panning (Bias is too high)

If you increase the bias too much, you will encounter an artifact called Peter Panning (named after the character whose shadow is detached from his body). The shadow will appear to float, detached from the base of the object casting it.

To resolve Peter Panning, decrease the shadow.bias until the shadow reconnects with the object, while keeping it high enough to prevent shadow acne.

Using Normal Bias

In addition to shadow.bias, Three.js offers shadow.normalBias. While standard bias offsets the depth along the light’s direction, normal bias offsets the depth along the geometry’s surface normals.

Using shadow.normalBias is highly effective for directional lights, especially on curved surfaces, as it reduces shadow acne without causing severe Peter Panning.

// A common setup combining both properties
directionalLight.shadow.bias = -0.0005;
directionalLight.shadow.normalBias = 0.02;

Best Practices for Tuning Directional Light Shadows

To minimize the need for extreme bias values, optimize your shadow camera’s frustum first: