Scene module

Scene module Manual

This manual teaches the Scene module from first sketch to advanced patterns.

The Scene module is a small Processing.org like environment built on top of Metal Shading Language (MSL). You write Metal code, the runtime injects a drawing API around it, and each frame your code emits 2D or 3D geometry.

Unlike shaders, which operate on pixels, the Scene module works with vertices and triangles. It lets you define and manipulate 3D geometry directly, giving you control over the shape, position, and structure of objects before they are rendered to the screen. In other words, shaders determine how surfaces look, while the Scene module determines what geometry exists and where it is placed.

It is entirely code-driven and uses MSL together with custom macros that make defining and constructing objects more convenient. The Scene module also allows you to define custom state that persists between frames. This makes it well suited for stateful compositions, where geometry, motion, simulation data, or other parameters need to evolve continuously over time rather than being recalculated from scratch for every frame.

The most important idea is simple:

  • your program is real Metal code
  • draw() is called every frame
  • optional parallelDraw() runs afterward when present
  • you draw by calling helper functions such as background(...), line(...), circle(...), push(), translate(...), and rotate(...)
  • those built-in helper names are runtime macros that expand to functions needing ctx underneath
  • any user helper function that calls built-in drawing/runtime helpers should accept thread RuntimeContext &ctx, and callers should pass ctx

The Scene module is especially good for:

  • simple sketches and studies
  • geometric compositions
  • animated generative art
  • transform-based recursive structures
  • particle and swarm-like systems
  • procedural 3D scenes and surfaces

If you are new to shader-style code, do not worry about Metal first. You can begin by thinking of the Scene module as “Processing with stricter syntax and a GPU-oriented runtime.” The one extra rule to remember is that drawing helpers secretly depend on the runtime context, so pass ctx through your own drawing helpers.

1. 2D and 3D at a Glance

The Scene module has two main drawing modes:

  • 2d mode for flat drawing, line work, shape composition, particles, and screen-space animation
  • 3d mode for lit solids, camera-based scenes, procedural surfaces, and spatial structures

If you do not specify a render mode, the default is 2d.

Smallest 2D sketch:

void draw(thread RuntimeContext &ctx) {
    background(0.08, 0.10, 0.14, 1.0);
    circle(0.0, 0.0, 0.25);
}

Smallest 3D sketch:

[[render_mode(3d)]]
void draw(thread RuntimeContext &ctx) {
    camera(float3(0.0, 0.0, 5.0),
           float3(0.0, 0.0, 0.0),
           float3(0.0, 1.0, 0.0));
    perspective(0.92, 0.1, 50.0);
    ambientLight(float3(0.16, 0.16, 0.18));
    directionalLight(normalize(float3(-0.6, -1.0, -0.4)),
                     float3(1.0, 1.0, 1.0),
                     1.2);

    fill(0.30, 0.72, 0.98, 1.0);
    box(1.2, 1.2, 1.2);
}

This manual teaches both modes in this order:

  1. introduce 2d and 3d early
  2. build the full 2D model first
  3. move into a dedicated 3D part afterward

3D is easier to learn once the core Scene module ideas already feel natural:

  • drawing each frame
  • style state
  • transforms
  • helper functions
  • repetition and structure

Part I: 2D

2. The Smallest Useful Sketch

Every sketch defines one frame entry:

  • void draw(thread RuntimeContext &ctx)

This is the smallest useful program:

void draw(thread RuntimeContext &ctx) {
    background(0.08, 0.10, 0.14, 1.0);
    line(-0.6, 0.0, 0.6, 0.0);
}

What happens here:

  • draw(...) runs once per frame
  • background(...) clears the frame
  • line(...) draws a single line segment

The ctx parameter is required even when you do not use it directly.

3. How the Scene module Thinks

The Scene module is not a custom scripting language. It compiles your code together with the runtime as Metal source. That means you can use normal Metal features such as:

  • struct
  • float, float2, float3, float4
  • int, uint, bool
  • helper functions
  • if, for, while, break, continue
  • math functions such as sin, cos, dot, length, normalize, clamp

The drawing facade is implemented with macros. For example, a call such as circle(0.0, 0.0, 0.1) is expanded by the runtime into a lower-level call that uses ctx. This is why draw() and parallelDraw() must receive thread RuntimeContext &ctx, and why user helper functions that call built-ins must receive and pass ctx too.

Example:

float wave(float x, float t) {
    return sin(x * 8.0 + t);
}

void marker(thread RuntimeContext &ctx, float x, float y) {
    fill(1.0, 0.9, 0.4, 1.0);
    circle(x, y, 0.08);
}

void draw(thread RuntimeContext &ctx) {
    float y = wave(0.2, time()) * 0.4;
    marker(ctx, 0.0, y);
}

wave(...) is pure math, so it does not need ctx. marker(...) calls fill(...) and circle(...), so it must receive thread RuntimeContext &ctx and must be called as marker(ctx, ...).

The runtime also injects a small Processing-style facade, so you can call:

  • float time()
  • float beatTime()
  • uint frameCount()
  • float width()
  • float height()
  • uint threadId()
  • uint threadCount()

and all drawing helpers directly from draw(), parallelDraw(), or a user helper that has received ctx. You do not pass ctx into the built-in call itself because the macro does that part for you.

4. Coordinates and What You See on Screen

In 2d mode, the Scene module uses an aspect-correct square coordinate system.

  • (0, 0) is the center of the screen
  • positive x goes right
  • positive y goes downward
  • the square [-1, 1] x [-1, 1] is always fully visible

This means:

  • circle(0.0, 0.0, 0.25) draws a circle in the center
  • line(-1.0, 0.0, 1.0, 0.0) spans the full visible width of the square view
  • equal x and y distances stay visually equal even on wide or tall windows

The coordinate system is one of the most important things to internalize early. The Scene module is not pixel-based by default. Most drawing uses this normalized drawing space.

5. Step 1: Set the Background

The first thing beginners usually want is control over the whole frame:

void draw(thread RuntimeContext &ctx) {
    background(0.02, 0.04, 0.08, 1.0);
}

Colors are usually expressed in the 0.0 ... 1.0 range per channel:

  • red
  • green
  • blue
  • alpha

So:

  • background(0.0, 0.0, 0.0, 1.0) is opaque black
  • background(1.0, 1.0, 1.0, 1.0) is opaque white
  • background(0.0, 0.0, 0.0, 0.0) is transparent black

In a parallel sketch, put frame-global setup such as background(...), camera, and lights in draw().

6. Step 2: Draw a Line

Lines use stroke settings:

void draw(thread RuntimeContext &ctx) {
    background(0.05, 0.06, 0.08, 1.0);
    stroke(1.0, 0.9, 0.7, 1.0);
    strokeWeight(0.02);
    line(-0.7, -0.4, 0.7, 0.4);
}

Key ideas:

  • stroke(...) sets the line color and enables stroking
  • strokeWeight(...) sets thickness
  • line(x0, y0, x1, y1) draws one segment

Important detail:

  • strokeWeight(...) uses drawing-space units, not pixels

So 0.02 is a visible drawing thickness, not “2 pixels.”

7. Step 3: Draw Basic Shapes

The core 2D shape helpers are:

  • rect(float x, float y, float width, float height)
  • triangle(float x0, float y0, float x1, float y1, float x2, float y2)
  • circle(float x, float y, float radius)
  • disc(float x, float y, float radius)
  • glowDisc(float x, float y, float radius)

Example:

void draw(thread RuntimeContext &ctx) {
    background(0.06, 0.07, 0.10, 1.0);

    stroke(1.0, 1.0, 1.0, 1.0);
    strokeWeight(0.01);

    fill(0.20, 0.70, 1.0, 0.30);
    rect(-0.75, -0.25, 0.45, 0.50);

    fill(1.0, 0.55, 0.25, 0.90);
    circle(0.0, 0.0, 0.22);

    fill(0.35, 1.0, 0.60, 0.85);
    triangle(0.35, 0.28,
             0.80, 0.20,
             0.58, -0.30);
}

Useful facts:

  • circle(...) takes a radius, not a diameter
  • disc(...) is a filled hard-edged disc
  • glowDisc(...) is a soft radial disc for particles and glows
  • glowDisc(...) is 2D-only

8. Fill, Stroke, and Default Style

Style state matters a lot in the Scene module.

In 2d mode, the runtime begins each frame with:

  • stroke enabled
  • fill disabled
  • identity transform

So if you call circle(...) at the start of a fresh frame, you get an outlined circle, not a filled one.

Useful style commands:

  • background(float4 color)
  • background(float r, float g, float b, float a)
  • stroke(float4 color)
  • stroke(float r, float g, float b, float a)
  • fill(float4 color)
  • fill(float r, float g, float b, float a)
  • noStroke()
  • noFill()
  • strokeWeight(float weight)

Example:

void draw(thread RuntimeContext &ctx) {
    background(0.04, 0.05, 0.07, 1.0);

    fill(0.25, 0.70, 1.0, 0.25);
    stroke(1.0, 1.0, 1.0, 1.0);
    strokeWeight(0.012);
    circle(-0.35, 0.0, 0.20);

    noStroke();
    fill(1.0, 0.55, 0.30, 1.0);
    circle(0.35, 0.0, 0.20);
}

This is worth remembering:

  • rect(...), triangle(...), and circle(...) can use both fill and stroke
  • line strips use stroke only
  • beginShape(...) geometry uses fill only

9. Motion: Drawing Changes Every Frame

Animation usually starts with time():

void draw(thread RuntimeContext &ctx) {
    background(0.04, 0.05, 0.08, 1.0);
    noStroke();
    fill(1.0, 0.75, 0.25, 1.0);

    float t = time();
    float x = cos(t) * 0.55;
    float y = sin(t * 1.4) * 0.35;
    circle(x, y, 0.08);
}

Two things are happening:

  • time() grows continuously
  • beatTime() is a host-supplied musical sequence clock and stays 0.0 until the host updates it
  • sin(...) and cos(...) turn time into smooth motion

You also have:

  • frameCount() for a frame index
  • width() and height() for viewport size in pixels

For most motion studies, time() is the right starting point. For beat-synced visuals, use beatTime().

10. The Transform Stack

This is where the Scene module starts to feel powerful.

The transform commands are:

  • push()
  • pop()
  • translate(float x, float y)
  • translate(float x, float y, float z)
  • rotate(float angle)
  • rotateX(float angle)
  • rotateY(float angle)
  • rotateZ(float angle)
  • scale(float x, float y)
  • scale(float x, float y, float z)

You can think of them as “move the local drawing space, then draw there.”

Example:

void draw(thread RuntimeContext &ctx) {
    background(0.05, 0.05, 0.07, 1.0);
    fill(0.30, 0.80, 0.55, 0.55);
    stroke(1.0, 1.0, 1.0, 1.0);
    strokeWeight(0.01);

    push();
    translate(0.2, 0.1);
    rotate(0.6);
    scale(0.7, 0.4);
    rect(-0.3, -0.3, 0.6, 0.6);
    pop();
}

What this means:

  • push() saves the current transform and style state
  • translate(...) moves the local origin
  • rotate(...) rotates future drawing
  • scale(...) scales future drawing
  • pop() restores the previous state

This is the foundation of context-free style drawing: you define a local coordinate system, draw something, then move into a new local system and draw again.

Important notes:

  • rotate(...) uses radians, not degrees
  • non-uniform scale(x, y) can turn circles into ellipses
  • pop() without a matching push() causes a stack underflow diagnostic
  • maximum stack depth is 8

11. Local Coordinates and Repetition

Once you understand transforms, you can repeat a shape in many places without rewriting its geometry.

void petal(thread RuntimeContext &ctx) {
    fill(0.90, 0.35, 0.55, 0.40);
    rect(-0.18, -0.08, 0.36, 0.16);
}

void draw(thread RuntimeContext &ctx) {
    background(0.04, 0.04, 0.06, 1.0);
    noStroke();

    for (uint i = 0u; i < 12u; i += 1u) {
        float a = float(i) / 12.0 * 6.2831853;
        push();
        rotate(a);
        translate(0.35, 0.0);
        rotate(a * 0.5);
        petal(ctx);
        pop();
    }

    fill(1.0, 0.95, 0.80, 1.0);
    circle(0.0, 0.0, 0.10);
}

This pattern appears everywhere in the Scene module:

  • define a helper
  • use transforms to place it
  • repeat it in a loop

12. Drawing Longer Lines and Custom Shapes

Single primitives are not the whole story. You can also build geometry incrementally.

12.1 Polyline Drawing

Polyline helpers:

  • beginLine(float x, float y)
  • lineTo(float x, float y)
  • endLine()

Example:

void draw(thread RuntimeContext &ctx) {
    background(0.05, 0.06, 0.09, 1.0);
    noFill();
    stroke(0.75, 0.92, 1.0, 1.0);
    strokeWeight(0.012);

    beginLine(-0.7, 0.0);
    lineTo(-0.3, -0.25);
    lineTo(0.0, 0.35);
    lineTo(0.3, -0.20);
    lineTo(0.7, 0.0);
    endLine();
}

12.2 Triangle-Based Custom Shapes

Shape helpers:

  • beginShape()
  • beginShape(TRIANGLE_STRIP)
  • beginShape(TRIANGLE_FAN)
  • vertex(float x, float y)
  • endShape()

Each vertex(...) stores the current transform and current fill color. That means if you change fill(...) between vertices, the result becomes a gradient across the generated triangles.

Example:

void draw(thread RuntimeContext &ctx) {
    background(0.05, 0.05, 0.08, 1.0);
    noStroke();

    beginShape(TRIANGLE_FAN);
    fill(1.0, 1.0, 1.0, 1.0);
    vertex(0.0, 0.0);
    fill(0.35, 0.65, 1.0, 1.0);
    vertex(-0.5, -0.2);
    fill(0.20, 1.0, 0.65, 1.0);
    vertex(-0.1, -0.55);
    fill(1.0, 0.85, 0.25, 1.0);
    vertex(0.55, -0.10);
    fill(1.0, 0.35, 0.45, 1.0);
    vertex(0.20, 0.45);
    endShape();
}

13. User Parameters

You can expose adjustable parameters directly in draw() or parallelDraw():

void draw(thread RuntimeContext &ctx,
              float radius [[param]],
              float speed [[param]]) {
    float t = time() * (0.2 + speed * 2.0);
    float x = cos(t) * 0.5;
    float y = sin(t) * 0.5;

    noStroke();
    fill(0.9, 0.6, 0.2, 1.0);
    circle(x, y, 0.04 + radius * 0.2);
}

Rules:

  • the first parameter of draw() and parallelDraw() must be thread RuntimeContext &ctx
  • argument annotations tell the runtime what kind of entry an argument is
  • supported user arguments are float, Poly, MIDIKeys, and texture2d<float>
  • float name [[param]] exposes a knob-style user parameter
  • float name [[param, bipolar]] exposes a bipolar knob-style user parameter
  • Poly name [[input]] receives a host poly input
  • MIDIKeys name [[input]] receives host MIDI key state
  • texture2d<float> name [[input]] receives a host texture input
  • regular [[param]] floats are normalized to the 0.0 ... 1.0 range
  • bipolar parameters use [[param, bipolar]] and are normalized to the -1.0 ... 1.0 range
  • input resources must use [[input]]
  • textures can be used only as inputs and must be declared as texture2d<float> name [[input]]
  • a parameter may be declared only in draw(), only in parallelDraw(), or in both
  • if both functions declare the same parameter name with the same type and annotations, they share one UI parameter
  • duplicate names with different types or annotations are compile errors
  • parameters are exposed in first-seen declaration order
  • the current float parameter maximum is 32
  • the current Poly input maximum is 32
  • the current texture input maximum is 1
  • the current MIDIKeys input maximum is 1

Bipolar parameter example:

void draw(thread RuntimeContext &ctx,
          float turn [[param, bipolar]]) {
    rotate(turn * 3.14159265);
}

Texture inputs can be sampled directly in user code:

void draw(thread RuntimeContext &ctx, texture2d<float> image [[input]]) {
    float2 uv = float2(0.5 + 0.25 * cos(time()),
                       0.5 + 0.25 * sin(time()));
    float4 color = sampleTexture(image, uv);

    noStroke();
    fill(color);
    circle(0.0, 0.0, 0.35);
}

Use sampleTexture(image, uv) for normalized coordinates, sampleTexturePx(image, xy) for pixel coordinates, and textureSize(image) when you need the real texture dimensions. Use brightness(color) to convert a sampled float4 color to alpha-weighted perceived luminance. Use gradient(image, uv) or gradientPx(image, xy) to read the local brightness direction.

Texture sampling helper signatures:

  • uint2 textureSize(texture2d<float> tex)
  • float4 sampleTexture(texture2d<float> tex, float2 uv)
  • float4 sampleTexture(texture2d<float> tex, float u, float v)
  • float4 sampleTexturePx(texture2d<float> tex, float2 xy)
  • float4 sampleTexturePx(texture2d<float> tex, float x, float y)
  • float brightness(float4 color)
  • float2 gradient(texture2d<float> tex, float2 uv)
  • float2 gradient(texture2d<float> tex, float u, float v)
  • float2 gradientPx(texture2d<float> tex, float2 xy)
  • float2 gradientPx(texture2d<float> tex, float x, float y)

When you want to apply the input image as a material instead of sampling it manually, call useInputTexture() or useInputTexture(scale) before drawing. Right now that material path works on built-in 3D solids and surfaces, plus rect(...) in 2d.

Texture material helper signatures:

  • noTexture()
  • textureUV()
  • textureChecker(float scale)
  • textureStripes(float scale)
  • textureDots(float scale)
  • textureGrid(float scale)
  • textureNoise(float scale)
  • useInputTexture()
  • useInputTexture(float scale)

Parameters are ideal when you want:

  • a speed slider
  • a size control
  • a color bias
  • a spacing or density control

14. User Data and Frame-to-Frame Recurrence

By default, your function redraws from scratch each frame. To remember anything from one frame to the next, declare State.

Example:

struct State {
    float angle;
    float2 position;
    float trail[6];
};

void draw(thread RuntimeContext &ctx) {
    float angle = state().angle + 0.035;
    state().angle = angle;

    for (uint i = 0u; i < 5u; i += 1u) {
        state().trail[i] = state().trail[i + 1u];
    }
    state().trail[5] = angle;

    float2 position = float2(cos(angle), sin(angle * 1.37)) * 0.46;
    state().position = position;

    background(0.04, 0.05, 0.07, 1.0);
    noStroke();

    for (uint i = 0u; i < 6u; i += 1u) {
        float t = state().trail[i];
        float fade = float(i + 1u) / 6.0;
        float2 p = float2(cos(t), sin(t * 1.37)) * 0.46;
        fill(0.22 + fade * 0.55, 0.42 + fade * 0.45, 1.0, 0.16 + fade * 0.12);
        circle(p.x, p.y, 0.024 + fade * 0.028);
    }

    fill(1.0, 0.98, 0.82, 0.95);
    circle(position.x, position.y, 0.07);
}

The important model is:

  • state() is this frame’s writable single-frame state
  • before draw() starts, the runtime copies the previous State into state()
  • if you do not overwrite a field, it keeps its previous value

State access helper signatures:

  • state()
  • parallelState()
  • parallelStateAt(uint i)

State is the shared per-frame state for the sketch. If you also use parallelDraw(), that same State can be read there after draw() has updated it for the current frame.

This is the right place to talk about recurrence in the mathematical sense. A stateful sketch often evolves by a recurrence rule:

  • new position depends on old position
  • new angle depends on old angle
  • new velocity depends on old velocity and forces

That is how you build:

  • trails
  • bouncing systems
  • swarms
  • particle systems
  • simple simulations

Current State and ParallelState fields are intentionally limited. Supported field types are:

  • float
  • int
  • uint
  • float2
  • float3
  • float4
  • fixed-size arrays of those types

15. Parallel Sketches

The Scene module can run many logical drawing lanes in parallel:

void draw(thread RuntimeContext &ctx,
          float spread [[param]]) {
    background(0.03, 0.035, 0.06, 1.0);
}

[[parallel_count(128)]]
void parallelDraw(thread RuntimeContext &ctx,
                  float spread [[param]]) {
    float lane = float(threadId()) / float(max(threadCount() - 1u, 1u));
    float angle = lane * 6.2831853 + time() * 0.4;
    float radius = 0.2 + spread * 0.6;

    noStroke();
    fill(0.2 + lane * 0.8, 0.4, 1.0 - lane * 0.5, 0.35);
    circle(cos(angle) * radius, sin(angle) * radius, 0.03);
}

Concepts:

  • draw() runs once first and owns frame-global setup
  • parallelDraw() is optional
  • [[parallel_count(N)]] on parallelDraw() runs N logical drawing lanes afterward
  • threadId() gives the current lane index
  • threadCount() gives the active lane count
  • [[parallel_count(N)]] belongs on parallelDraw(), not draw()

This is useful when one sketch is really “many similar actors”:

  • many particles
  • many circles
  • many branches
  • many orbiting objects

In 2d, draw order is deterministic:

  • all output from draw() first
  • then all output from parallel lane 0
  • then all output from parallel lane 1
  • and so on

In 3d, both stages contribute to the same scene and visibility is determined by depth.

16. State in Parallel

When ParallelState and parallelDraw() are both active, each parallel lane gets its own state slot.

Declare ParallelState only when each lane needs its own persistent data:

struct ParallelState {
    float angle;
    float2 position;
};
  • state() reads the shared State written by draw()
  • parallelState() is the current lane’s writable state
  • parallelStateAt(i) reads lane i from the previous frame

That distinction is very important:

  • write shared state only in draw()
  • write lane state only through parallelState()
  • use parallelStateAt(i) when one actor needs to inspect other actors from the previous frame
  • do not use threadId() == 0u as a substitute for frame-global setup; that belongs in draw()

This makes patterns such as flocking, networks, and agent interaction possible without exposing partially updated state from the current frame.

17. Recursive Drawing

Recursive drawing is one of the clearest expressions of the transform-stack model.

In recursion, a helper function draws one piece of a structure, changes the local transform, and calls itself again with a smaller size or a deeper level.

Example:

void branch(thread RuntimeContext &ctx,
            float length,
            uint depth) {
    beginLine(0.0, 0.0);
    lineTo(0.0, length);
    endLine();

    if (depth >= 5u) {
        return;
    }

    push();
    translate(0.0, length);

    push();
    rotate(0.42);
    branch(ctx, length * 0.72, depth + 1u);
    pop();

    push();
    rotate(-0.42);
    branch(ctx, length * 0.72, depth + 1u);
    pop();

    pop();
}

void draw(thread RuntimeContext &ctx) {
    background(0.04, 0.05, 0.07, 1.0);
    stroke(0.94, 0.86, 0.65, 1.0);
    noFill();
    strokeWeight(0.008);

    push();
    translate(0.0, -0.92);
    branch(ctx, 0.22, 0u);
    pop();
}

Why this works:

  • each call draws in its own local coordinate system
  • push() and pop() isolate each branch
  • smaller child calls produce self-similar structure

The runtime tracks recursion depth and reports a diagnostic if the recursion limit is reached.

Part II: 3D

18. 3D Mode

Everything so far has focused on 2d, because that is the best place to learn the model. The Scene module also supports a lit 3d mode:

[[render_mode(3d)]]
void draw(thread RuntimeContext &ctx) {
    camera(float3(0.0, 1.5, 6.0),
           float3(0.0, 0.0, 0.0),
           float3(0.0, 1.0, 0.0));
    perspective(0.92, 0.1, 50.0);
    ambientLight(float3(0.16, 0.17, 0.20));
    directionalLight(normalize(float3(-0.7, -1.0, -0.5)),
                     float3(1.0, 0.97, 0.92),
                     1.2);

    push();
    rotateY(time() * 0.5);
    fill(0.30, 0.72, 0.98, 1.0);
    specular(0.92, 0.98, 1.0, 1.0);
    shininess(24.0);
    box(1.2, 1.2, 1.2);
    pop();
}

The biggest differences from 2d:

  • you enable it with [[render_mode(3d)]]
  • the transform system becomes 3D
  • you usually set camera and lighting once in draw()
  • you do not need leader-thread guards inside draw()
  • fill/material state matters more than stroke

Useful 3D transform and camera helpers:

  • push()
  • pop()
  • translate(float x, float y, float z)
  • scale(float x, float y, float z)
  • rotate(float angle)
  • rotateX(float angle)
  • rotateY(float angle)
  • rotateZ(float angle)
  • camera(float3 eye, float3 target, float3 up)
  • perspective(float fovY, float nearPlane, float farPlane)

Useful 3D material and lighting helpers:

  • background(float4 color)
  • background(float r, float g, float b, float a)
  • ambientLight(float3 color)
  • directionalLight(float3 direction, float3 color, float intensity)
  • shadows()
  • shadows(float strength)
  • noShadows()
  • fill(float4 color)
  • fill(float r, float g, float b, float a)
  • stroke(float4 color)
  • stroke(float r, float g, float b, float a)
  • noStroke()
  • noFill()
  • strokeWeight(float weight)
  • specular(float4 color)
  • specular(float r, float g, float b, float a)
  • shininess(float value)
  • ambientStrength(float value)
  • diffuseStrength(float value)
  • noTexture()
  • textureUV()
  • textureChecker(float scale)
  • textureStripes(float scale)
  • textureDots(float scale)
  • textureGrid(float scale)
  • textureNoise(float scale)
  • useInputTexture()
  • useInputTexture(float scale)
  • environmentSphere()
  • environmentSphere(float radius)

shadows(float strength) enables a 3D shadow map from the first directional light only. It is ignored in 2d; presets that do not call it stay on the faster non-shadow render path.

environmentSphere(...) creates a camera-centered inverted sphere using the current texture style, so it can show useInputTexture(...) or procedural textures as a sky/environment layer.

Useful 3D solids:

  • sphere(float radius)
  • capsule(float radius, float height)
  • capsule(float3 start, float3 end, float radius)
  • plane(float width, float depth)
  • pipe(float innerRadius, float outerRadius, float height)
  • box(float width, float height, float depth)
  • cone(float radius, float height)
  • cylinder(float radius, float height)
  • torus(float majorRadius, float minorRadius)

capsule(radius, height) builds a Y-aligned capsule using total end-to-end height, including the hemispherical caps.

capsule(start, end, radius) builds a capsule between two float3(...) spine points. start and end are the centers of the hemispherical caps, so the cylindrical middle section spans exactly between them.

plane(width, depth) builds a centered XZ plane on the local y = 0 surface, with UVs spanning 0..1 across width and depth.

pipe(innerRadius, outerRadius, height) builds a centered Y-aligned hollow cylinder with inner and outer walls plus top and bottom ring caps.

In 3d mode, the default style changes:

  • fill enabled
  • stroke disabled

That default is sensible because most 3D drawing is about lit surfaces.

19. Advanced 3D Geometry

The Scene module also supports incremental 3D surface generation.

Tube path:

  • beginTube(float radius, uint segments)
  • tubeVertex(float x, float y, float z)
  • tubeVertex(float x, float y, float z, float radius)
  • endTube()

Grid surface:

  • beginGridSurface(uint cols, uint rows)
  • surfaceVertex(float x, float y, float z)
  • endGridSurface()

Sphere-like procedural surface:

  • beginSphereSurface(uint segments)
  • sphereVertex(float radius)
  • endSphereSurface()

These are useful when a built-in solid is not enough and you want:

  • ribbons and tubes
  • terrain-like surfaces
  • blobby or deformed spheres

Part III: General Notes

20. Practical Rules and Limits

The most important practical rules are:

  • use radians for all rotation functions
  • use push() and pop() to isolate transforms
  • use State when shared data must persist between frames
  • use ParallelState only when each parallel lane needs its own persistent memory
  • use parallelDraw(), threadId(), and threadCount() when the same sketch logic should run many times
  • put frame-global setup such as background(...), camera, and lights in draw()
  • remember that 2d and 3d helpers are not interchangeable

Current fixed limits include:

  • maximum stack depth: 8
  • maximum primitives: 131072
  • maximum polyline points: 65536
  • maximum generated vertices: 1048576
  • maximum user parameters: 32

Possible diagnostics include:

  • primitive buffer overflow
  • point buffer overflow
  • vertex buffer overflow
  • stack overflow
  • stack underflow
  • recursion limit reached

If output is unexpectedly truncated, you may be hitting a geometry limit.

21. A Good Learning Path

If you are starting from zero, this order works well:

  1. background
  2. line
  3. circle, rect, triangle
  4. fill and stroke
  5. time-based motion
  6. push, pop, translate, rotate, scale
  7. loops and repeated forms
  8. user parameters
  9. State and recurrence
  10. parallel sketches
  11. recursion
  12. 3D mode

That order matches the way the language grows conceptually:

  • first you draw one thing
  • then you place many things
  • then you make them evolve
  • then you make them interact

22. When to Use the Reference

Use this manual when you want to learn, understand patterns, or see complete examples.

Use reference.md when you want the fast technical answer to questions like:

  • what is the exact entry-point signature
  • what helpers exist in 2d or 3d
  • what are the defaults
  • what are the limits