What you write
Write helper functions if you need them, plus one entry point that takes a vec2 and returns a vec4:
vec4 shade(vec2 uv) {
vec4 color = gp_sample_input_frames(uv);
return vec4(color.rgb * gp_amount, color.a);
}Grapple wraps your source in a complete GLSL 4.50 program: the version line, the texture bindings, the parameter block, the output and main. uv runs from 0 to 1 across the frame being rendered, and the colour you return is the effect's output for that pixel.
- Name the entry point
- Any identifier that isn't reserved, such as
shade.main, GLSL keywords, and names starting withgl_orgp_are reserved. - Leave out declarations
- Don't write
#version,main,layout, uniforms, samplers orin/outvariables. - Line numbers
- Compiler messages use the line numbers of your own source.
Built-in values
| Name | Type | Meaning |
|---|---|---|
gp_sample_<input>(uv) | vec4 | Samples a declared input at uv, returning colour and alpha. With an input called input_frames, call gp_sample_input_frames(uv). |
gp_time | float | The current time in seconds. |
gp_resolution | vec2 | The size in pixels of the frame being rendered. It is smaller in a reduced-resolution preview. |
gp_composition_resolution | vec2 | The composition's frame size in pixels, the same in preview and export. |
gp_pixel | vec2 | The size of one rendered pixel in uv units, for sampling neighbouring pixels. |
gp_<name> | Depends on the parameter | Each declared parameter, such as gp_amount. |
For sizes that should look the same in preview and in the export, such as an offset of 12 pixels, divide by gp_composition_resolution rather than using gp_pixel.
Parameters
Declared parameters appear in Properties and can be keyed. In the shader they have these types:
| Control | GLSL type |
|---|---|
| Slider or angle | float |
| Toggle | bool |
| Point or vector | vec2 |
| Colour | vec3 |
Parameters share a small block of memory with the time and resolution: there is room for 28 numbers in total, where a colour counts as three and a point as two. Parameter and input names must be valid GLSL identifiers and unique.
Inputs, output and placement
A shader effect has one output, named frames, and up to three inputs, each read through its own gp_sample_ function. Where the effect sits decides what its main input receives:
- On a track
- The contents of one visual track.
- On an adjustment layer
- Everything beneath the adjustment layer.
- On the composition
- The finished composition, as the last step before output.
Shader effects run on Grapple's Vulkan renderer and need a GPU with Vulkan support.
Examples
Channel split
vec4 shade(vec2 uv) {
vec2 offset = vec2(gp_split / gp_composition_resolution.x, 0.0);
float r = gp_sample_input_frames(uv + offset).r;
vec4 c = gp_sample_input_frames(uv);
float b = gp_sample_input_frames(uv - offset).b;
return vec4(r, c.g, b, c.a);
}Parameter: split, a slider in composition pixels. Key it from 0 to 12 and back over a few frames for a hit on a cut.
Vignette
vec4 shade(vec2 uv) {
vec4 c = gp_sample_input_frames(uv);
vec2 aspect = vec2(gp_composition_resolution.x / gp_composition_resolution.y, 1.0);
float d = length((uv - 0.5) * aspect);
float edge = smoothstep(gp_radius - gp_softness, gp_radius, d);
return vec4(c.rgb * (1.0 - gp_amount * edge), c.a);
}Parameters: radius, softness and amount. The aspect correction keeps the vignette round on a wide frame.
Heat-haze wave
vec4 shade(vec2 uv) {
float wave = sin(uv.y * gp_frequency + gp_time * gp_speed);
float shift = wave * gp_amplitude / gp_composition_resolution.x;
return gp_sample_input_frames(vec2(uv.x + shift, uv.y));
}Parameters: frequency, speed and amplitude in composition pixels. Because it reads gp_time, the haze moves on its own; key amplitude to fade it in and out.
Add a shader to a project
Ape adds shader effects with the effect.author_from_source operation. Paste your function into the Ape panel with the parameters and where the effect should go, and Ape builds the request and looks up the track for you. This is the request for the channel split on one track:
{
"displayName": "Channel split",
"implementationKind": "shader",
"language": "grapple-shader-0",
"entrypoint": "shade",
"source": "vec4 shade(vec2 uv) { ... }",
"params": [{
"name": "split",
"label": "Split",
"description": "Distance between the red and blue channels, in composition pixels.",
"value": 0,
"editor": { "family": "scalar", "min": 0, "max": 40, "step": 0.5 }
}],
"inputPorts": [{ "name": "input_frames", "type": "frame_stream", "frameStream": { "space": "composition_space" } }],
"outputPorts": [{ "name": "frames", "type": "frame_stream", "frameStream": { "space": "composition_space" } }],
"activeRange": { "start": 0, "end": 30 },
"placement": { "kind": "track", "trackNodeId": "<track id>", "inputPort": "input_frames", "outputPort": "frames" }
}For the finished composition, use "placement": { "kind": "composition_finish", "compositionNodeId": "<composition id>", "inputPort": "input_frames", "outputPort": "frames" }. The active range is in seconds. To send the request from the command line:
grapple-cli --open-package "C:\Users\you\Documents\Grapple\Night walk" --invoke-tool effect.author_from_source --arguments-file split.jsonRun --invoke-tool project.inspect with an arguments file containing {} to find track and composition IDs.
Check the result
Preview a short, representative range before extending the effect across a sequence, and look at it at the size it will be delivered. If the effect doesn't compile, the message names the line in your source. A parameter of an unsupported type, or more parameters than fit, is reported by name, and a machine without a usable Vulkan GPU reports that shaders can't run.