How to Clear Depth Buffer in Three.js Post-Processing

Rendering 3D user interface (UI) elements on top of a post-processed scene in Three.js requires clearing the depth buffer mid-way through your rendering pipeline. This article explains how to configure Three.js’s EffectComposer to clear the depth buffer before rendering a UI layer, ensuring your interface elements always render on top of your 3D environment without being affected by post-processing shaders or spatial occlusion.

To achieve this effect, you must separate your 3D world and your UI elements into two distinct scenes rendered by the same EffectComposer.

Step 1: Set Up Two Scenes and Cameras

Create one scene for your main 3D world and a second scene for your UI elements. Each scene should have its own camera.

import * as THREE from 'three';

// Main 3D Scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

// UI Scene
const uiScene = new THREE.Scene();
const uiCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);

Step 2: Initialize the EffectComposer and Main Passes

Set up the EffectComposer and add your primary rendering and post-processing passes (such as bloom, film grain, or depth of field) to the main scene.

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';

const renderer = new THREE.WebGLRenderer();
const composer = new EffectComposer(renderer);

// 1. Render the main 3D scene
const mainRenderPass = new RenderPass(scene, camera);
composer.addPass(mainRenderPass);

// 2. Add post-processing effects (e.g., FXAA)
const effectPass = new ShaderPass(FXAAShader);
composer.addPass(effectPass);

Step 3: Clear the Depth Buffer

To render the UI on top of the post-processed image, you must clear the depth buffer. This prevents the depth data of the 3D scene from clipping or hiding the UI elements.

You can inject a custom pass into the composer chain that explicitly calls renderer.clearDepth().

const clearDepthPass = {
    enabled: true,
    needsSwap: false,
    renderToScreen: false,
    render: function (renderer, writeBuffer, readBuffer) {
        // Clear only the depth buffer, preserving the color buffer
        renderer.clearDepth();
    }
};

composer.addPass(clearDepthPass);

Step 4: Render the UI Scene

Finally, add a second RenderPass to draw the UI scene. Crucially, you must set the clear property of this pass to false so it does not overwrite the color buffer containing your post-processed 3D scene.

const uiRenderPass = new RenderPass(uiScene, uiCamera);

// Disable clearing color so the post-processed scene remains visible
uiRenderPass.clear = false; 

composer.addPass(uiRenderPass);

Step 5: Execute the Render Loop

Replace your standard renderer.render() call in your animation loop with composer.render().

function animate() {
    requestAnimationFrame(animate);
    
    // Update logic here...

    composer.render();
}
animate();

By placing the custom clearDepth pass directly after your post-processing effects and immediately before your UI render pass, you ensure that UI meshes are drawn with a clean depth buffer while retaining the post-processed visual output of the main 3D scene.