Selective Post-Processing in Three.js Using Mask Pass
Applying post-processing effects to specific objects in a Three.js
scene—rather than the entire viewport—is a common requirement for
creating advanced visual highlights, glowing elements, or localized
shaders. This article explains how to achieve selective post-processing
using layers, the EffectComposer, and the
MaskPass (accompanied by ClearMaskPass) to
isolate rendering effects to a designated subset of your 3D objects.
The Core Concept
To apply an effect to only specific objects, you must define which
pixels on the screen those objects occupy. The MaskPass
achieves this by writing the stencil buffer based on the geometry of
your selected objects. Any subsequent post-processing passes will only
render within the boundaries of that stencil mask.
To implement this, you will use Three.js Layers to separate your standard objects from your masked objects, allowing a single camera to view different sets of objects during different rendering passes.
Step-by-Step Implementation
1. Set Up the Layers
Three.js objects have a .layers property. By default,
all objects and cameras belong to Layer 0. Allocate a separate layer
(e.g., Layer 1) for the objects that should receive the selective
post-processing effect.
const DEFAULT_LAYER = 0;
const SELECTIVE_LAYER = 1;
// Create standard object
const backgroundBox = new THREE.Mesh(geometry, standardMaterial);
backgroundBox.layers.set(DEFAULT_LAYER);
scene.add(backgroundBox);
// Create the object that will receive the effect
const glowingSphere = new THREE.Mesh(geometry, effectMaterial);
glowingSphere.layers.enable(SELECTIVE_LAYER); // Object now exists on both 0 and 1
scene.add(glowingSphere);2. Configure the EffectComposer and Passes
Import the necessary post-processing files. You will need
EffectComposer, RenderPass,
MaskPass, ClearMaskPass, and the shader/effect
pass you wish to apply (e.g., ShaderPass or
GlitchPass).
Ensure your WebGLRenderer has stencil enabled, as the
MaskPass relies on the stencil buffer to mask the
pixels.
const renderer = new THREE.WebGLRenderer({ stencil: true });
const composer = new THREE.EffectComposer(renderer);3. Build the Pass Stack
The rendering pipeline must be structured so that the main scene renders first, followed by the mask definition, the localized effect, and finally the removal of the mask.
// 1. Render the entire scene normally
const renderPass = new THREE.RenderPass(scene, camera);
composer.addPass(renderPass);
// 2. Create the Mask Pass
const maskPass = new THREE.MaskPass(scene, camera);
composer.addPass(maskPass);
// 3. Create the effect pass (e.g., a custom vignette, color-shift, or blur)
const effectPass = new THREE.ShaderPass(MyCustomShader);
composer.addPass(effectPass);
// 4. Create the Clear Mask Pass to stop masking subsequent passes
const clearMaskPass = new THREE.ClearMaskPass();
composer.addPass(clearMaskPass);4. Direct the Camera via Render Hooks
By default, the MaskPass will render the entire scene
into the stencil buffer. To restrict the mask to your specific layer,
temporarily alter the camera’s layer mask before the
MaskPass executes, and restore it afterward.
You can hook into the rendering cycle of the passes:
// Before the mask pass renders, force the camera to only see the selective layer
maskPass.onBeforeRender = () => {
renderer.autoClear = false;
camera.layers.set(SELECTIVE_LAYER);
};
// After the mask pass renders, restore the camera to see the default layer
maskPass.onAfterRender = () => {
camera.layers.set(DEFAULT_LAYER);
};5. Update the Render Loop
Replace your standard renderer.render(scene, camera)
call in your animation loop with the composer’s update method.
function animate() {
requestAnimationFrame(animate);
// Update animations or controls here
// Render through the composer
composer.render();
}
animate();Performance Considerations
- Stencil Buffer Cost: Masking relies on stencil buffer operations, which are highly efficient on modern GPUs but still require an extra render call for the masked objects. Keep the geometry in the masked layer as simple as possible.
- AutoClear: Setting
renderer.autoClear = falseprevents the WebGL context from clearing the color buffer between passes, which is necessary for the effect to draw on top of the base scene. Ensure your pipeline ends with a pass that handles rendering to the screen.