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.