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 precision

Using 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:

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.