threejs-postprocessing

Screen-space visual effects pipeline with bloom, depth-of-field, anti-aliasing, and custom shader composition. EffectComposer chains multiple rendering passes (bloom, SSAO, DOF, film grain, vignette, color grading) in sequence, with each pass reading the previous output Supports 15+ built-in effects including UnrealBloom, BokehPass (depth-of-field), FXAA/SMAA anti-aliasing, OutlinePass, and GlitchPass Custom ShaderPass enables writing fragment shaders for effects like chromatic aberration, wave distortion, and color inversion Selective bloom via layer masking applies glow only to tagged objects; multi-composer rendering combines separate effect chains for foreground and background layers Performance-critical: each pass adds a full-screen render, so disable unused effects, reduce resolution for blur passes, and profile GPU usage on mobile

INSTALLATION
npx skills add https://github.com/cloudai-x/threejs-skills --skill threejs-postprocessing
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2a

// Add bloom

const 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 renderer

function animate() {

requestAnimationFrame(animate);

composer.render(); // NOT renderer.render()

}

## EffectComposer Setup

import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";

import { RenderPass } from "three/addons/postprocessing/RenderPass.js";

const composer = new EffectComposer(renderer);

// First pass: render scene

const renderPass = new RenderPass(scene, camera);

composer.addPass(renderPass);

// Add more passes...

composer.addPass(effectPass);

// Last pass should render to screen

effectPass.renderToScreen = true; // Default for last pass

// Handle resize

function 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)

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 runtime

bloomPass.strength = 2.0;

bloomPass.threshold = 0.5;

bloomPass.radius = 0.8;


### Selective Bloom

Apply bloom only to specific objects.

import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";

import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";

// Layer setup

const BLOOM_LAYER = 1;

const bloomLayer = new THREE.Layers();

bloomLayer.set(BLOOM_LAYER);

// Mark objects to bloom

glowingMesh.layers.enable(BLOOM_LAYER);

// Dark material for non-blooming objects

const 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 loop

function render() {

// Render bloom pass

scene.traverse(darkenNonBloomed);

composer.render();

scene.traverse(restoreMaterial);

// Render final scene over bloom

renderer.render(scene, camera);

}


### FXAA (Anti-Aliasing)

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 resize

function onResize() {

fxaaPass.material.uniforms["resolution"].value.set(

1 / window.innerWidth,

1 / window.innerHeight,

);

}


### SMAA (Better Anti-Aliasing)

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)

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 modes

ssaoPass.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)

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 dynamically

bokehPass.uniforms["focus"].value = distanceToTarget;


### Film Grain

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

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 size

vignettePass.uniforms["darkness"].value = 1.0; // Vignette intensity

composer.addPass(vignettePass);


### Color Correction

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); // Power

colorPass.uniforms["mulRGB"].value = new THREE.Vector3(1.0, 1.0, 1.0); // Multiply

composer.addPass(colorPass);


### Gamma Correction

import { GammaCorrectionShader } from "three/addons/shaders/GammaCorrectionShader.js";

const gammaPass = new ShaderPass(GammaCorrectionShader);

composer.addPass(gammaPass);


### Pixelation

import { RenderPixelatedPass } from "three/addons/postprocessing/RenderPixelatedPass.js";

const pixelPass = new RenderPixelatedPass(6, scene, camera); // 6 = pixel size

composer.addPass(pixelPass);


### Glitch Effect

import { GlitchPass } from "three/addons/postprocessing/GlitchPass.js";

const glitchPass = new GlitchPass();

glitchPass.goWild = false; // Continuous glitching

composer.addPass(glitchPass);


### Halftone

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

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 outline

outlinePass.selectedObjects = [mesh1, mesh2];

composer.addPass(outlinePass);


## Custom ShaderPass

Create your own post-processing effects.

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 loop

customPass.uniforms.time.value = clock.getElapsedTime();


### Invert Colors Shader

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

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

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 scene

composer.addPass(new RenderPass(scene, camera));

// 2. Bloom

const bloomPass = new UnrealBloomPass(

new THREE.Vector2(window.innerWidth, window.innerHeight),

0.5,

0.4,

0.85,

);

composer.addPass(bloomPass);

// 3. Vignette

const vignettePass = new ShaderPass(VignetteShader);

vignettePass.uniforms["offset"].value = 0.95;

vignettePass.uniforms["darkness"].value = 1.0;

composer.addPass(vignettePass);

// 4. Gamma correction

composer.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

// Create render target

const renderTarget = new THREE.WebGLRenderTarget(512, 512);

// Render scene to target

renderer.setRenderTarget(renderTarget);

renderer.render(scene, camera);

renderer.setRenderTarget(null);

// Use texture

const texture = renderTarget.texture;

otherMaterial.map = texture;


## Multi-Pass Rendering

// Multiple composers for different scenes/layers

const 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 loop

function 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+)

import { postProcessing } from "three/addons/nodes/Nodes.js";

import { pass, bloom, dof } from "three/addons/nodes/Nodes.js";

// Using node-based system

const scenePass = pass(scene, camera);

const bloomNode = bloom(scenePass, 0.5, 0.4, 0.85);

const postProcessing = new THREE.PostProcessing(renderer);

postProcessing.outputNode = bloomNode;

// Render

function animate() {

postProcessing.render();

}


## Performance Tips

- **Limit passes**: Each pass adds a full-screen render

- **Lower resolution**: Use smaller render targets for blur passes

- **Disable unused effects**: Toggle passes on/off

- **Use FXAA over MSAA**: Less expensive anti-aliasing

- **Profile with DevTools**: Check GPU usage

// Disable pass

bloomPass.enabled = false;

// Reduce bloom resolution

const bloomPass = new UnrealBloomPass(

new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2),

strength,

radius,

threshold,

);

// Only apply effects in high-performance scenarios

const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);

if (!isMobile) {

composer.addPass(expensivePass);

}


## Handle Resize

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);

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card