Code Nodes

Render live shaders, JavaScript drawings, or math plots to an image.

Like making images by writing a bit of code? Three nodes: Shader, JavaScript, and Plot. Each has an editor on the node with inline errors. Shader outputs an image. For the other two, wire the render output into a Renderer 2D node to turn it into an image the rest of the board can use.

Shader  ->  filters, Export, ...
JavaScript / Plot  ->  Renderer 2D  ->  filters, Export, ...

Press Play in the title bar so anything time-based runs.

Shader (SL)#

A shader in SL, the shader language of OneJS games. The code on the node is a .sl file, so the same code runs in a OneJS game and draws the same picture. Drop a .sl file on the board to make a Shader node from it. To take one out, right-click the node: Copy as .sl or Save as .sl....

uniform float speed = 0.5;
uniform float4 tint = #ff8040;
texture2D src;

float4 main() {
    float wave = sin(uv.x * 12 + time * speed * 6) * 0.5 + 0.5;
    float4 under = tex2D(src, uv);
    return float4(lerp(under.rgb, tint.rgb, wave), 1);
}

main returns the colour of each pixel. uv runs 0 to 1 across the image from the bottom left, and time is the clock in seconds.

Each uniform becomes an input port, with a control while nothing is wired in: a slider for a number, a whole-number field for an int, a swatch for a colour written as hex. The controls are under the gear in the node's bottom pill. Each texture2D becomes an image input. Sample it with tex2D(src, uv). tex2Dlod(src, uv, lod) reads a smaller, smoother copy: level 1 is half size, 2 a quarter, and a fraction blends two levels. The node makes those levels only for an input read this way.

Square brackets before a uniform shape its control:

[Range(0, 2)] uniform float speed = 0.5;
[Header("Look")] [Toggle] uniform float flip = 0;
[Enum(Soft, Hard, Glow)] uniform float mode = 0;
[Label("Tint")] uniform float4 tint = #ff8040;
[Hide] uniform float seed = 3;
  • [Range(0, 2)]: the slider stops at 0 and 2. A third number sets its step.
  • [Toggle]: a switch. The uniform is 0 or 1.
  • [Enum(Soft, Hard, Glow)]: a dropdown. The uniform is the pick's place in the list, counting from 0.
  • [Header("Look")]: a heading over this control and the ones after it.
  • [Label("Tint")]: the name the control shows.
  • [Hide]: no control. The port stays, for a wire.
  • [Color]: a swatch, for a colour written as numbers instead of hex.

The controls keep the order the uniforms are written in.

The code can branch and loop: if, for, while, break, continue, switch, and return anywhere. Whole numbers are int and uint, and bool is true or false. A loop that could run forever stops after a set number of turns, so a shader cannot hang the board.

float map(float3 p) { return length(p) - 0.4; }

float4 main() {
    float3 ro = float3(uv - 0.5, -2);
    float t = 0;
    for (int i = 0; i < 100; i++) {
        float d = map(ro + float3(0, 0, t));
        if (d < 0.001) return float4(1, 1, 1, 1);
        t += d;
    }
    return float4(0, 0, 0, 1);
}

A shader can build on what it drew the frame before. tex2D(previous, uv) reads the last frame, frame counts frames and deltaTime is the seconds since the last one. While the timeline plays, each frame follows the one before. A seek, a jump back to the start, a new size or an edit to the code starts again from a clear picture. Rendered to video, it steps exactly one video frame at a time. Inside a layer or clip filter there is no last frame to build on, so previous is always clear there.

float4 main() {
    float4 last = tex2D(previous, uv + float2(0, 0.002));
    float spark = step(length(uv - 0.5), 0.02);
    return max(last * pow(0.5, deltaTime * 3), float4(1, 0.6, 0.2, 1) * spark);
}

Scaled by deltaTime, the trail above fades by half every third of a second at any frame rate.

Shader works in linear light, like a OneJS game: 0.5 is lighter than mid grey, as in Unity. A hex colour comes out as the colour you wrote.

Errors are underlined in the code as you type. They come one kind at a time: fix a typo and any it was hiding show up. The pill shows the first one's line, and how many more there are (Line 3 +2): click it to jump there. When the fix is certain, such as GLSL's mix where SL says lerp, the pill also shows Fix: one click makes the change. The node keeps its last good picture while you fix it.

Got a shader in GLSL, from Shadertoy or a WebGL page? Copy it, right-click a Shader node and choose Paste GLSL: it replaces the node's code with the SL version, in one step you can undo. Or drop a .glsl or .frag file on the board for a new node. Loops, int, bool, iFrame and iTimeDelta carry over. What the change touched, like a line it dropped, is noted in comments at the top of the code. Anything it could not carry over, like a mat2 that is not a rotation, shows on the pill as an error.

While you type, the completion list shows what the highlighted built-in does, in a line under the list. Type [ before a uniform to list the brackets.

Output size is under the gear too, in Output.

JavaScript#

A Canvas2D drawing surface plus the whole node library as functions. Declare outputs at the top, draw in a callback:

//@output renderer2d out 480x360

let { noise } = magerie.generators
let tex = noise("simplex", 3, { width: out.width, height: out.height })

out.draw((ctx, time) => {
    ctx.drawImage(tex, 0, 0, out.width, out.height)
    ctx.fillStyle = `hsl(${time * 40}, 70%, 60%)`
    ctx.fillRect(40, 40, 60, 20)
})

Press Run on the node, or Play in the title bar. magerie.generators and magerie.ops are the node library as functions: noise, gradients, shapes, every blend mode, blur, levels, warp. magerie.anim adds easing curves and springs. The editor suggests names as you type. //@output image <name> gives a finished image output. //@output float <name> drives a slider downstream.

Plot#

A live grapher. One equation per line.

//@xrange -3..3
//@yrange -1.5..1.5

y = sin(2 * x)
y = x^2 / 4

Inputs#

Declare ports at the top of JavaScript. Wire something in and read it by name. Plot takes no inputs. Shader declares its inputs as uniform and texture2D (above).

//@input image src
//@input float strength 0..2 = 1
//@input color tint = #ffaa00

image, float (with an optional slider range and default), vec2 to vec4, color. Read each by name, as src.value.

Where to next#