Web Engine Docs
Preparing documentation
Use the search bar to quickly find any topic
Preparing documentation
Use the search bar to quickly find any topic
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.
HDR bloom with adjustable intensity, threshold, and radius
Screen-space ambient occlusion for contact shadows
Realistic camera focus with bokeh blur
Camera and object motion blur
Tone mapping, color correction, and LUTs
Build your own post-processing passes
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 effectsrenderPostProcessing();}
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.
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 SSAOssaoPass.kernelRadius = 16; // Sample radiusssaoPass.kernelSize = 32; // Number of samplesssaoPass.minDistance = 0.001; // Min sample distancessaoPass.maxDistance = 0.1; // Max sample distancessaoPass.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 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 distanceaperture: 0.025, // Aperture size (blur amount)maxblur: 0.01, // Maximum blur amountwidth: window.innerWidth,height: window.innerHeight,});composer.addPass(bokehPass);// Update focus dynamicallyfunction 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 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 strengthjitter: 1.0, // Sample jittervelocityFactor: 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 adjusts the final image colors for stylistic or artistic effect:
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, // BrightertoneMappingExposure: 0.8, // Darker});
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 LUTcomposer.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 ... */,});
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 centervec2 center = vec2(0.5, 0.5);float dist = distance(vUv, center);// Vignette fallofffloat 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);
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);
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js';const filmPass = new FilmPass(0.35, // Noise intensity0.5, // Scanline intensity648, // Scanline countfalse // Grayscale);composer.addPass(filmPass);
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.
| 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 |
// 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 blurrier
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.