Phaser AE

Post-process chain

Free packv3.0.0-52D and 3DPost

The full-screen effect runtime. Once the world is drawn, `post` runs each effect you added as one pass over the frame, in order, and the HUD is drawn crisp above the result. It owns the chain rather than the looks, so you compose the effect packs you want, or write your own in a few lines of WGSL.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 20, 2026

Post-process chain: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

post

function post(): Pack<{ post: PostApi }>

The post-process runtime pack. Adds the post pass (after lighting, before screen) and exposes game.post. Owns no effects: compose post-looks and the other post-* packs for those. Supports WebGPU and WebGL2; with no effects added the pass is skipped and costs nothing.

Helpers

checkPostEffect

function checkPostEffect(def: PostEffectDef, opts: { maxCode?: number } = {}): string | null

Check a post effect definition the way the runtime takes it, with no GPU: the name, each param's default (a finite number, a CSS colour the kernel parses, or 2 to 4 finite numbers; a param's name is only its key in add and set, since the snippet reads it by slot, p0..p15) and the sixteen slots they share; the code (it must set color, use no while or loop, since an unbounded loop can hang the GPU, call no texture function but tex, and stay under maxCode characters, default 8000); and its WebGL2 translation, which names an unknown identifier (a param read by its name rather than its p slot), an unknown function or an unsupported construct. Returns the problem in one sentence, or null.

registerPostEffect

function registerPostEffect(k: { registry: { add(kind: string, def: PostEffectDef): void } }, def: PostEffectDef): void

Register an effect under the registry kind 'post' so game.post.add(def.name) finds it: the one-liner a post-* pack's attach is, and a game's own effect as registerPostEffect(game.kernel, def). The definition is checked first (checkPostEffect), so a bad name, param or snippet throws here, saying what is wrong, rather than when it is drawn. Registering a name again replaces the effect (the last definition wins), and the copies in the chain take it on the next frame, keeping their values by param name; an identical definition changes nothing, so a scene's enter can register its effect every time it runs. Two packs registering one name as they compose throws.

Functions

postScope

function postScope(): SnippetScope

What a snippet can see, by GLSL type, as the WebGL2 translator is told it. tex(p) is a shared helper.

Constants

POST_PARAM_SLOTS

const POST_PARAM_SLOTS: 16

The number of f32 param slots an effect has, p0..p15.

Types

PostApi

interface PostApi

game.post: the effect chain. Effects run in add order, each reading the previous result; the HUD draws above.

  • add(name: string, params?: Record<string, PostParam>): PostHandle — Append an effect by name. Params override the definition's defaults. Scene-scoped. Throws if no composed pack registered name (the message names the post-<name> pack to add), if a param key is unknown or its value the wrong kind, or if the definition needs more than sixteen slots.
  • clear(): void — Empty the chain. Existing handles become inert.
  • readonly chain: ReadonlyArray<string> — Effect names in run order; a fresh array each read.
  • readonly available: ReadonlyArray<string> — Every effect a composed pack registered.

PostEffectDef

interface PostEffectDef

What a post-* pack registers: one full-screen effect as data. code is the body of a WGSL fragment function with uv, time, texel, tex(p) and p0..p15 in scope; it leaves its result in color. See engine/packs/post/post/docs/post.md, "Writing an effect pack".

  • name: string — The name game.post.add() is called with, e.g. 'bloom'. Unique per composed game.
  • description?: string — One line for the planner's menu: what it looks like.
  • code: string — WGSL: the fragment body. Starts with color = tex(uv); must leave the result in color (vec4, straight alpha).
  • params: Record<string, PostParam> — The params and their defaults, laid into p0..p15 in key order: a number takes one slot, a CSS colour three (r, g, b), an array its length. At most sixteen slots; add() throws past that.

PostHandle

interface PostHandle

One added effect in the chain, returned by PostApi.add().

  • set(param: string, value: PostParam): void — Set one param live: a number, a CSS colour for a colour param, or an array (or one number for every component) for a vector param. Throws if param is not one the effect declares, if the value is the wrong kind, or if a colour does not parse.
  • remove(): void — Remove this instance from the chain. Idempotent; also run by the scene's teardown.
  • readonly name: string — The effect name this handle was added with.

PostParam

type PostParam = number | string | readonly number[]

One param value. A number fills one slot; a CSS colour string fills three (red, green, blue, 0..1; alpha is ignored); an array of 2 to 4 numbers fills that many. The DEFAULT in PostEffectDef.params fixes the kind: a vector param also takes one number for every component, and a colour param takes only a CSS string.