Apply Depth Texture with Three.js MeshDepthMaterial

This article explains how to capture and apply a depth texture to a scene in Three.js using MeshDepthMaterial. You will learn how to use this material to visualize distance from the camera, set up a render target with a depth texture, and apply that texture to objects or post-processing shaders.

Understanding MeshDepthMaterial

MeshDepthMaterial is a built-in Three.js material that renders white at the camera’s near plane and black at the far plane (or vice versa, depending on configuration). It measures the distance of geometry from the camera, making it ideal for rendering depth maps, simulating fog, creating soft shadows, or applying post-processing effects.

There are two primary ways to work with depth in Three.js: overriding the scene’s materials for a quick depth visualization, or rendering the scene depth to a texture for advanced shader usage.

Method 1: Visualizing Depth with Scene Override

The simplest way to apply MeshDepthMaterial to your entire scene is by using the scene.overrideMaterial property. This forces every object in the scene to render with the depth material without changing their original materials permanently.

import * as THREE from 'three';

// 1. Create the depth material
const depthMaterial = new THREE.MeshDepthMaterial();

// 2. Assign it to the scene override
scene.overrideMaterial = depthMaterial;

// 3. Render loop (render as normal)
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}
animate();

By default, the depth rendering will look entirely white unless your objects are close to the camera’s far clipping plane. To make the depth gradient visible, adjust your camera’s near and far properties:

camera.near = 1;
camera.far = 20;
camera.updateProjectionMatrix();

Method 2: Creating and Applying a Depth Texture

For advanced effects like custom shaders, water rendering, or depth-of-field, you need to store the depth data in a texture. This is achieved using WebGLRenderTarget and DepthTexture.

1. Set Up the Render Target and Depth Texture

Create a WebGLRenderTarget and assign a DepthTexture to its .depthTexture property.

const width = window.innerWidth;
const height = window.innerHeight;

// Create a depth texture
const depthTexture = new THREE.DepthTexture();
depthTexture.format = THREE.DepthFormat;
depthTexture.type = THREE.UnsignedIntType;

// Create a render target and attach the depth texture
const renderTarget = new THREE.WebGLRenderTarget(width, height, {
    minFilter: THREE.NearestFilter,
    magFilter: THREE.NearestFilter,
});
renderTarget.depthTexture = depthTexture;

2. Render the Scene to the Texture

In your animation loop, render the scene to the render target first to populate the depth texture, then render your final output to the screen.

function animate() {
    requestAnimationFrame(animate);

    // Render the depth information into our render target
    renderer.setRenderTarget(renderTarget);
    renderer.render(scene, camera);

    // Reset the render target to the screen
    renderer.setRenderTarget(null);

    // Now you can render your main scene or post-processing quad
    renderer.render(postScene, postCamera);
}
animate();

3. Use the Depth Texture in a Material

You can now pass the populated depthTexture as a uniform into a custom ShaderMaterial to apply depth-based effects to your scene.

const customShaderMaterial = new THREE.ShaderMaterial({
    uniforms: {
        tDepth: { value: renderTarget.depthTexture },
        cameraNear: { value: camera.near },
        cameraFar: { value: camera.far }
    },
    vertexShader: `
        varying vec2 vUv;
        void main() {
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: `
        #include <packing>
        varying vec2 vUv;
        uniform sampler2D tDepth;
        uniform float cameraNear;
        uniform float cameraFar;

        float readDepth(sampler2D depthSampler, vec2 coord) {
            float fragCoordZ = texture2D(depthSampler, coord).x;
            float viewZ = perspectiveDepthToViewZ(fragCoordZ, cameraNear, cameraFar);
            return viewZToOrthographicDepth(viewZ, cameraNear, cameraFar);
        }

        void main() {
            float depth = readDepth(tDepth, vUv);
            gl_FragColor = vec4(vec3(depth), 1.0);
        }
    `
});