Post-processing: Bloom, FXAA, Depth of Field

Back to Modules

Post-processing in Three.js: Bloom, FXAA, and Depth of Field

Post-processing refers to effects applied to an entire rendered image after the 3D scene has been drawn. Three.js offers robust post-processing capabilities to enhance visual realism and artistic effects, including Bloom, FXAA (Fast Approximate Anti-aliasing), and Depth of Field (DoF). These effects are typically managed using the EffectComposer.

General Post-Processing Setup

To apply post-processing effects in Three.js, you generally follow these steps:

  1. Initialize EffectComposer: Instead of rendering directly to the WebGLRenderer, you render to an EffectComposer.
  2. Add RenderPass: The first pass added to the composer is usually a RenderPass, which renders your scene into the composer's internal render target.
  3. Add Effect Passes: Subsequent passes apply the desired visual effects (like Bloom, FXAA, DoF) by processing the output of the previous pass.
  4. Add OutputPass: Often, an OutputPass is added as the final pass to handle tone mapping and output the result to the screen.
  5. Render with Composer: In your animation loop, you call composer.render() instead of renderer.render().

You will need to import the necessary passes from three/examples/jsm/postprocessing/.

Bloom (Unreal Bloom)

The Bloom effect simulates the optical phenomenon where light from bright areas of an image appears to bleed into surrounding darker areas, creating a glowing effect.

FXAA (Fast Approximate Anti-aliasing)

FXAA is a post-processing anti-aliasing technique that smooths jagged edges in a rendered image. It's a relatively fast method suitable for real-time applications.

Depth of Field (DoF)

Depth of Field simulates the focus of a camera lens, where objects at a certain distance are in sharp focus, while objects closer or further away appear blurred.

Example: Basic Post-processing Setup (Conceptual)

This example shows a conceptual setup for post-processing. To see the effects, you would typically add specific passes like UnrealBloomPass, ShaderPass (for FXAA), or DepthOfFieldPass to the composer.

import *s THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
// import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
// import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
// import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';

// Scene, Camera, Renderer setup (assuming these are already defined)
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);

// Add a cube to the scene
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

camera.position.z = 5;

// 1. Initialize EffectComposer
const composer = new EffectComposer(renderer);

// 2. Add RenderPass
composer.addPass(new RenderPass(scene, camera));

// 3. Add Effect Passes (uncomment and configure as needed)
// const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
// composer.addPass(bloomPass);

// const fxaaPass = new ShaderPass(FXAAShader);
// fxaaPass.material.uniforms[ 'resolution' ].value.x = 1 / ( window.innerWidth * window.devicePixelRatio );
// fxaaPass.material.uniforms[ 'resolution' ].value.y = 1 / ( window.innerHeight * window.devicePixelRatio );
// composer.addPass(fxaaPass);

// Animation loop
function animate() {
    requestAnimationFrame(animate);

    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    // Render with composer instead of renderer
    composer.render();
}
animate();

window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    composer.setSize(window.innerWidth, window.innerHeight);
    // Update FXAA resolution if used
    // if (fxaaPass) {
    //     fxaaPass.material.uniforms[ 'resolution' ].value.x = 1 / ( window.innerWidth * window.devicePixelRatio );
    //     fxaaPass.material.uniforms[ 'resolution' ].value.y = 1 / ( window.innerHeight * window.devicePixelRatio );
    // }
});