Three.js Post-Processing and EffectComposer Guide

This article explains the fundamentals of post-processing in Three.js, a technique used to apply visual effects to a 3D scene after it has been rendered. You will learn what post-processing is and receive a step-by-step guide on how to construct and configure the EffectComposer to implement effects like bloom, glitch, and color correction in your web projects.

What is Post-Processing?

In standard 3D rendering, the geometry, materials, and lights of a scene are calculated and drawn directly to the screen. Post-processing changes this workflow. Instead of rendering the scene directly to the canvas, the scene is rendered to an off-screen buffer (a render target) as a 2D image.

Once the scene is saved as a 2D texture, you can apply one or more shader filters to it before displaying the final image to the user. This is similar to applying filters in Photoshop or Instagram. Common post-processing effects include:

How to Construct the EffectComposer

In Three.js, post-processing is managed by the EffectComposer class. The composer coordinates a chain of “passes” that are executed in sequence.

To use EffectComposer, you must import it along with the necessary passes from the Three.js addons directory, as they are not included in the core library.

Step 1: Import the Required Modules

First, import the core Three.js library, the EffectComposer, the foundational RenderPass, and any specific effect passes you want to apply (for example, GlitchPass).

import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';

Step 2: Initialize the Renderer, Scene, and Camera

Set up your standard Three.js environment.

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

Step 3: Construct the EffectComposer

Instantiate the EffectComposer by passing your WebGLRenderer as the argument. The composer will handle the rendering output.

const composer = new EffectComposer(renderer);

Step 4: Add the Passes

An EffectComposer needs at least one pass to work.

  1. RenderPass: This is almost always the first pass. It renders your 3D scene normally from the perspective of your camera and inputs the resulting image into the composer’s memory buffer.
  2. Effect Passes: Add any subsequent effect passes. The last pass in the chain will automatically render to the screen unless specified otherwise.
// 1. Create and add the default render pass
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);

// 2. Create and add a post-processing effect pass
const glitchPass = new GlitchPass();
composer.addPass(glitchPass);

Step 5: Update the Animation Loop

Normally, you would render a scene using renderer.render(scene, camera). When using post-processing, you must replace this call with composer.render() inside your animation loop. This tells Three.js to execute the chain of passes you configured.

function animate() {
    requestAnimationFrame(animate);

    // Cube rotation or other updates go here

    // Render the scene through the effect composer
    composer.render();
}
animate();

Step 6: Handle Window Resizing

If the browser window resizes, both the renderer and the composer must be updated to maintain the correct resolution.

window.addEventListener('resize', () => {
    const width = window.innerWidth;
    const height = window.innerHeight;

    camera.aspect = width / height;
    camera.updateProjectionMatrix();

    renderer.setSize(width, height);
    composer.setSize(width, height);
});