Skip to content

Shader ​

Run a custom WGSL fragment shader — written, pasted, or AI-generated — as a node. Acts as a generator, filter, or compositor depending on how many image inputs you declare.

Category: Code Menu path: Code > Shader

Ports ​

Ports are dynamic — they're derived from the //! input pragmas in your source.

PortTypeDirectionDescription
in0, in1, …imageRgba16finputOne per //! input you declare. Sample them with textureSample(in0, samp, uv).
outimageRgba16foutputThe shaded result.
  • 0 inputs → generator (procedural pattern, like Noise/Gradient).
  • 1 input → filter (processes the upstream image, like Blur/Levels).
  • 2+ inputs → compositor (combines layers, like Merge/Mix).

Parameters ​

ParamTypeDefaultDescription
sourcestringstarter generatorThe shader source. Edited in the code editor in Properties (not a normal field).
formatenumWGSLShader language: WGSL (Caddis's native contract) or GLSL (Shadertoy-style). Switching swaps in that language's starter.

Plus dynamic params — every //! param you declare becomes a node param (slider / color / vector) you can keyframe like any other. (GLSL is builtins-only in v1 — see below.)

GLSL / Shadertoy ​

Set Format → GLSL to write (or paste) Shadertoy-style shaders. You write a standard Shadertoy entry point:

glsl
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    fragColor = vec4(0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0,2,4)), 1.0);
}

The engine wraps it in a Shadertoy-compatible prelude (declaring iResolution, iTime, iFrame, iMouse), flips Y to Shadertoy's bottom-left origin, transpiles GLSL → WGSL via naga, and renders it. Don't write #version, precision, uniform declarations, or main() — those are synthesized. iResolution is the composition size, so output is resolution-independent (identical at Draft/Preview/Full).

This makes paste-a-Shadertoy work for the huge class of single-pass procedural shaders — including raymarching and fractals (they're just mainImage with a loop; nothing special is needed).

v1 limitations: builtins-only — no iChannel texture inputs, no custom //! param knobs, and no multipass (Buffer A–D). Drive everything from iTime / iResolution. A pasted shader that samples iChannel0 won't compile yet; the AI assistant (set to GLSL) can rework it to be procedural.

How It Works (WGSL) ​

How It Works ​

You write a single shade(uv) function (plus any helper functions) and a small pragma header that declares your inputs and params. The engine generates the surrounding WGSL — the texture/sampler/uniform bindings, the param struct, and the @fragment entry point — compiles your shader at runtime (cached by source hash), and runs it as a fullscreen GPU pass at the working precision (RGBA 16-bit float).

The authoring contract:

wgsl
//! input in0 label="Source"
//! param intensity float default=0.5 range=0,1 label="Intensity"
//! param tint color default=1,0,0,1 label="Tint"

fn shade(uv: vec2<f32>) -> vec4<f32> {
    let src = textureSample(in0, samp, uv);
    return vec4<f32>(src.rgb * params.tint.rgb * params.intensity, src.a);
}

Available inside shade:

  • uv — vec2<f32> in [0,1], origin top-left.
  • samp — a linear sampler for your input textures.
  • in0, in1, … — the textures you declared with //! input.
  • params.<name> — your declared params (float→f32, vec2/vec3, color→vec4<f32>).
  • builtins.iResolution (vec2<f32>, composition size), builtins.iTime (f32, seconds), builtins.iFrame (f32).

Return straight-alpha RGBA — the engine premultiplies. Don't write @group/@binding lines, the param/builtins structs, or @fragment fn fs_main — the engine generates those (declaring them yourself is a duplicate-definition compile error).

Pragma grammar:

  • //! input <id> [label="..."] — <id> is in0, in1, … in order.
  • //! param <name> <type> [default=<comma,sep>] [range=<min>,<max>] [label="..."] — <type> is float | vec2 | vec3 | color. For color, default is r,g,b,a. label= must be the last field on the line.

Compile errors never crash the engine: they surface as inline markers on the failing line in the editor, and the node keeps rendering the last shader that compiled.

AI authoring (the Assistant panel) ​

You don't have to know WGSL. With a Shader node selected, open the Assistant panel and describe the effect — "a VHS glitch with chromatic aberration and rolling scanlines" — and the AI writes the shader into the node. Refine iteratively: "thinner scanlines", "add an Intensity knob". Generated code is compiled before it's applied; if it doesn't compile, the error is fed back automatically (a couple of retries) so you only ever get working shaders.

Two ways to power it (Assistant → Settings):

  • Caddis AI — uses Caddis' service and your token balance.
  • Use my own key — calls your own Claude or OpenAI account directly. No tokens are used and your prompts stay on your account. Your key is stored encrypted on your device.

Usage Examples ​

Basic: Procedural generator ​

Create a Shader node with no //! input lines. The starter source is an animated rainbow gradient driven by builtins.iTime — a self-contained generator you can drop on a layer and animate immediately.

Filter: Tint + intensity ​

Add //! input in0 and sample it. Wire an image (or any rendered chain) into in0; the shader processes whatever flows through. Add //! param lines to expose live controls.

Compositor: Blend two inputs ​

Declare in0 and in1, sample both, and mix — e.g. mix(a, b, params.amount). The node now has two image input ports.

Tips ​

  • Typing is the primary path — the editor has line numbers, WGSL highlighting, and inline error markers. The AI is optional.
  • The code editor in Properties is resizable — drag its bottom edge to give yourself more room.
  • builtins.iResolution is the composition size, so coordinate math (uv * iResolution) is identical at Draft / Preview / Full quality.
  • Animate with builtins.iTime (seconds) rather than iFrame for frame-rate-independent motion.
  • WGSL is strongly typed: write 0.0, not 0, where a float is expected, and use explicit constructors like vec3<f32>(1.0, 0.0, 0.0).
  • To feed a Shader's output into a field (for point/shape work), pass it through ImageSample.
  • Noise — built-in procedural generator
  • Gradient — built-in spatial gradient generator
  • UVRemap — built-in UV-space distortion
  • DrawAscii — another fullscreen GPU shader effect