
Threejs Impl Post Processing
- 18 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-impl-post-processing is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-impl-post-processing
- AI & Agent Building
- AI-coding skill
Threejs Impl Post Processing by the numbers
- 18 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,736 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-impl-post-processingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
threejs-impl-post-processing
Quick Reference
Architecture Overview
Three.js offers TWO incompatible post-processing systems. NEVER mix them.
| System | Package | Approach | Best For |
|---|---|---|---|
| Built-in EffectComposer | three/addons/postprocessing/ | One shader pass per effect | Simple setups, custom ShaderPass |
| pmndrs/postprocessing | postprocessing (npm) | Merges effects into single pass | Performance-critical, R3F apps |
| WebGPU PostProcessing | three/addons/tsl/display/ | Node-based TSL graphs | WebGPU renderer only |
Standard Pipeline (Built-in EffectComposer)
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) ); // ALWAYS first
// ... effect passes here ...
composer.addPass( new OutputPass() ); // ALWAYS last
function animate() {
composer.render(); // replaces renderer.render( scene, camera )
}Pass Ordering Rules
1. RenderPass MUST be the FIRST pass -- it renders the scene to the internal buffer 2. Effect passes go in the MIDDLE in any order (bloom, SSAO, outline, etc.) 3. Anti-aliasing passes (SMAA, FXAA) go AFTER effect passes 4. OutputPass MUST be the LAST pass -- it applies tone mapping and color space conversion 5. NEVER omit OutputPass -- without it, colors appear washed out or incorrect
Critical Warnings
NEVER mix three/addons/postprocessing/EffectComposer with pmndrs/postprocessing EffectComposer. They use incompatible buffer formats and will produce rendering artifacts or crashes.
NEVER forget to call composer.setSize() on window resize. Failing to resize the composer causes blurry or misaligned effects.
NEVER call renderer.render( scene, camera ) when using EffectComposer. ALWAYS call composer.render() instead -- calling both renders the scene twice.
NEVER use WebGL post-processing passes with the WebGPU renderer. WebGPU uses an entirely separate node-based PostProcessing class.
ALWAYS include RenderPass as the first pass. Without it, subsequent passes receive an empty buffer.
---
EffectComposer API
Constructor
new EffectComposer( renderer: WebGLRenderer, renderTarget?: WebGLRenderTarget )renderer-- the WebGLRenderer instancerenderTarget-- optional custom render target; auto-created if omitted
Properties
| Property | Type | Description |
|---|---|---|
.passes | Pass[] | Ordered array of post-processing passes |
.readBuffer | WebGLRenderTarget | Internal read buffer |
.writeBuffer | WebGLRenderTarget | Internal write buffer |
.renderToScreen | boolean | Whether the final pass renders to screen (default true) |
.renderer | WebGLRenderer | The renderer instance |
Methods
| Method | Description |
|---|---|
.addPass( pass ) | Appends a pass to the end of the chain |
.insertPass( pass, index ) | Inserts a pass at a specific position |
.removePass( pass ) | Removes a pass from the chain |
.render( deltaTime? ) | Executes all enabled passes in order |
.setSize( width, height ) | Resizes all internal buffers and passes |
.setPixelRatio( ratio ) | Configures device pixel ratio |
.swapBuffers() | Exchanges read/write buffers |
.reset( renderTarget? ) | Restores internal state |
.dispose() | Frees all GPU resources |
---
Available Pass Types
Core Passes (ALWAYS needed)
| Pass | Import | Purpose |
|---|---|---|
RenderPass | postprocessing/RenderPass.js | Renders scene to buffer; ALWAYS first |
OutputPass | postprocessing/OutputPass.js | Tone mapping + color space; ALWAYS last |
Effect Passes
| Pass | Constructor | Purpose |
|---|---|---|
UnrealBloomPass | ( resolution, strength?, radius?, threshold? ) | HDR bloom glow |
SSAOPass | ( scene, camera, width?, height? ) | Screen-space ambient occlusion |
GTAOPass | ( scene, camera, width?, height? ) | Ground truth AO (higher quality) |
SAOPass | ( scene, camera ) | Scalable ambient obscurance |
OutlinePass | ( resolution, scene, camera, selectedObjects? ) | Object selection outlines |
BokehPass | ( scene, camera, params ) | Depth of field |
SSRPass | ( params ) | Screen-space reflections |
FilmPass | ( intensity?, grayscale? ) | Film grain / scanlines |
GlitchPass | ( dtSize? ) | Digital glitch effect |
HalftonePass | ( width, height, params ) | Halftone dot pattern |
DotScreenPass | ( center?, angle?, scale? ) | Dot screen overlay |
AfterimagePass | ( damp? ) | Motion trails / ghosting |
LUTPass | ( params ) | Color LUT grading |
RenderPixelatedPass | ( pixelSize, scene, camera ) | Pixelation effect |
Anti-Aliasing Passes
| Pass | Constructor | Quality | Cost |
|---|---|---|---|
FXAAPass | () | Low -- fast approximation | Cheapest |
SMAAPass | ( width, height ) | Medium -- subpixel morphological | Moderate |
SSAARenderPass | ( scene, camera ) | High -- super-sampling | Expensive |
TAARenderPass | ( scene, camera ) | High -- temporal accumulation | Expensive |
Utility Passes
| Pass | Purpose |
|---|---|
ShaderPass | Custom GLSL shader effect |
MaskPass | Stencil masking |
ClearMaskPass | Clears stencil mask |
ClearPass | Clears buffer |
TexturePass | Renders a texture |
CubeTexturePass | Renders cubemap background |
SavePass | Saves current buffer to render target |
RenderTransitionPass | Animated transition between two scenes |
ALL passes import from three/addons/postprocessing/{PassName}.js.
---
Key Effect Configuration
UnrealBloomPass
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
const bloom = new UnrealBloomPass(
new THREE.Vector2( window.innerWidth, window.innerHeight ),
1.5, // strength -- bloom intensity [0, 3+]
0.4, // radius -- bloom spread [0, 1]
0.85 // threshold -- luminance cutoff [0, 1]
);Tone mapping MUST be enabled on the renderer for bloom to work correctly. Selective bloom (per-object) is NOT natively supported -- use the layers system with multiple render passes as a workaround.
SSAOPass / GTAOPass
ALWAYS prefer GTAOPass over SSAOPass for production quality. Use SSAOPass only for prototyping.
// SSAOPass (faster, lower quality)
const ssao = new SSAOPass( scene, camera, width, height );
ssao.kernelRadius = 8;
ssao.minDistance = 0.005;
ssao.maxDistance = 0.1;
// GTAOPass (slower, production quality)
const gtao = new GTAOPass( scene, camera, width, height );OutlinePass
const outline = new OutlinePass(
new THREE.Vector2( window.innerWidth, window.innerHeight ),
scene, camera
);
outline.selectedObjects = [ mesh1, mesh2 ];
outline.edgeStrength = 3;
outline.edgeGlow = 0;
outline.edgeThickness = 1;
outline.visibleEdgeColor.set( 0xffffff );
outline.hiddenEdgeColor.set( 0x190a05 );---
Custom ShaderPass
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
const myShader = {
uniforms: {
tDiffuse: { value: null }, // ALWAYS include -- receives read buffer
amount: { value: 0.5 }
},
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() {
vec4 color = texture2D( tDiffuse, vUv );
gl_FragColor = mix( color, vec4( 1.0 - color.rgb, color.a ), amount );
}
`
};
const customPass = new ShaderPass( myShader );
composer.insertPass( customPass, 1 ); // after RenderPassThe textureID parameter defaults to 'tDiffuse'. If your shader uses a different uniform name for the input texture, pass it as the second argument: new ShaderPass( myShader, 'myInputTexture' ).
---
pmndrs/postprocessing
Architecture
The pmndrs/postprocessing library merges multiple effects into a SINGLE shader pass, reducing draw calls significantly compared to the built-in system.
import { EffectComposer, EffectPass, RenderPass, BloomEffect,
SMAAEffect } from 'postprocessing';
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );
composer.addPass( new EffectPass( camera, new BloomEffect(), new SMAAEffect() ) );
function animate() {
composer.render();
}Key Effects
| Effect | Purpose |
|---|---|
BloomEffect | Configurable bloom with mipmaps |
SMAAEffect | Subpixel morphological AA |
SSAOEffect | Screen-space ambient occlusion |
DepthOfFieldEffect | Bokeh depth-of-field |
ToneMappingEffect | Tone mapping operators |
VignetteEffect | Screen edge darkening |
ChromaticAberrationEffect | Color fringing |
NoiseEffect | Film grain |
GodRaysEffect | Volumetric light scattering |
For React Three Fiber, use @react-three/postprocessing which wraps these effects as JSX components.
---
Resize Handling
ALWAYS resize both the renderer AND the composer on window resize:
window.addEventListener( 'resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
composer.setSize( window.innerWidth, window.innerHeight ); // MUST resize
});---
WebGPU PostProcessing
The WebGPU renderer uses a SEPARATE node-based system:
import { PostProcessing } from 'three/addons/tsl/display/PostProcessing.js';
const postProcessing = new PostProcessing( renderer );
// Uses TSL (Three Shading Language) node graphs for effectsThis is entirely incompatible with both the built-in WebGL EffectComposer and pmndrs/postprocessing.
---
Reference Links
- references/methods.md -- Complete API signatures for EffectComposer, all passes, and pmndrs effects
- references/examples.md -- Working code examples for common post-processing setups
- references/anti-patterns.md -- What NOT to do with post-processing
Official Sources
- https://threejs.org/docs/#examples/en/postprocessing/EffectComposer
- https://threejs.org/examples/?q=postprocessing
- https://github.com/pmndrs/postprocessing
- https://docs.pmnd.rs/react-three-fiber/tutorials/post-processing
anti-patterns.md -- threejs-impl-post-processing
What NOT to do with Three.js post-processing, with explanations of WHY.
---
Anti-Pattern 1: Missing RenderPass
WRONG:
const composer = new EffectComposer( renderer );
composer.addPass( new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ) );
composer.addPass( new OutputPass() );WHY: Without RenderPass as the first pass, the bloom pass receives an EMPTY buffer. No scene content is rendered into the post-processing pipeline. The result is a completely black screen.
CORRECT:
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) ); // ALWAYS first
composer.addPass( new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ) );
composer.addPass( new OutputPass() );---
Anti-Pattern 2: Missing OutputPass
WRONG:
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );
composer.addPass( new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ) );
// No OutputPassWHY: OutputPass applies tone mapping and color space conversion. Without it, colors appear washed out, overly bright, or in the wrong color space (linear instead of sRGB). This is especially visible with HDR effects like bloom.
CORRECT:
composer.addPass( new RenderPass( scene, camera ) );
composer.addPass( new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ) );
composer.addPass( new OutputPass() ); // ALWAYS last---
Anti-Pattern 3: Mixing Two EffectComposer Libraries
WRONG:
// Importing from BOTH libraries
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { BloomEffect, EffectPass } from 'postprocessing';
const composer = new EffectComposer( renderer ); // Three.js built-in
composer.addPass( new EffectPass( camera, new BloomEffect() ) ); // pmndrs effectWHY: The built-in EffectComposer and pmndrs/postprocessing EffectComposer use incompatible internal buffer formats and rendering pipelines. Mixing passes from one library into the other produces rendering artifacts, black screens, or crashes. They are two completely separate systems.
CORRECT: Choose ONE library and use it exclusively:
// Option A: Built-in only
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 { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
// Option B: pmndrs only
import { EffectComposer, RenderPass, EffectPass, BloomEffect } from 'postprocessing';---
Anti-Pattern 4: Forgetting to Resize the Composer
WRONG:
window.addEventListener( 'resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
// Missing: composer.setSize()
} );WHY: The EffectComposer maintains its own internal render targets at a fixed resolution. If you resize only the renderer, the internal buffers remain at the OLD size. This causes blurry, stretched, or misaligned post-processing effects.
CORRECT:
window.addEventListener( 'resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
composer.setSize( window.innerWidth, window.innerHeight ); // ALWAYS resize both
} );---
Anti-Pattern 5: Calling renderer.render() AND composer.render()
WRONG:
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera ); // renders scene directly to screen
composer.render(); // renders scene AGAIN through post-processing
}WHY: The RenderPass inside the composer already calls renderer.render() internally. Calling renderer.render() before composer.render() renders the scene twice: once directly to the screen (without effects) and once through the post-processing pipeline. This wastes GPU resources and can cause flickering.
CORRECT:
function animate() {
requestAnimationFrame( animate );
composer.render(); // This is ALL you need
}---
Anti-Pattern 6: Wrong Pass Order (AA Before Effects)
WRONG:
composer.addPass( new RenderPass( scene, camera ) );
composer.addPass( new SMAAPass( width, height ) ); // AA FIRST
composer.addPass( new UnrealBloomPass( res, 1.5, 0.4, 0.85 ) ); // Bloom AFTER AA
composer.addPass( new OutputPass() );WHY: Anti-aliasing smooths edges in the current buffer. If applied BEFORE bloom, the bloom pass introduces new aliased edges that are NOT anti-aliased. ALWAYS apply anti-aliasing AFTER all effect passes.
CORRECT:
composer.addPass( new RenderPass( scene, camera ) );
composer.addPass( new UnrealBloomPass( res, 1.5, 0.4, 0.85 ) ); // Effects FIRST
composer.addPass( new SMAAPass( width, height ) ); // AA AFTER effects
composer.addPass( new OutputPass() );---
Anti-Pattern 7: Missing tDiffuse Uniform in Custom ShaderPass
WRONG:
const myShader = {
uniforms: {
amount: { value: 0.5 }
// Missing tDiffuse uniform
},
vertexShader: `...`,
fragmentShader: `
uniform float amount;
varying vec2 vUv;
void main() {
gl_FragColor = vec4( amount, amount, amount, 1.0 );
}
`
};
const pass = new ShaderPass( myShader );WHY: ShaderPass writes the read buffer texture into the uniform named by textureID (default 'tDiffuse'). If that uniform does not exist, the shader receives no input from the previous pass and cannot process the scene image. The result is a solid color or garbage output.
CORRECT:
const myShader = {
uniforms: {
tDiffuse: { value: null }, // ALWAYS include this
amount: { value: 0.5 }
},
vertexShader: `...`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float amount;
varying vec2 vUv;
void main() {
vec4 color = texture2D( tDiffuse, vUv );
gl_FragColor = mix( color, vec4( 1.0 - color.rgb, color.a ), amount );
}
`
};---
Anti-Pattern 8: Using WebGL Passes with WebGPU Renderer
WRONG:
import { WebGPURenderer } from 'three/addons/renderers/webgpu/WebGPURenderer.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
const renderer = new WebGPURenderer();
const composer = new EffectComposer( renderer ); // INCOMPATIBLEWHY: The built-in EffectComposer is designed exclusively for WebGLRenderer. It uses WebGL-specific render targets, framebuffers, and shader compilation. The WebGPU renderer has its own node-based PostProcessing class that uses TSL (Three Shading Language).
CORRECT:
import { WebGPURenderer } from 'three/addons/renderers/webgpu/WebGPURenderer.js';
import { PostProcessing } from 'three/addons/tsl/display/PostProcessing.js';
const renderer = new WebGPURenderer();
const postProcessing = new PostProcessing( renderer );---
Anti-Pattern 9: Bloom Without Tone Mapping
WRONG:
const renderer = new THREE.WebGLRenderer();
// renderer.toneMapping is THREE.NoToneMapping (default)
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );
composer.addPass( new UnrealBloomPass( res, 1.5, 0.4, 0.85 ) );
composer.addPass( new OutputPass() );WHY: Bloom relies on HDR values (colors above 1.0) to determine which areas glow. Without tone mapping, these HDR values are clamped to [0, 1] before bloom can detect them. The bloom effect appears extremely faint or invisible.
CORRECT:
const renderer = new THREE.WebGLRenderer();
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;---
Anti-Pattern 10: Not Disposing Post-Processing Resources
WRONG:
// Switching scenes or removing post-processing
composer = new EffectComposer( renderer ); // old composer leakedWHY: Each EffectComposer creates internal WebGL render targets and each pass may allocate additional GPU resources. Creating a new composer without disposing the old one leaks GPU memory. Over time, this causes performance degradation or crashes.
CORRECT:
// Clean up before replacing
composer.dispose(); // frees all internal render targets and passes
composer = new EffectComposer( renderer );examples.md -- threejs-impl-post-processing
Working code examples for common Three.js post-processing setups.
All examples use ES module imports and Three.js r160+.
---
Example 1: Bloom Effect
HDR bloom that makes bright objects glow.
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';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
// Renderer -- tone mapping MUST be enabled for bloom
const renderer = new THREE.WebGLRenderer( { antialias: false } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild( renderer.domElement );
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.z = 5;
// Emissive material -- bloom picks up values above threshold
const geometry = new THREE.SphereGeometry( 1, 32, 32 );
const material = new THREE.MeshStandardMaterial( {
color: 0x000000,
emissive: 0xff6600,
emissiveIntensity: 2.0 // above 1.0 to trigger bloom
} );
scene.add( new THREE.Mesh( geometry, material ) );
scene.add( new THREE.AmbientLight( 0xffffff, 0.2 ) );
// Post-processing pipeline
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) ); // ALWAYS first
composer.addPass( new UnrealBloomPass(
new THREE.Vector2( window.innerWidth, window.innerHeight ),
1.5, // strength
0.4, // radius
0.85 // threshold
) );
composer.addPass( new OutputPass() ); // ALWAYS last
// Resize handler
window.addEventListener( 'resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
composer.setSize( window.innerWidth, window.innerHeight );
} );
function animate() {
requestAnimationFrame( animate );
composer.render(); // NOT renderer.render()
}
animate();---
Example 2: SSAO + Bloom Combo
Multiple effects combined in a single pipeline.
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.toneMapping = THREE.ACESFilmicToneMapping;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
const composer = new EffectComposer( renderer );
// Pass 1: RenderPass (ALWAYS first)
composer.addPass( new RenderPass( scene, camera ) );
// Pass 2: GTAO for ambient occlusion (better quality than SSAOPass)
const gtaoPass = new GTAOPass( scene, camera, window.innerWidth, window.innerHeight );
composer.addPass( gtaoPass );
// Pass 3: Bloom
const bloomPass = new UnrealBloomPass(
new THREE.Vector2( window.innerWidth, window.innerHeight ),
0.8, 0.3, 0.9
);
composer.addPass( bloomPass );
// Pass 4: OutputPass (ALWAYS last)
composer.addPass( new OutputPass() );
function animate() {
requestAnimationFrame( animate );
composer.render();
}
animate();---
Example 3: Object Outlines on Hover
Highlighting objects with OutlinePass on mouse hover.
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.set( 0, 2, 5 );
// Create some objects
const objects = [];
for ( let i = 0; i < 5; i++ ) {
const mesh = new THREE.Mesh(
new THREE.BoxGeometry( 1, 1, 1 ),
new THREE.MeshStandardMaterial( { color: Math.random() * 0xffffff } )
);
mesh.position.x = ( i - 2 ) * 2;
scene.add( mesh );
objects.push( mesh );
}
scene.add( new THREE.DirectionalLight( 0xffffff, 1 ) );
scene.add( new THREE.AmbientLight( 0x404040 ) );
// Post-processing
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );
const outlinePass = new OutlinePass(
new THREE.Vector2( window.innerWidth, window.innerHeight ),
scene, camera
);
outlinePass.edgeStrength = 5;
outlinePass.edgeGlow = 0.5;
outlinePass.edgeThickness = 2;
outlinePass.visibleEdgeColor.set( 0x00ff00 );
composer.addPass( outlinePass );
composer.addPass( new OutputPass() );
// Raycaster for hover detection
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
window.addEventListener( 'mousemove', ( event ) => {
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = -( event.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
const intersects = raycaster.intersectObjects( objects );
outlinePass.selectedObjects = intersects.length > 0 ? [ intersects[ 0 ].object ] : [];
} );
function animate() {
requestAnimationFrame( animate );
composer.render();
}
animate();---
Example 4: Custom ShaderPass (Vignette)
Writing a custom post-processing effect with ShaderPass.
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
const vignetteShader = {
uniforms: {
tDiffuse: { value: null }, // ALWAYS include -- receives read buffer
darkness: { value: 1.5 },
offset: { 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 darkness;
uniform float offset;
varying vec2 vUv;
void main() {
vec4 texel = texture2D( tDiffuse, vUv );
vec2 uv = ( vUv - vec2( 0.5 ) ) * vec2( offset );
float vignette = clamp( 1.0 - dot( uv, uv ), 0.0, 1.0 );
gl_FragColor = vec4( texel.rgb * mix( 1.0, vignette, darkness ), texel.a );
}
`
};
// Setup (scene, camera, renderer assumed created)
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );
const vignettePass = new ShaderPass( vignetteShader );
composer.addPass( vignettePass );
composer.addPass( new OutputPass() );
// Update uniforms at runtime
vignettePass.uniforms.darkness.value = 2.0;
function animate() {
requestAnimationFrame( animate );
composer.render();
}
animate();---
Example 5: pmndrs/postprocessing with Effect Merging
Using the pmndrs library for better performance with multiple effects.
import * as THREE from 'three';
import { EffectComposer, EffectPass, RenderPass, BloomEffect,
SMAAEffect, VignetteEffect, SMAAPreset } from 'postprocessing';
const renderer = new THREE.WebGLRenderer( {
powerPreference: 'high-performance',
antialias: false, // SMAA handles AA
stencil: false,
depth: false
} );
renderer.setSize( window.innerWidth, window.innerHeight );
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
// pmndrs EffectComposer (NOT Three.js built-in)
const composer = new EffectComposer( renderer, {
multisampling: 0 // disable MSAA since we use SMAA
} );
// RenderPass (pmndrs version)
composer.addPass( new RenderPass( scene, camera ) );
// EffectPass merges ALL effects into ONE shader pass
composer.addPass( new EffectPass( camera,
new BloomEffect( {
luminanceThreshold: 0.8,
luminanceSmoothing: 0.075,
mipmapBlur: true,
intensity: 1.5
} ),
new VignetteEffect( {
darkness: 0.5
} ),
new SMAAEffect( {
preset: SMAAPreset.HIGH
} )
) );
// No OutputPass needed -- pmndrs handles tone mapping internally
window.addEventListener( 'resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
const width = window.innerWidth;
const height = window.innerHeight;
renderer.setSize( width, height );
composer.setSize( width, height );
} );
function animate() {
requestAnimationFrame( animate );
composer.render();
}
animate();methods.md -- threejs-impl-post-processing
Complete API signatures for Three.js post-processing classes.
All imports from three/addons/postprocessing/ unless otherwise noted.---
EffectComposer
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';Constructor
new EffectComposer( renderer: WebGLRenderer, renderTarget?: WebGLRenderTarget )Properties
| Property | Type | Default | Description |
|---|---|---|---|
.passes | Pass[] | [] | Ordered array of post-processing passes |
.readBuffer | WebGLRenderTarget | auto | Internal read buffer |
.writeBuffer | WebGLRenderTarget | auto | Internal write buffer |
.renderToScreen | boolean | true | Final pass renders to screen |
.renderer | WebGLRenderer | -- | The renderer instance |
Methods
addPass( pass: Pass ): void
insertPass( pass: Pass, index: number ): void
removePass( pass: Pass ): void
render( deltaTime?: number ): void
setSize( width: number, height: number ): void
setPixelRatio( pixelRatio: number ): void
swapBuffers(): void
reset( renderTarget?: WebGLRenderTarget ): void
dispose(): void---
Pass (Base Class)
All passes extend the abstract Pass class.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
.enabled | boolean | true | Whether this pass executes |
.needsSwap | boolean | true | Whether to swap read/write buffers after rendering |
.clear | boolean | false | Whether to clear the buffer before rendering |
.renderToScreen | boolean | false | Whether this pass renders directly to screen |
Methods
setSize( width: number, height: number ): void
dispose(): void---
RenderPass
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
new RenderPass(
scene: Scene,
camera: Camera,
overrideMaterial?: Material,
clearColor?: Color,
clearAlpha?: number
)| Property | Type | Description |
|---|---|---|
.scene | Scene | The scene to render |
.camera | Camera | The camera to render from |
.overrideMaterial | `Material \ | null` |
.clearColor | `Color \ | null` |
.clearAlpha | number | Clear alpha override |
.clearDepth | boolean | Whether to clear depth buffer |
---
OutputPass
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
new OutputPass()Applies the renderer's tone mapping and output color space conversion. ALWAYS the last pass.
---
UnrealBloomPass
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
new UnrealBloomPass(
resolution: Vector2,
strength?: number, // default 1
radius?: number, // default 0
threshold?: number // default 0
)| Property | Type | Range | Description |
|---|---|---|---|
.strength | number | 0 -- 3+ | Bloom intensity |
.radius | number | 0 -- 1 | Bloom spread width |
.threshold | number | 0 -- 1 | Luminance cutoff for bloom |
.resolution | Vector2 | -- | Resolution of bloom buffers |
---
SSAOPass
import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
new SSAOPass(
scene: Scene,
camera: Camera,
width?: number,
height?: number,
kernelSize?: number
)| Property | Type | Default | Description |
|---|---|---|---|
.kernelRadius | number | 8 | AO spread radius |
.minDistance | number | 0.005 | Minimum distance for AO |
.maxDistance | number | 0.1 | Maximum distance for AO |
.output | number | SSAOPass.OUTPUT.Default | Output mode |
Output modes: SSAOPass.OUTPUT.Default, SSAOPass.OUTPUT.SSAO, SSAOPass.OUTPUT.Blur, SSAOPass.OUTPUT.Depth, SSAOPass.OUTPUT.Normal.
---
GTAOPass
import { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';
new GTAOPass(
scene: Scene,
camera: Camera,
width?: number,
height?: number
)Higher quality than SSAOPass. Supports blend intensity and denoise settings.
---
OutlinePass
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
new OutlinePass(
resolution: Vector2,
scene: Scene,
camera: Camera,
selectedObjects?: Object3D[]
)| Property | Type | Default | Description |
|---|---|---|---|
.selectedObjects | Object3D[] | [] | Objects to outline |
.edgeStrength | number | 3 | Edge intensity |
.edgeGlow | number | 0 | Animated glow amount |
.edgeThickness | number | 1 | Outline width |
.visibleEdgeColor | Color | 0xffffff | Visible edge color |
.hiddenEdgeColor | Color | 0x190a05 | Occluded edge color |
.pulsePeriod | number | 0 | Pulse animation period (0 = off) |
.downSampleRatio | number | 2 | Resolution downsampling |
---
SMAAPass
import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js';
new SMAAPass( width: number, height: number )Subpixel morphological anti-aliasing. Moderate quality and cost.
---
FXAAPass
import { FXAAPass } from 'three/addons/postprocessing/FXAAPass.js';
new FXAAPass()Fast approximate anti-aliasing. Lowest quality but cheapest. Access resolution uniform via pass.material.uniforms[ 'resolution' ].
---
ShaderPass
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
new ShaderPass( shader: object, textureID?: string )shader-- object withuniforms,vertexShader,fragmentShadertextureID-- name of the uniform that receives the read buffer (default'tDiffuse')
ALWAYS include a tDiffuse uniform (or your custom textureID name) set to { value: null }.
---
BokehPass
import { BokehPass } from 'three/addons/postprocessing/BokehPass.js';
new BokehPass( scene: Scene, camera: Camera, params: {
focus?: number, // focus distance
aperture?: number, // aperture size
maxblur?: number // maximum blur amount
})---
FilmPass
import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';
new FilmPass( intensity?: number, grayscale?: boolean )---
GlitchPass
import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
new GlitchPass( dtSize?: number )| Property | Type | Description |
|---|---|---|
.goWild | boolean | Continuous glitch mode (default false) |
---
LUTPass
import { LUTPass } from 'three/addons/postprocessing/LUTPass.js';
new LUTPass( params: { lut: DataTexture, intensity?: number } )---
MaskPass / ClearMaskPass
import { MaskPass } from 'three/addons/postprocessing/MaskPass.js';
import { ClearMaskPass } from 'three/addons/postprocessing/ClearMaskPass.js';
new MaskPass( scene: Scene, camera: Camera )
new ClearMaskPass()Used for stencil-based masking. MaskPass enables the stencil, ClearMaskPass disables it.
---
SSAARenderPass
import { SSAARenderPass } from 'three/addons/postprocessing/SSAARenderPass.js';
new SSAARenderPass( scene: Scene, camera: Camera, clearColor?: Color, clearAlpha?: number )| Property | Type | Default | Description |
|---|---|---|---|
.sampleLevel | number | 4 | Number of samples (2^n) |
.unbiased | boolean | true | Use unbiased sampling |
---
TAARenderPass
import { TAARenderPass } from 'three/addons/postprocessing/TAARenderPass.js';
new TAARenderPass( scene: Scene, camera: Camera, clearColor?: Color, clearAlpha?: number )Extends SSAARenderPass with temporal accumulation. Only re-renders when camera moves.
---
pmndrs/postprocessing -- Key Classes
import { EffectComposer, EffectPass, RenderPass, BloomEffect,
SMAAEffect, SSAOEffect, DepthOfFieldEffect, ToneMappingEffect,
VignetteEffect, ChromaticAberrationEffect, NoiseEffect,
GodRaysEffect } from 'postprocessing';EffectComposer (pmndrs)
new EffectComposer( renderer: WebGLRenderer, options?: {
depthBuffer?: boolean,
stencilBuffer?: boolean,
alpha?: boolean,
multisampling?: number,
frameBufferType?: number
})EffectPass (pmndrs)
new EffectPass( camera: Camera, ...effects: Effect[] )Merges multiple effects into a single shader pass. This is the key performance advantage over the built-in system.
BloomEffect (pmndrs)
new BloomEffect( options?: {
blendFunction?: BlendFunction,
luminanceThreshold?: number,
luminanceSmoothing?: number,
mipmapBlur?: boolean,
intensity?: number,
radius?: number
})SMAAEffect (pmndrs)
new SMAAEffect( options?: {
preset?: SMAAPreset,
edgeDetectionMode?: EdgeDetectionMode
})Presets: SMAAPreset.LOW, SMAAPreset.MEDIUM, SMAAPreset.HIGH, SMAAPreset.ULTRA.
---
WebGPU PostProcessing
import { PostProcessing } from 'three/addons/tsl/display/PostProcessing.js';
new PostProcessing( renderer: WebGPURenderer )Uses TSL (Three Shading Language) node graphs. Entirely separate API from WebGL post-processing.