Three.js Depth Pass Using Scene overrideMaterial
This article explains how to efficiently render a depth pass in
Three.js by utilizing the Scene.overrideMaterial property.
You will learn how to temporarily force all meshes in a scene to render
using a single MeshDepthMaterial to capture depth data,
which is highly useful for post-processing effects like depth-of-field,
ambient occlusion, and custom shadow mapping.
Understanding overrideMaterial
The overrideMaterial property of a
THREE.Scene forces every object in the scene to render with
a specified material, ignoring the materials assigned to individual
meshes. This is incredibly efficient because it avoids the need to
manually traverse the scene graph to swap materials on every single 3D
object before and after rendering a depth pass.
To capture depth, you pair this property with
THREE.MeshDepthMaterial, which renders grayscale values
based on the distance of geometry from the camera’s near and far
planes.
Step-by-Step Implementation
To render a depth pass to a texture, you need a
WebGLRenderTarget to store the depth data, a depth
material, and a rendering loop that toggles the override.
1. Create the Render Target and Depth Material
First, initialize a render target to capture the output of the depth pass, and instantiate the depth material.
import * as THREE from 'three';
// Create a render target to hold the depth texture
const depthRenderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat
});
// Create the depth material
const depthMaterial = new THREE.MeshDepthMaterial();
depthMaterial.depthPacking = THREE.RGBADepthPacking; // Provides higher precisionUsing THREE.RGBADepthPacking packs the 24-bit or 32-bit
depth value across the Red, Green, Blue, and Alpha channels of the
render target, offering much higher precision than a standard 8-bit
grayscale representation.
2. Configure the Render Loop
In your animation loop, you must swap the scene’s material, render to your target, reset the material, and then render the final scene to the screen.
function animate() {
requestAnimationFrame(animate);
// 1. Enable the override material
scene.overrideMaterial = depthMaterial;
// 2. Set the render target and render the depth pass
renderer.setRenderTarget(depthRenderTarget);
renderer.clear();
renderer.render(scene, camera);
// 3. Disable the override material to restore original materials
scene.overrideMaterial = null;
// 4. Render the normal scene to the screen (or use the depth texture in a post-processing shader)
renderer.setRenderTarget(null);
renderer.render(scene, camera);
}Handling Transparency and Skinning
While overrideMaterial is incredibly fast, it applies
the exact same material settings to every object. This can cause issues
in two common scenarios:
- Alpha Map Testing: Transparent textures (like
leaves or fences) will render as solid blocks because
MeshDepthMaterialdoes not inherently know about individual alpha maps. - Skinned Meshes: Animated characters require a
material that supports skinning (
skinning: true).
If your scene contains skinned meshes or transparent textures, you
may need to bypass overrideMaterial for those specific
objects. In such cases, you can set
material.depthWrite = false on transparent objects, or
implement a manual scene traversal using scene.traverse()
to selectively swap materials only on compatible meshes.