Threejs Postprocessing

CloudAI-X/threejs-skills/skills/threejs-postprocessing

by CloudAI-Xb1c623076c661fc9b03dac19292e825a5d106823No license3.4K starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 3 months ago

Three.js post-processing - EffectComposer, bloom, DOF, screen effects. Use when adding visual effects, color grading, blur, glow, or creating custom screen-space shaders.

AI-generated overview

Reference guide for adding Three.js post-processing effects with EffectComposer, including bloom, DOF, and custom shaders.

What it does
Provides code recipes for setting up an EffectComposer pipeline in Three.js and chaining post-processing passes. It covers effects such as bloom, selective bloom, FXAA, SMAA, SSAO, depth of field, film grain, vignette, color and gamma correction, pixelation, glitch, halftone, and outlines. It also shows how to write custom ShaderPass effects, combine multiple passes, render to texture, and handle resize and performance concerns.
When to use it
Use when adding screen-space visual effects, color grading, blur, glow, or custom post-processing shaders to a Three.js scene. Also useful when tuning pass order, resolution, or performance of an existing composer setup.
Requirements
Requires a Three.js project with the addons/postprocessing and addons/shaders modules available; no scripts ship with the skill, only instructions and code examples.

Three.js Post-Processing

Quick Start

javascript
import * as 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";
// Setup composerconst composer = new EffectComposer(renderer);
// Render sceneconst renderPass = new RenderPass(scene, camera);composer.addPass(renderPass);
// Add bloomconst bloomPass = new UnrealBloomPass(  new THREE.Vector2(window.innerWidth, window.innerHeight),  1.5, // strength  0.4, // radius  0.85, // threshold);composer.addPass(bloomPass);
// Animation loop - use composer instead of rendererfunction animate() {  requestAnimationFrame(animate);  composer.render(); // NOT renderer.render()}

EffectComposer Setup

javascript
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
const composer = new EffectComposer(renderer);
// First pass: render sceneconst renderPass = new RenderPass(scene, camera);composer.addPass(renderPass);
// Add more passes...composer.addPass(effectPass);
// Last pass should render to screeneffectPass.renderToScreen = true; // Default for last pass
// Handle resizefunction onResize() {  const width = window.innerWidth;  const height = window.innerHeight;
  camera.aspect = width / height;  camera.updateProjectionMatrix();
  renderer.setSize(width, height);  composer.setSize(width, height);}

Common Effects

Bloom (Glow)

javascript
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
const bloomPass = new UnrealBloomPass(  new THREE.Vector2(window.innerWidth, window.innerHeight),  1.5, // strength - intensity of glow  0.4, // radius - spread of glow  0.85, // threshold - brightness threshold);
composer.addPass(bloomPass);
// Adjust at runtimebloomPass.strength = 2.0;bloomPass.threshold = 0.5;bloomPass.radius = 0.8;

Selective Bloom

Apply bloom only to specific objects.

javascript
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
// Layer setupconst BLOOM_LAYER = 1;const bloomLayer = new THREE.Layers();bloomLayer.set(BLOOM_LAYER);
// Mark objects to bloomglowingMesh.layers.enable(BLOOM_LAYER);
// Dark material for non-blooming objectsconst darkMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });const materials = {};
function darkenNonBloomed(obj) {  if (obj.isMesh && !bloomLayer.test(obj.layers)) {    materials[obj.uuid] = obj.material;    obj.material = darkMaterial;  }}
function restoreMaterial(obj) {  if (materials[obj.uuid]) {    obj.material = materials[obj.uuid];    delete materials[obj.uuid];  }}
// Custom render loopfunction render() {  // Render bloom pass  scene.traverse(darkenNonBloomed);  composer.render();  scene.traverse(restoreMaterial);
  // Render final scene over bloom  renderer.render(scene, camera);}

FXAA (Anti-Aliasing)

javascript
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";import { FXAAShader } from "three/addons/shaders/FXAAShader.js";
const fxaaPass = new ShaderPass(FXAAShader);fxaaPass.material.uniforms["resolution"].value.set(  1 / window.innerWidth,  1 / window.innerHeight,);
composer.addPass(fxaaPass);
// Update on resizefunction onResize() {  fxaaPass.material.uniforms["resolution"].value.set(    1 / window.innerWidth,    1 / window.innerHeight,  );}

SMAA (Better Anti-Aliasing)

javascript
import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js";
const smaaPass = new SMAAPass(  window.innerWidth * renderer.getPixelRatio(),  window.innerHeight * renderer.getPixelRatio(),);
composer.addPass(smaaPass);

SSAO (Ambient Occlusion)

javascript
import { SSAOPass } from "three/addons/postprocessing/SSAOPass.js";
const ssaoPass = new SSAOPass(  scene,  camera,  window.innerWidth,  window.innerHeight,);ssaoPass.kernelRadius = 16;ssaoPass.minDistance = 0.005;ssaoPass.maxDistance = 0.1;
composer.addPass(ssaoPass);
// Output modesssaoPass.output = SSAOPass.OUTPUT.Default;// SSAOPass.OUTPUT.Default - Final composited output// SSAOPass.OUTPUT.SSAO - Just the AO// SSAOPass.OUTPUT.Blur - Blurred AO// SSAOPass.OUTPUT.Depth - Depth buffer// SSAOPass.OUTPUT.Normal - Normal buffer

Depth of Field (DOF)

javascript
import { BokehPass } from "three/addons/postprocessing/BokehPass.js";
const bokehPass = new BokehPass(scene, camera, {  focus: 10.0, // Focus distance  aperture: 0.025, // Aperture (smaller = more DOF)  maxblur: 0.01, // Max blur amount});
composer.addPass(bokehPass);
// Update focus dynamicallybokehPass.uniforms["focus"].value = distanceToTarget;

Film Grain

javascript
import { FilmPass } from "three/addons/postprocessing/FilmPass.js";
const filmPass = new FilmPass(  0.35, // noise intensity  0.5, // scanline intensity  648, // scanline count  false, // grayscale);
composer.addPass(filmPass);

Vignette

javascript
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";import { VignetteShader } from "three/addons/shaders/VignetteShader.js";
const vignettePass = new ShaderPass(VignetteShader);vignettePass.uniforms["offset"].value = 1.0; // Vignette sizevignettePass.uniforms["darkness"].value = 1.0; // Vignette intensity
composer.addPass(vignettePass);

Color Correction

javascript
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";import { ColorCorrectionShader } from "three/addons/shaders/ColorCorrectionShader.js";
const colorPass = new ShaderPass(ColorCorrectionShader);colorPass.uniforms["powRGB"].value = new THREE.Vector3(1.2, 1.2, 1.2); // PowercolorPass.uniforms["mulRGB"].value = new THREE.Vector3(1.0, 1.0, 1.0); // Multiply
composer.addPass(colorPass);

Gamma Correction

javascript
import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.js";
const gammaPass = new ShaderPass(GammaCorrectionShader);composer.addPass(gammaPass);

Pixelation

javascript
import { RenderPixelatedPass } from "three/addons/postprocessing/RenderPixelatedPass.js";
const pixelPass = new RenderPixelatedPass(6, scene, camera); // 6 = pixel size
composer.addPass(pixelPass);

Glitch Effect

javascript
import { GlitchPass } from "three/addons/postprocessing/GlitchPass.js";
const glitchPass = new GlitchPass();glitchPass.goWild = false; // Continuous glitching
composer.addPass(glitchPass);

Halftone

javascript
import { HalftonePass } from "three/addons/postprocessing/HalftonePass.js";
const halftonePass = new HalftonePass(window.innerWidth, window.innerHeight, {  shape: 1, // 1 = dot, 2 = ellipse, 3 = line, 4 = square  radius: 4, // Dot size  rotateR: Math.PI / 12,  rotateB: (Math.PI / 12) * 2,  rotateG: (Math.PI / 12) * 3,  scatter: 0,  blending: 1,  blendingMode: 1,  greyscale: false,});
composer.addPass(halftonePass);

Outline

javascript
import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js";
const outlinePass = new OutlinePass(  new THREE.Vector2(window.innerWidth, window.innerHeight),  scene,  camera,);
outlinePass.edgeStrength = 3;outlinePass.edgeGlow = 0;outlinePass.edgeThickness = 1;outlinePass.pulsePeriod = 0;outlinePass.visibleEdgeColor.set(0xffffff);outlinePass.hiddenEdgeColor.set(0x190a05);
// Select objects to outlineoutlinePass.selectedObjects = [mesh1, mesh2];
composer.addPass(outlinePass);

Custom ShaderPass

Create your own post-processing effects.

javascript
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
const CustomShader = {  uniforms: {    tDiffuse: { value: null }, // Required: input texture    time: { value: 0 },    intensity: { value: 1.0 },  },  vertexShader: `    varying vec2 vUv;
    void main() {      vUv = uv;      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);    }  `,  fragmentShader: `    uniform sampler2D tDiffuse;    uniform float time;    uniform float intensity;    varying vec2 vUv;
    void main() {      vec2 uv = vUv;
      // Wave distortion      uv.x += sin(uv.y * 10.0 + time) * 0.01 * intensity;
      vec4 color = texture2D(tDiffuse, uv);      gl_FragColor = color;    }  `,};
const customPass = new ShaderPass(CustomShader);composer.addPass(customPass);
// Update in animation loopcustomPass.uniforms.time.value = clock.getElapsedTime();

Invert Colors Shader

javascript
const InvertShader = {  uniforms: {    tDiffuse: { value: null },  },  vertexShader: `    varying vec2 vUv;    void main() {      vUv = uv;      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);    }  `,  fragmentShader: `    uniform sampler2D tDiffuse;    varying vec2 vUv;
    void main() {      vec4 color = texture2D(tDiffuse, vUv);      gl_FragColor = vec4(1.0 - color.rgb, color.a);    }  `,};

Chromatic Aberration

javascript
const ChromaticAberrationShader = {  uniforms: {    tDiffuse: { value: null },    amount: { value: 0.005 },  },  vertexShader: `    varying vec2 vUv;    void main() {      vUv = uv;      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);    }  `,  fragmentShader: `    uniform sampler2D tDiffuse;    uniform float amount;    varying vec2 vUv;
    void main() {      vec2 dir = vUv - 0.5;      float dist = length(dir);
      float r = texture2D(tDiffuse, vUv - dir * amount * dist).r;      float g = texture2D(tDiffuse, vUv).g;      float b = texture2D(tDiffuse, vUv + dir * amount * dist).b;
      gl_FragColor = vec4(r, g, b, 1.0);    }  `,};

Combining Multiple Effects

javascript
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";import { VignetteShader } from "three/addons/shaders/VignetteShader.js";import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.js";
const composer = new EffectComposer(renderer);
// 1. Render scenecomposer.addPass(new RenderPass(scene, camera));
// 2. Bloomconst bloomPass = new UnrealBloomPass(  new THREE.Vector2(window.innerWidth, window.innerHeight),  0.5,  0.4,  0.85,);composer.addPass(bloomPass);
// 3. Vignetteconst vignettePass = new ShaderPass(VignetteShader);vignettePass.uniforms["offset"].value = 0.95;vignettePass.uniforms["darkness"].value = 1.0;composer.addPass(vignettePass);
// 4. Gamma correctioncomposer.addPass(new ShaderPass(GammaCorrectionShader));
// 5. Anti-aliasing (always last before output)const fxaaPass = new ShaderPass(FXAAShader);fxaaPass.uniforms["resolution"].value.set(  1 / window.innerWidth,  1 / window.innerHeight,);composer.addPass(fxaaPass);

Render to Texture

javascript
// Create render targetconst renderTarget = new THREE.WebGLRenderTarget(512, 512);
// Render scene to targetrenderer.setRenderTarget(renderTarget);renderer.render(scene, camera);renderer.setRenderTarget(null);
// Use textureconst texture = renderTarget.texture;otherMaterial.map = texture;

Multi-Pass Rendering

javascript
// Multiple composers for different scenes/layersconst bgComposer = new EffectComposer(renderer);bgComposer.addPass(new RenderPass(bgScene, camera));
const fgComposer = new EffectComposer(renderer);fgComposer.addPass(new RenderPass(fgScene, camera));fgComposer.addPass(bloomPass);
// Combine in render loopfunction animate() {  // Render background without clearing  renderer.autoClear = false;  renderer.clear();
  bgComposer.render();
  // Render foreground over it  renderer.clearDepth();  fgComposer.render();}

WebGPU Post-Processing (Three.js r150+)

javascript
import { postProcessing } from "three/addons/nodes/Nodes.js";import { pass, bloom, dof } from "three/addons/nodes/Nodes.js";
// Using node-based systemconst scenePass = pass(scene, camera);const bloomNode = bloom(scenePass, 0.5, 0.4, 0.85);
const postProcessing = new THREE.PostProcessing(renderer);postProcessing.outputNode = bloomNode;
// Renderfunction animate() {  postProcessing.render();}

Performance Tips

  1. Limit passes: Each pass adds a full-screen render
  2. Lower resolution: Use smaller render targets for blur passes
  3. Disable unused effects: Toggle passes on/off
  4. Use FXAA over MSAA: Less expensive anti-aliasing
  5. Profile with DevTools: Check GPU usage
javascript
// Disable passbloomPass.enabled = false;
// Reduce bloom resolutionconst bloomPass = new UnrealBloomPass(  new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2),  strength,  radius,  threshold,);
// Only apply effects in high-performance scenariosconst isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);if (!isMobile) {  composer.addPass(expensivePass);}

Handle Resize

javascript
function onWindowResize() {  const width = window.innerWidth;  const height = window.innerHeight;  const pixelRatio = renderer.getPixelRatio();
  camera.aspect = width / height;  camera.updateProjectionMatrix();
  renderer.setSize(width, height);  composer.setSize(width, height);
  // Update pass-specific resolutions  if (fxaaPass) {    fxaaPass.material.uniforms["resolution"].value.set(      1 / (width * pixelRatio),      1 / (height * pixelRatio),    );  }
  if (bloomPass) {    bloomPass.resolution.set(width, height);  }}
window.addEventListener("resize", onWindowResize);

See Also

  • threejs-shaders - Custom shader development
  • threejs-textures - Render targets
  • threejs-fundamentals - Renderer setup

Source and attribution

Source:CloudAI-X/threejs-skillsinskills/threejs-postprocessingat commitb1c6230

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal