Post-Processing
The PostProcess API applies full-screen shader effects after each camera renders. Effects are managed through PostProcessStack objects that can be bound to individual cameras, allowing different cameras to have different effects.
Quick Start
import { PostProcess } from 'esengine';
// Create an effect stackconst fx = PostProcess.createStack();fx.addPass('vignette', PostProcess.createVignette());fx.setUniform('vignette', 'u_intensity', 0.6);fx.setUniform('vignette', 'u_softness', 0.5);
// Bind to a camera entityPostProcess.bind(cameraEntity, fx);PostProcessStack
A PostProcessStack is a reusable effect chain. Create one, add passes, and bind it to one or more cameras:
const fx = PostProcess.createStack();
// Add effects (execute in order)fx.addPass('blur', PostProcess.createBlur());fx.addPass('vignette', PostProcess.createVignette());
// Configure uniformsfx.setUniform('blur', 'u_intensity', 3.0);fx.setUniform('vignette', 'u_intensity', 0.8);fx.setUniform('vignette', 'u_softness', 0.3);
// Bind to camera — effects apply immediatelyPostProcess.bind(cameraEntity, fx);Stack Methods
| Method | Description |
|---|---|
addPass(name, shader) | Append a named pass |
removePass(name) | Remove a pass by name |
clearPasses() | Remove all passes |
setEnabled(name, enabled) | Enable or disable a pass |
setUniform(pass, name, value) | Set a float uniform |
setUniformVec4(pass, name, value) | Set a vec4 uniform |
setAllPassesEnabled(enabled) | Enable or disable all passes at once |
passCount | Total number of passes |
enabledPassCount | Number of enabled passes |
destroy() | Destroy the stack (auto-unbinds all cameras) |
Camera Binding
// Bind a stack to a cameraPostProcess.bind(cameraEntity, fx);
// Query the current stackconst stack = PostProcess.getStack(cameraEntity); // null if none
// Unbind (camera renders without effects)PostProcess.unbind(cameraEntity);Built-in Effects
| Method | Effect | Uniforms |
|---|---|---|
createBloomExtract() | Bloom: extract bright pixels | u_threshold (0–1) — brightness cutoff |
createBloomKawase(iteration) | Bloom: Kawase blur pass | u_radius — blur spread, u_resolution (auto) |
createBloomComposite() | Bloom: recombine with scene | u_intensity — glow strength, u_sceneTexture (auto) |
createBlur() | Gaussian blur | u_intensity — blur spread |
createVignette() | Darkened edges | u_intensity (0–1) — effect strength, u_softness (0–1) — falloff |
createGrayscale() | Desaturation | u_intensity (0–1) — blend ratio |
createChromaticAberration() | RGB channel offset | u_intensity — offset amount |
Bloom
Bloom uses a multi-pass pipeline: extract bright pixels, apply several Kawase blur iterations, then composite the result back onto the scene. This produces higher-quality results than a single-pass approach:
const fx = PostProcess.createStack();
// 1. Extract bright pixelsfx.addPass('bloom_extract', PostProcess.createBloomExtract());fx.setUniform('bloom_extract', 'u_threshold', 0.4);
// 2. Kawase blur (multiple iterations for wider, smoother blur)for (let i = 0; i < 5; i++) { fx.addPass(`bloom_kawase_${i}`, PostProcess.createBloomKawase(i)); fx.setUniform(`bloom_kawase_${i}`, 'u_radius', 1.0);}
// 3. Composite bloom back onto the original scenefx.addPass('bloom_composite', PostProcess.createBloomComposite());fx.setUniform('bloom_composite', 'u_intensity', 1.5);
PostProcess.bind(cameraEntity, fx);Blur
9-tap Gaussian blur. Higher u_intensity increases the blur spread:
const fx = PostProcess.createStack();fx.addPass('blur', PostProcess.createBlur());fx.setUniform('blur', 'u_intensity', 3.0);PostProcess.bind(cameraEntity, fx);Vignette
Darkens the screen edges:
const fx = PostProcess.createStack();fx.addPass('vignette', PostProcess.createVignette());fx.setUniform('vignette', 'u_intensity', 0.6);fx.setUniform('vignette', 'u_softness', 0.5);PostProcess.bind(cameraEntity, fx);Grayscale
Blends between the original color and grayscale:
const fx = PostProcess.createStack();fx.addPass('grayscale', PostProcess.createGrayscale());fx.setUniform('grayscale', 'u_intensity', 1.0); // fully grayPostProcess.bind(cameraEntity, fx);Chromatic Aberration
Offsets the R and B channels for a lens distortion look:
const fx = PostProcess.createStack();fx.addPass('chromatic', PostProcess.createChromaticAberration());fx.setUniform('chromatic', 'u_intensity', 2.0);PostProcess.bind(cameraEntity, fx);Screen Post-Processing
Screen-level effects apply after all cameras have rendered, affecting the final composited image. This is useful for global effects like fade-to-black, full-screen vignette, or cinematic letterboxing.
import { PostProcess } from 'esengine';
const screenFx = PostProcess.createStack();screenFx.addPass('vignette', PostProcess.createVignette());screenFx.setUniform('vignette', 'u_intensity', 0.8);
// Apply to the final composited image (after all cameras)PostProcess.setScreenStack(screenFx);
// Remove screen effectsPostProcess.setScreenStack(null);Post-Process Volumes
Volumes allow spatial post-processing — effects that activate when the camera enters a region. Multiple volumes blend by priority, similar to Unity’s Post Process Volumes.
Volume Properties
| Property | Type | Default | Description |
|---|---|---|---|
isGlobal | boolean | true | Applies everywhere regardless of shape |
shape | 'box' | 'sphere' | 'box' | Trigger region shape |
size | { x, y } | { x: 5, y: 5 } | Half-extents (box) or radius (sphere) |
priority | number | 0 | Higher priority overrides lower |
weight | number | 1 | Blend weight (0–1) |
blendDistance | number | 0 | Smooth fade distance at edges (world units) |
Volume Blending
When multiple volumes overlap, the system evaluates each volume’s influence using SDF distance functions and blends their effects by priority:
// Global volume — always active, low priority (base layer)// Entity A: PostProcessVolume with isGlobal=true, priority=0// effects: [{ type: 'vignette', enabled: true, uniforms: { u_intensity: 0.3 } }]
// Local volume — activates when camera enters the region// Entity B: PostProcessVolume with isGlobal=false, shape='sphere', size={x:10,y:10}// priority=1, blendDistance=3// effects: [{ type: 'blur', enabled: true, uniforms: { u_intensity: 5.0 } }]Editor Workflow
Add a PostProcessVolume component to a Camera entity in the editor to configure effects visually:
- Select the Camera entity in the Hierarchy
- Add Component → PostProcessVolume
- Configure volume properties:
- Is Global — check for scene-wide effects, uncheck for spatial volumes
- Priority / Weight — control blending order and strength
- Shape / Size / Blend Distance — define the spatial trigger region (when not global)
- Click Add Effect and choose from the built-in effects
- Adjust parameters with the sliders
Effects are visible in both the Scene View and the Game View. The component data is saved with the scene file.
Custom Pass
Write a custom fragment shader. The vertex shader uses a fixed full-screen triangle — you only write the fragment shader:
import { Material, PostProcess } from 'esengine';
const invertShader = Material.createShader( `#version 300 es precision highp float; layout(location = 0) in vec2 a_position; layout(location = 1) in vec2 a_texCoord; out vec2 v_texCoord; void main() { v_texCoord = a_texCoord; gl_Position = vec4(a_position, 0.0, 1.0); }`, `#version 300 es precision highp float; in vec2 v_texCoord; uniform sampler2D u_texture; uniform float u_intensity; out vec4 fragColor; void main() { vec4 color = texture(u_texture, v_texCoord); vec3 inverted = mix(color.rgb, 1.0 - color.rgb, u_intensity); fragColor = vec4(inverted, color.a); }`);
const fx = PostProcess.createStack();fx.addPass('invert', invertShader);fx.setUniform('invert', 'u_intensity', 1.0);PostProcess.bind(cameraEntity, fx);See Materials & Shaders for more on writing shaders.
Example: Pause Menu Blur
Enable blur and grayscale when the game is paused:
import { PostProcess } from 'esengine';
const pauseFx = PostProcess.createStack();pauseFx.addPass('blur', PostProcess.createBlur());pauseFx.addPass('gray', PostProcess.createGrayscale());pauseFx.setAllPassesEnabled(false);
PostProcess.bind(cameraEntity, pauseFx);
function setPaused(paused: boolean) { pauseFx.setAllPassesEnabled(paused); pauseFx.setUniform('blur', 'u_intensity', 4.0); pauseFx.setUniform('gray', 'u_intensity', 0.6);}Example: Different Effects per Camera
// Main camera — bloom + vignetteconst mainFx = PostProcess.createStack();mainFx.addPass('bloom_extract', PostProcess.createBloomExtract());mainFx.setUniform('bloom_extract', 'u_threshold', 0.4);for (let i = 0; i < 5; i++) { mainFx.addPass(`bloom_kawase_${i}`, PostProcess.createBloomKawase(i));}mainFx.addPass('bloom_composite', PostProcess.createBloomComposite());mainFx.setUniform('bloom_composite', 'u_intensity', 1.5);mainFx.addPass('vignette', PostProcess.createVignette());mainFx.setUniform('vignette', 'u_intensity', 0.6);PostProcess.bind(mainCamera, mainFx);
// Minimap camera — grayscaleconst minimapFx = PostProcess.createStack();minimapFx.addPass('gray', PostProcess.createGrayscale());minimapFx.setUniform('gray', 'u_intensity', 0.5);PostProcess.bind(minimapCamera, minimapFx);Lifecycle
| Method | Description |
|---|---|
PostProcess.createStack() | Create a new effect stack |
PostProcess.bind(camera, stack) | Bind a stack to a camera |
PostProcess.unbind(camera) | Unbind the stack from a camera |
PostProcess.getStack(camera) | Get the bound stack (or null) |
PostProcess.setScreenStack(stack) | Set the screen-level effect stack (pass null to clear) |
PostProcess.screenStack | Get the current screen stack |
PostProcess.init(width, height) | Initialize the pipeline (called automatically) |
PostProcess.shutdown() | Shut down the pipeline |
PostProcess.resize(width, height) | Update framebuffer size (called automatically) |
PostProcess.begin() | Begin capturing the scene for post-processing |
PostProcess.end() | End capture and execute all effect passes |
PostProcess.createBloomExtract() | Create bloom brightness extraction shader |
PostProcess.createBloomKawase(iteration) | Create bloom Kawase blur shader for a given iteration |
PostProcess.createBloomComposite() | Create bloom composite shader |
PostProcess.createBlur() | Create a blur effect shader |
PostProcess.createVignette() | Create a vignette effect shader |
PostProcess.createGrayscale() | Create a grayscale effect shader |
PostProcess.createChromaticAberration() | Create a chromatic aberration shader |
Next Steps
- Custom Draw — immediate-mode drawing primitives
- Materials & Shaders — shader creation and uniforms
- Rendering Overview — camera, transform, render order