threejs-postprocessing
Install this skill
npx skills add cloudai-x/threejs-skillsWorks across Claude Code, Cursor, Codex, Copilot & Antigravity
The Three.js postprocessing skill manages the visual pipeline applied to rendered scenes after the initial geometry pass. By implementing an EffectComposer, developers transition from standard renderer output to a multi-stage process where image-space shaders generate visual enhancements. This involves chaining various modules—such as color grading, bloom, and spatial filters—directly into the frame buffer. Because the composer replaces the standard renderer's frame output, managing render targets and pass sequences is necessary for visual fidelity. This system enables aesthetic depth, such as simulated camera lens effects or complex lighting accumulation, which are otherwise difficult to achieve with basic material shaders. It provides a modular architecture for stacking passes like Bokeh, SSAO, and anti-aliasing while maintaining performance through deliberate control over the render loop and resolution settings.
When to Use This Skill
- •Adding glowing neon highlights to interactive UI elements or objects
- •Improving edge quality on low-resolution renders using SMAA
- •Creating realistic depth perception in virtual photography scenes
- •Applying global ambient occlusion to flat-lit 3D environments
How to Invoke This Skill
Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:
- “add bloom to my three.js scene
- “how to implement effectcomposer for post-processing
- “fix aliasing in three.js with smaa
- “create depth of field effect in three.js
- “setup ambient occlusion in threejs
Pro Tips
- 💡Chain multiple passes: Leverage the `EffectComposer` to combine various passes (e.g., `RenderPass`, `UnrealBloomPass`, `SAOPass`) for layered visual effects, optimizing their order for desired output.
- 💡Optimize performance: Be mindful of the number and complexity of your post-processing passes, as they can be GPU intensive. Consider techniques like downsampling for passes that don't require full resolution, or using conditional rendering based on user device capabilities.
- 💡Custom shader integration: Use `ShaderPass` to easily integrate your own GLSL shaders into the post-processing pipeline, allowing for limitless creative control over visual effects beyond the standard Three.js library.
What this skill does
- •Execute post-rendering filter chains via EffectComposer
- •Apply cinematic bloom and physical light bleeding
- •Perform screen-space ambient occlusion for depth and contact shadows
- •Integrate anti-aliasing techniques like FXAA and SMAA
- •Simulate lens-based depth of field with Bokeh effects
When not to use it
- ✕Simple projects where standard light/shadow materials provide enough visual impact
- ✕Performance-critical mobile applications where fill-rate overhead is a strict bottleneck
Example workflow
- Instantiate the EffectComposer using your existing WebGLRenderer
- Add a RenderPass to capture the initial scene state
- Append aesthetic passes like UnrealBloomPass or BokehPass to the composer
- Set the final pass to renderToScreen true
- Replace the standard renderer.render() loop with composer.render()
- Synchronize composer resolution with window resize events
Prerequisites
- –Basic Three.js scene setup
- –WebGLRenderer instance
- –Understanding of the animation loop
Pitfalls & limitations
- !Forgetting to switch the render loop from renderer.render to composer.render
- !Failing to update composer size during browser window resize events
- !Over-stacking passes which exponentially increases frame time overhead
FAQ
How it compares
Doing this manually requires complex manual frame buffer management and shader writing, whereas this skill provides a pre-architected framework to plug-and-play production-ready effects.
📄 Full skill instructions — original source: cloudai-x/threejs-skills
## Quick Start
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 composer
const composer = new EffectComposer(renderer);
// Render scene
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// 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
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
// 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);## See Also
-
threejs-shaders - Custom shader development-
threejs-textures - Render targets-
threejs-fundamentals - Renderer setupHow to Use This Skill Unit
Option A: Project-Specific (Recommended)
- Click "Download" above
- In your project, create the directory:
.agent/skills/threejs-postprocessing/ - Save the file as
SKILL.md - The agent will automatically discover the skill based on its description.
Option B: Global Installation (All Agents)
Save the file to these locations to make it available across all projects:
- Claude Code:
~/.claude/skills/cloudai-x/threejs-skills/threejs-postprocessing/SKILL.md - Cursor:
~/.cursor/skills/cloudai-x/threejs-skills/threejs-postprocessing/SKILL.md - Antigravity:
~/.gemini/antigravity/skills/cloudai-x/threejs-skills/threejs-postprocessing/SKILL.md
🚀 Install with CLI:npx skills add cloudai-x/threejs-skills