Post-Processing
Post-processing effects including bloom, SSAO, depth of field, motion blur, color grading, and custom effect chains.
Post-processing applies screen-space effects after the main scene is rendered. Web Engine provides a complete post-processing pipeline with common effects and support for custom passes.
Built-in Effects#
Bloom
HDR bloom with adjustable intensity, threshold, and radius
SSAO
Screen-space ambient occlusion for contact shadows
Depth of Field
Realistic camera focus with bokeh blur
Motion Blur
Camera and object motion blur
Color Grading
Tone mapping, color correction, and LUTs
Custom Effects
Build your own post-processing passes
Setting Up Post-Processing#
The post-processing system uses Three.js EffectComposer to chain multiple passes:
import { initPostProcessing, updatePostProcessing, renderPostProcessing,} from '@web-engine-dev/core/engine/rendering'; // Initialize post-processing (once during setup)initPostProcessing(); // Configure effectsupdatePostProcessing({ bloomEnabled: true, bloomIntensity: 1.5, bloomThreshold: 0.9, bloomRadius: 0.5, toneMappingExposure: 1.0,}); // Render with post-processing (in game loop)function gameLoop() { // Render scene with all post-processing effects renderPostProcessing();}Bloom#
Bloom creates a glow effect around bright areas, simulating how real cameras and eyes perceive intense light:
import { updatePostProcessing } from '@web-engine-dev/core/engine/rendering'; // Enable bloomupdatePostProcessing({ bloomEnabled: true, bloomIntensity: 1.5, // Glow strength (0-3) bloomThreshold: 0.9, // Brightness threshold (0-1) bloomRadius: 0.5, // Blur radius (0-1)}); // For emissive objects to bloom, increase emissive intensitymaterial.emissive = new THREE.Color(0xff0000);material.emissiveIntensity = 2.0; // > 1.0 for bloom| Parameter | Range | Effect |
|---|---|---|
| intensity | 0-3 | Strength of the bloom glow |
| threshold | 0-1 | Minimum brightness to bloom (0.9 = only very bright) |
| radius | 0-1 | Size of the glow radius |
Selective Bloom
To make only specific objects bloom, render them to a separate layer and use selective bloom. Alternatively, use high emissive values (greater than 1.0) to ensure they exceed the bloom threshold.
Screen-Space Ambient Occlusion (SSAO)#
SSAO darkens areas where surfaces are close together, adding depth and realism:
import { SSAOPass } from 'three/examples/jsm/postprocessing/SSAOPass.js';import { getComposer } from '@web-engine-dev/core/engine/rendering'; const composer = getComposer();if (composer) { const ssaoPass = new SSAOPass( scene, camera, window.innerWidth, window.innerHeight ); // Configure SSAO ssaoPass.kernelRadius = 16; // Sample radius ssaoPass.kernelSize = 32; // Number of samples ssaoPass.minDistance = 0.001; // Min sample distance ssaoPass.maxDistance = 0.1; // Max sample distance ssaoPass.output = SSAOPass.OUTPUT.Default; composer.addPass(ssaoPass);}| Parameter | Effect | Recommended |
|---|---|---|
| kernelRadius | Occlusion radius in world units | 8-32 |
| kernelSize | Sample count (quality vs performance) | 16-64 |
| minDistance | Prevents self-shadowing | 0.001-0.005 |
| maxDistance | Maximum occlusion distance | 0.05-0.2 |
Depth of Field#
Depth of field simulates camera focus, blurring objects outside the focal range:
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';import { getComposer } from '@web-engine-dev/core/engine/rendering'; const composer = getComposer();if (composer) { const bokehPass = new BokehPass(scene, camera, { focus: 10.0, // Focus distance aperture: 0.025, // Aperture size (blur amount) maxblur: 0.01, // Maximum blur amount width: window.innerWidth, height: window.innerHeight, }); composer.addPass(bokehPass); // Update focus dynamically function updateFocus(targetDistance) { bokehPass.uniforms.focus.value = targetDistance; }}Cinematic DOF
For cinematic scenes, use a large aperture (0.05-0.1) and position the focus on your subject. This creates a shallow depth of field that isolates the subject from the background.
Motion Blur#
Motion blur creates streaking for fast-moving objects or camera movement:
import { MotionBlurPass } from 'three/examples/jsm/postprocessing/MotionBlurPass.js'; const motionBlurPass = new MotionBlurPass(scene, camera, { samples: 16, // Sample count (quality) intensity: 0.5, // Blur strength jitter: 1.0, // Sample jitter velocityFactor: 1.0, // Velocity multiplier}); composer.addPass(motionBlurPass);Performance Impact
Motion blur is expensive as it requires velocity buffers and multiple samples. Use sparingly or disable on mobile/low-end devices. Consider using fewer samples (8-12) for better performance.
Color Grading and Tone Mapping#
Color grading adjusts the final image colors for stylistic or artistic effect:
Tone Mapping#
import * as THREE from 'three'; // Set tone mapping on rendererrenderer.toneMapping = THREE.ACESFilmicToneMapping;renderer.toneMappingExposure = 1.0; // Available tone mapping algorithms:// - THREE.NoToneMapping (default, HDR clipping)// - THREE.LinearToneMapping (simple linear)// - THREE.ReinhardToneMapping (photographic)// - THREE.CineonToneMapping (film-like)// - THREE.ACESFilmicToneMapping (industry standard, recommended) // Adjust exposureupdatePostProcessing({ toneMappingExposure: 1.5, // Brighter toneMappingExposure: 0.8, // Darker});LUT (Look-Up Table)#
Apply color grading using 3D LUTs for precise color control:
import { LUTPass } from 'three/examples/jsm/postprocessing/LUTPass.js';import { LUTCubeLoader } from 'three/examples/jsm/loaders/LUTCubeLoader.js'; // Load LUT file (.cube format)const lutLoader = new LUTCubeLoader();lutLoader.load('lut/cinematic.cube', (lut) => { const lutPass = new LUTPass({ lut: lut.texture3D }); lutPass.intensity = 1.0; // 0 = original, 1 = full LUT composer.addPass(lutPass);}); // Or create custom color adjustmentsconst colorCorrectionPass = new ShaderPass({ uniforms: { tDiffuse: { value: null }, uSaturation: { value: 1.2 }, uContrast: { value: 1.1 }, uBrightness: { value: 1.0 }, }, vertexShader: /* ... */, fragmentShader: /* ... color correction shader ... */,});Custom Post-Processing Effects#
Create your own post-processing effects using ShaderPass:
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; // Custom vignette effectconst vignetteShader = { uniforms: { tDiffuse: { value: null }, uIntensity: { value: 0.5 }, uExtent: { value: 0.8 }, }, vertexShader: /* glsl */ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: /* glsl */ ` uniform sampler2D tDiffuse; uniform float uIntensity; uniform float uExtent; varying vec2 vUv; void main() { vec4 color = texture2D(tDiffuse, vUv); // Distance from center vec2 center = vec2(0.5, 0.5); float dist = distance(vUv, center); // Vignette falloff float vignette = smoothstep(uExtent, uExtent - 0.3, dist); vignette = mix(1.0 - uIntensity, 1.0, vignette); gl_FragColor = vec4(color.rgb * vignette, color.a); } `,}; const vignettePass = new ShaderPass(vignetteShader);composer.addPass(vignettePass);Example: Glitch Effect#
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass.js'; const glitchPass = new GlitchPass(); // Trigger glitch intermittentlyfunction triggerGlitch() { glitchPass.goWild = true; setTimeout(() => { glitchPass.goWild = false; }, 100);} composer.addPass(glitchPass);Example: Film Grain#
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js'; const filmPass = new FilmPass( 0.35, // Noise intensity 0.5, // Scanline intensity 648, // Scanline count false // Grayscale); composer.addPass(filmPass);Pass Execution Order#
Post-processing passes execute in the order they are added. The order matters for certain effects:
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js'; const composer = new EffectComposer(renderer); // 1. Render Pass (renders the scene)const renderPass = new RenderPass(scene, camera);composer.addPass(renderPass); // 2. SSAO (needs depth buffer from render pass)composer.addPass(ssaoPass); // 3. Bloom (before tone mapping for HDR)composer.addPass(bloomPass); // 4. Depth of Field (after bloom)composer.addPass(bokehPass); // 5. Color Grading / LUTcomposer.addPass(lutPass); // 6. Vignettecomposer.addPass(vignettePass); // 7. Output Pass (must be last - handles tone mapping and color space)const outputPass = new OutputPass();composer.addPass(outputPass);Output Pass
Always add an OutputPass as the final pass. It handles tone mapping and color space conversion for proper display.
Performance Optimization#
| Technique | Benefit | Trade-off |
|---|---|---|
| Reduce resolution | 2-4x faster | Lower quality |
| Limit pass count | Faster per frame | Fewer effects |
| Lower sample counts | Faster SSAO/DOF/motion blur | More noise/artifacts |
| Disable on mobile | Works on all devices | No post-processing |
| Use simpler effects | Better performance | Less impressive visuals |
Resolution Scaling#
// Render post-processing at half resolutionconst renderScale = 0.5;const width = window.innerWidth * renderScale;const height = window.innerHeight * renderScale; composer.setSize(width, height); // Upscale to screen resolution// This is 4x faster but slightly blurrierBest Practices#
Effect Selection#
- Start with bloom and tone mapping for HDR rendering
- Add SSAO for enhanced depth perception
- Use depth of field for cinematic scenes
- Avoid stacking too many effects (3-5 is usually enough)
- Test on target devices to ensure acceptable performance
Quality Settings#
- Provide quality presets (Low, Medium, High, Ultra)
- Disable expensive effects on mobile (motion blur, DOF)
- Use lower sample counts on medium settings
- Scale resolution based on device performance
- Let users toggle individual effects in settings
Artistic Use#
- Use bloom sparingly - subtle is usually better
- Match DOF to your scene's scale and camera settings
- Use color grading to establish mood and atmosphere
- Test effects in different lighting conditions
- Avoid over-processing - maintain visual clarity
Accessibility
Some post-processing effects can cause motion sickness or visual discomfort. Always provide options to disable effects like motion blur, chromatic aberration, and screen shake. Respect the prefers-reduced-motion setting.