Route Graphics has a fully inline shader-effects interface:
elements[].filters[] post-processes a visual element.animations[].compositor combines previous and next transition surfaces.An effect can be a single pass or an ordered multi-pass chain. It can expose independently animated scalar, vector, and matrix parameters, use custom textures, opt into deterministic time, and run on WebGL or WebGPU.
There is no root effects registry. Shader source and configuration stay next to their owner, while unchanged compiled programs are still reused internally.
Every pass supplies both GLSL and WGSL. Choose the preferred renderer during initialization:
await graphics.init({
// other options
rendererPreference: "webgpu",
rendererFallback: true,
});
console.log(graphics.rendererType); // "webgpu" or fallback "webgl"
The defaults are rendererPreference: "webgl" and
rendererFallback: true. Set fallback to false when using a different
backend would be an error.
filters works on every built-in visual element:
rect, text, containersprite, spritesheet-animation, videoinput, slider, text-revealing, particlesMultiple effects execute in array order. Passes inside an effect also execute in array order:
element rendering
-> built-in effects
-> filters[0].passes[0]
-> filters[0].passes[1]
-> filters[1]
-> final output
For input, the GPU-rendered element is filtered. The HTML editor temporarily
shown while the input is focused is a DOM overlay and is not shader-filtered.
elements:
- id: portrait
type: sprite
src: portrait
x: 80
y: 40
width: 480
height: 640
filters:
- id: grade
type: shader
parameters:
amount: 0.35
tint: [0.3, 0.8, 1]
textures:
noise:
src: film-noise
wrap: repeat
mipmap: true
time: true
padding: 8
pipeline:
blend: normal
textureWrap: clamp
mipmap: false
source:
webgl:
fragment: |
precision mediump float;
in vec2 vTextureCoord;
out vec4 finalColor;
uniform sampler2D uTexture;
uniform float uProgress;
uniform float uTime;
uniform vec2 uResolution;
uniform float uAmount;
uniform vec3 uTint;
void main(void)
{
vec4 color = texture(uTexture, vTextureCoord);
vec3 shifted = mix(color.rgb, color.rgb * uTint, uAmount);
finalColor = vec4(shifted, color.a);
}
webgpu:
source: |
// Full WGSL source with mainVertex and mainFragment.
Each filter id must be unique within its element.
Use passes instead of source:
filters:
- id: bloom
type: shader
parameters:
radius: 8
strength: 0.7
padding: 24
resolution: 0.5
passes:
- id: horizontal
uniforms:
direction: [1, 0]
source:
webgl:
fragment: |
# GLSL horizontal blur
webgpu:
source: |
# WGSL horizontal blur
- id: vertical
uniforms:
direction: [0, 1]
source:
webgl:
fragment: |
# GLSL vertical blur
webgpu:
source: |
# WGSL vertical blur
- id: combine
pipeline:
blend: add
source:
webgl:
fragment: |
# GLSL combine
webgpu:
source: |
# WGSL combine
The first pass reads the element surface through uTexture; every later pass
reads the previous pass output. This is a linear chain, not an arbitrary render
graph.
Top-level values are inherited by each pass. A pass may override pipeline,
mesh, padding, resolution, antialias, clipToViewport, and time.
It may add pass-local static uniforms and textures.
Use top-level parameters for values you plan to animate. Use pass-local
uniforms for fixed values such as the blur direction.
| Field | Default | Notes |
|---|---|---|
id |
required for filters | Optional on a compositor |
type |
required | Must be shader |
parameters |
{} |
Shared mutable/animatable values |
uniforms |
{} |
Backward-compatible alias for parameters |
textures |
{} |
Shared custom textures |
source |
- | Single-pass source; exclusive with passes |
passes |
- | Non-empty ordered pass list; exclusive with source |
pipeline.blend |
normal |
normal, add, multiply, screen |
pipeline.textureWrap |
clamp |
clamp or repeat |
pipeline.mipmap |
false |
Default custom-texture mipmapping |
mesh.grid |
[1, 1] |
[columns, rows], each 1 through 512 |
padding |
0 |
Extra output extent in pixels |
resolution |
1 |
Positive scale or inherit |
antialias |
off |
on, off, inherit, or boolean |
clipToViewport |
true |
Viewport clipping |
time |
false |
Include deterministic uTime |
Names use lower camel case and become shader symbols by adding u and
capitalizing the first letter:
amount -> uAmount
edgeWidth -> uEdgeWidth
colorMatrix -> uColorMatrix
Supported values:
| YAML value | GLSL / WGSL type |
|---|---|
| number | float / f32 |
| 2-number array | vec2 / vec2<f32> |
| 3-number array | vec3 / vec3<f32> |
| 4-number array | vec4 / vec4<f32> |
| 9-number array | mat3 / mat3x3<f32> |
| 16-number array | mat4 / mat4x4<f32> |
You can state the type explicitly:
parameters:
exposure:
type: f32
value: 1.2
tint:
type: vec3
value: [1, 0.8, 0.5]
Accepted type names are f32, vec2, vec2<f32>, vec3, vec3<f32>,
vec4, vec4<f32>, mat3, mat3x3<f32>, mat4, and mat4x4<f32>.
Do not define both parameters and legacy uniforms on one effect.
Target one filter by id:
animations:
- id: portrait-glow
targetId: portrait
type: update
playback:
continuity: persistent
loop: true
tween:
filters:
grade:
amount:
keyframes:
- duration: 500
value: 1
easing: easeInOutSine
- delay: 250
duration: 500
value: 0.2
easing: easeInOutSine
tint:
keyframes:
- duration: 1000
value: [0.4, 0.7, 1]
easing: linear
An update animation may combine ordinary properties and any number of filter
ids in one tween. Arrays interpolate component by component. Missing initial
values come from the filter's current parameters. Only one active animation
may write a particular target/filter/parameter channel.
Shader parameter keyframes accept the same optional delay as ordinary
tweens. It must be a finite number greater than or equal to zero and holds the
previous scalar, vector, or matrix value before that segment starts.
Use progress to animate one filter's built-in uProgress:
tween:
filters:
grade:
progress:
initialValue: 0
keyframes:
- duration: 300
value: 1
A compositor is the same inline effect shape, but receives two captured surfaces:
animations:
- id: burn
targetId: scene
type: transition
mask:
- kind: single
texture: paper-mask
compositor:
type: shader
parameters:
edgeWidth: 0.04
passes:
- id: burnEdge
source:
webgl:
fragment: |
# GLSL
webgpu:
source: |
# WGSL
- id: grade
source:
webgl:
fragment: |
# GLSL
webgpu:
source: |
# WGSL
tween:
progress:
initialValue: 0
keyframes:
- duration: 900
value: 1
easing: linear
edgeWidth:
keyframes:
- duration: 900
value: 0.12
compositor.tween.progress is required and maps to uProgress. Other tracks
target declared compositor parameters and infer their initial values from
compositor.parameters.
Masks and compositors can be combined. The mask executes first, followed by the
custom compositor passes. Every custom pass still receives the captured next
surface as uNextTexture.
Every pass receives:
| Input | Meaning |
|---|---|
uTexture |
Original input or previous pass output |
uProgress |
Filter or transition progress |
uResolution |
Pass size in logical pixels |
uTime |
Deterministic seconds, only with time: true |
Compositor passes additionally receive:
| Input | Meaning |
|---|---|
uNextTexture |
Captured next surface |
uNextTextureMatrix |
Maps the primary UV into next-texture space |
uNextTextureClamp |
Safe next-texture sampling bounds |
Always transform and clamp before sampling uNextTexture; previous and next
surfaces can have different bounds and transforms.
time: true adds uTime, in seconds. Automatic playback advances it from the
renderer ticker. Manual setAnimationTime(timeMS) sets animation sampling and
shader time together, so offline frames and screenshots are repeatable.
uTime is read-only. Animate a custom parameter for an authored timeline.
Time is opt-in to preserve the WGSL uniform layout of existing shaders.
Texture names map to symbols ending in Texture:
noise -> uNoiseTexture, uNoiseTextureSampler
displacementMap -> uDisplacementMapTexture, uDisplacementMapTextureSampler
Use an asset alias/URL directly or override sampling:
textures:
noise:
src: film-noise
wrap: repeat
mipmap: true
Per-texture values override pipeline defaults. A filter pass supports up to seven shared plus local custom textures; a compositor pass supports six.
source.webgl.fragment is required. source.webgl.vertex is optional. When it
is omitted, Route Graphics provides its standard filter vertex shader and
the fragment shader can consume vTextureCoord:
precision mediump float;
in vec2 vTextureCoord;
out vec4 finalColor;
uniform sampler2D uTexture;
uniform float uProgress;
uniform vec2 uResolution;
void main(void)
{
finalColor = texture(uTexture, vTextureCoord);
}
Custom mesh deformation requires a custom vertex shader accepting
in vec2 aPosition.
WGSL must define mainVertex and mainFragment and use the Route Graphics
shader groups:
@group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;
@group(0) @binding(1) var uTexture: texture_2d<f32>;
@group(0) @binding(2) var uSampler: sampler;
@group(1) @binding(0) var<uniform> shaderUniforms: ShaderUniforms;
Declare ShaderUniforms fields in this exact order:
uProgress: f32uTime: f32 when time: trueuResolution: vec2<f32>uNextTextureMatrix: mat3x3<f32>uNextTextureClamp: vec4<f32>Every WebGPU custom texture has an adjacent generated sampler. Filter pairs
start at bindings 1 and 2 in lexical key order. For compositors,
uNextTexture uses binding 1, so the first custom pair uses bindings 2 and 3:
@group(1) @binding(1) var uNoiseTexture: texture_2d<f32>;
@group(1) @binding(2) var uNoiseTextureSampler: sampler;
let noise = textureSample(uNoiseTexture, uNoiseTextureSampler, uv);
Always use the custom sampler for its texture. Group 0's uSampler belongs to
the filter input and does not contain the custom texture's wrap or mipmap
settings.
These bindings and uniform ordering are the stable Route Graphics shader ABI. They currently map onto Pixi internally, but authored shaders must not depend on any additional Pixi globals or objects. Renderer upgrades are handled by the internal adapter without changing this contract.
The repository's bun run test:webgpu command disables renderer fallback and
compiles/renders representative timed, multipass, textured-mesh, and compositor
effects through an actual WebGPU browser. This is separate from the default
WebGL visual-test run.
mesh.grid: [1, 1] is one quad. Subdivision enables vertex deformation but
does not change layout or semantic hit-test bounds.
Subdivided filter geometry depends on the tested Pixi filter system internals, so Route Graphics pins its Pixi version and contains all private access in one versioned adapter. Architecture tests prevent that access from spreading into the shader runtime. An intentional Pixi upgrade updates the adapter while the authored shader contract remains unchanged.
Use padding for effects that draw outside the source bounds. Transition
overlays include compositor padding.
Route Graphics shader output uses premultiplied alpha. When constructing color
from unpremultiplied values, return vec4(rgb * alpha, alpha).
The last compositor frame is shown before Route Graphics reveals the live next
target. Make the final shader result converge on uNextTexture to avoid a
visible handoff jump.
Changing only parameter values updates existing effects in place. Source, passes, static uniforms, textures, pipeline, mesh, or pass-option changes rebuild the effect. The renderer caches compiled programs by source.
Shader objects are strict. Unknown keys at the effect, pass, source, pipeline, mesh, texture, and typed-parameter levels are rejected instead of being ignored. Parameter values must be finite and animation values must match the declared scalar, vector, or matrix shape.
parse(...) checks configuration and animation bindings. render(...) also
preflights programs before changing the display tree. Invalid configuration,
an unloaded custom texture, or a synchronous WebGL compiler/linker failure
throws a contextual Route Graphics shader error and leaves the last good scene
mounted.
WebGPU layout and module creation are preflighted synchronously. The WebGPU API
reports final driver compiler diagnostics asynchronously, so those final
messages can still be delivered by the browser after render(...) returns.
The intentional boundaries are:
See Animation Node for the full animation and transition model.