Skip to content

Effects and compositions

An effect changes a picture; a composition is the graph that decides which effects run, on what, and in what order. This page explains both, and how to register an effect of your own. After reading it you can grade, stylize and layer a clip’s picture.

Every use of an effect is a ClipEffect:

{
"id": "alpha2.effect.vignette",
"version": 1,
"params": { "amount": { "mode": "constant", "value": { "type": "float", "value": 0.6 } } }
}
  • id and version name an exact definition. The built-in effects are alpha2.effect.*, version 1; the effect reference lists all of them with their parameters.
  • params are named, animatable values. Each value is typed: float, vec2, vec4, color (straight sRGB RGBA in 0..1), enum (an index into the definition’s choices) or bool. Parameters you leave out take the definition’s defaults.
  • resources maps the definition’s resource names to the project’s effect_resources, for example a .cube LUT for alpha2.effect.lut-3d.

Try one. The preview below runs alpha2.effect.rgb-split on a photo:

The explorer lets you pick any built-in effect and move its parameters:

A clip doesn’t list effects directly. It shows a composition: an acyclic graph of nodes with one output. Each node has an id and an op; node order has no meaning, because nodes refer to each other by id.

op.type What it produces
clip The picture of the clip showing the composition: its source at the time-mapped source time, or what its generated source draws.
media Another visual source (video, image or generated), read at start plus the composition time. It adds no sound.
generator A picture the engine draws: a fill, shapes, text or layout.
effect A registered effect applied to input, plus any extra named inputs it samples.
transition A registered transition between from and to, at an explicit, animatable progress.
transform The input placed by a position, anchor, scale, rotation and opacity.
merge layers composited bottom to top, each with a blend_mode and opacity; at most 32.
matte The input, kept where a second image’s alpha or luminance says.
shape_mask The input, kept inside shapes.
group Another composition drawn as one node, with its inputs fed in and its controls set.
group_input Inside a composition used as a group, the image the group node gives input index.

Any node with an input can set "bypass": true to pass that input through unchanged, which is how an editor toggles an effect off.

To chain effects, point each node’s input at the node before it. Your first render chains color-controls into vignette this way.

This composition turns the clip’s picture black and white, darkens its corners, then places a second photo over it as a picture-in-picture:

{
"id": "inset",
"nodes": [
{ "id": "main", "op": { "type": "clip" } },
{
"id": "mono",
"op": {
"type": "effect",
"input": "main",
"effect": { "id": "alpha2.effect.black-and-white", "version": 1 }
}
},
{
"id": "darker",
"op": {
"type": "effect",
"input": "mono",
"effect": {
"id": "alpha2.effect.vignette",
"version": 1,
"params": { "amount": { "mode": "constant", "value": { "type": "float", "value": 0.7 } } }
}
}
},
{ "id": "other", "op": { "type": "media", "source_id": "forest" } },
{
"id": "placed",
"op": {
"type": "transform",
"input": "other",
"transform": {
"position": { "mode": "constant", "value": { "x": 0.75, "y": 0.3 } },
"scale": { "mode": "constant", "value": { "x": 0.35, "y": 0.35 } }
}
}
},
{
"id": "stack",
"op": { "type": "merge", "layers": [{ "input": "darker" }, { "input": "placed" }] }
}
],
"output": "stack"
}

The media node reads the forest source, transform scales it to 35% and moves it to the upper right, and merge stacks the graded picture under it. The merge node is the output.

A composition can be reused inside another through a group node. The inner composition declares inputs (image ports its group_input nodes read) and controls (named effect parameters a group node may set, each pointing at a node and param). The group node then supplies images for the ports and values for the controls. Compositions nest at most 64 deep.

"composition": { "id": "look", "offset": 0, "scale": { "num": 1, "den": 1 } }

offset and scale map the clip’s time onto the composition’s time, which is the clock its animated settings use. Both default to the values above. See Composition time.

A composition can also be a source of its own ("kind": "composition"), shown by clips like any other source and drawn with the source time as its own time.

Besides the built-in catalog, you can define an effect in WGSL and register it at run time. A definition (EffectDefinition in alpha2/effects) has an id, a version, a kind ("effect" or "transition"), its params, optional extra inputs and resources, and wgslBody, the WGSL inserted into the engine’s effect shader. An effect body defines fn effect(uv: vec2f, src: vec4f) -> vec4f, reads its parameters as params.<name>, and samples an extra input <name> with sample_<name>(uv).

const tint = {
id: "example.warm",
version: 1,
kind: "effect",
params: [{ name: "amount", type: "float", default: 0.5, min: 0, max: 1, unit: "normalized" }],
wgslBody:
"fn effect(uv: vec2f, src: vec4f) -> vec4f { " +
"let warm = vec3f(src.r, src.g * 0.9, src.b * 0.7); " +
"return vec4f(mix(src.rgb, warm, params.amount), src.a); }",
};
// In the browser, on the engine you render with:
const key = engine.registerCompiledEffect(await engine.compileEffect(JSON.stringify(tint)));
// key === "example.warm@1"; a project now uses it as { "id": "example.warm", "version": 1 }

compileEffect rejects with EFFECT_ERROR and the shader’s diagnostics when the body does not compile. RuntimeEffectRegistry in alpha2/effects is a typed wrapper over the same calls. Natively, pass the definitions with the export: effects in @alpha2-sh/node and Python, --effects effects.json on the command line.

Loading a project checks its document: references, ranges, keyframe order. It does not check effect ids or parameter values, because the registry decides which effects exist. A frame that draws an effect the renderer has not registered, or a parameter value outside the definition’s range, fails with EFFECT_ERROR when it renders.