Skip to content

Scenes and passes

Describe what each target holds once, and draw every renderer's drawing into it, its shadow and its other views.

latticeThin beams turning over a sky, embers rising through them, smoothed over frames with TAA.
One description writes colour, motion and depth for every drawing in the scene. Switch the view to see each target the drawings filled. Drag to turn the camera.

A scene has three parts. Targets are the textures a frame draws into. A description, made with passes.create, says what each target holds. Drawings are what renderers hand over: what each vertex carries to its pixels, what a pixel looks like and the draw call. scene.add(drawing) builds the pipeline that writes every target the description lists.

The sun’s shadow is a description too, of depth alone, so a lit renderer casts its shadow from the same drawing:

import {
import passes
passes
,
import targets
targets
} from '@typegpu/kit';
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}>
screen
=
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}, options?: targets.Options): targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}>

Creates render targets that follow a size. Replaces the whole set when the size changes. Put targets needed only sometimes in their own set and create that set when needed. Throws for transient targets with sampling, storage or mip levels, for multisampled targets with storage or mip levels, and for layered targets that are multisampled, mipped or have more layers than the device allows.

@param ― source the size it follows, such as a surface

@param ― specs a format and options per target, by name

@param ― options.resolution the fraction of each side drawn, which changes without allocating

@returns ― a set of targets you fit every frame and destroy

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true },
depth: {
readonly format: "depth24plus";
}
depth
: {
format: "depth24plus"
format
: 'depth24plus' },
});
const
const light: lighting.LightingOf<{
readonly shadow: {
readonly extent: 9;
};
}>
light
=
import lighting
lighting
.
function create<{
readonly shadow: {
readonly extent: 9;
};
}>(root: TgpuRoot, options?: ({
readonly shadow: {
readonly extent: 9;
};
} & lighting.Options & {
readonly shadow: {
readonly extent: 9;
};
}) | undefined): lighting.LightingOf<{
readonly shadow: {
readonly extent: 9;
};
}>

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
shadow: {
readonly extent: 9;
} & FixedShadowOptions & {
readonly extent: 9;
}

Enables and configures the shadow from the sun. Default none.

shadow
: {
extent: 9

Sets the width of the square covered by the map in world units. The square is centred on the point passed to follow. Change it with setExtent. A shadow with an eye takes none. Default 20.

extent
: 9 } });
const
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
=
import passes
passes
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}, {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>(root: TgpuRoot, screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}>, writes: {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}, options?: passes.TargetOptions<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}> | undefined): passes.Passes<...> (+1 overload)

Describes the targets and outputs for scene passes. With a surface such as the canvas, writes only each renderer's colour. With a target set, writes colour into the target marked color, depth into the target marked depth, and other outputs into their named targets. Multisampled targets resolve according to resolve. Targets with layers draw one pass per layer, and every target the description draws into needs the same layers. add builds a renderer's drawing. begin starts a pass. pipe supplies fills such as a camera.

@param ― screen the surface, or the targets, which give the formats; each one the description names is drawn into

@param ― writes what fills each target, by its key in screen

@returns ― a description you destroy, which holds the pipelines it builds

create
(
const root: TgpuRoot
root
,
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}>
screen
, {
hdr: typeof passes.color
hdr
:
import passes
passes
.
const color: typeof passes.color

Marks the target that receives each renderer's colour.

color
,
depth: typeof passes.depth
depth
:
import passes
passes
.
const depth: typeof passes.depth

Marks the depth target.

depth
})
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.pipe(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

Adds fill after existing fills and returns a description of the same passes. Piped fills take precedence over a drawing's own. For example, passes.create(…).pipe(view.inject()) uses the camera from view.

pipe
(
const view: cameras.OrbitCamera
view
.
CameraBase.inject(): Inject

Fills cameras.current for the pipelines created from the configuration it is piped into.

inject
())
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.beneath(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

Adds fill after the fills given to earlier beneath calls and returns a description of the same passes. A drawing's own fills take precedence over these, and piped fills over both, so beneath supplies defaults such as a lighting. A compound drawing that passes its own from to pipeline gets them by starting from with inject().

beneath
(
const light: lighting.LightingOf<{
readonly shadow: {
readonly extent: 9;
};
}>
light
.
Lighting.inject(): Inject

Fills the shading hooks and resources for pipelines created from the configuration you pipe it into.

inject
());
const
const renderer: instances.Renderer
renderer
=
const things: instances.Instances<Plain>
things
.
Instances<Plain>.renderer(options?: instances.RendererOptions): instances.Renderer

Creates a renderer for colour and shadow pass descriptions, lit by the lighting each description gives through beneath unless options.light replaces it. Use pipe to fill resources read by kind callbacks.

renderer
(); // a drawing
const
const drawn: passes.Drawn
drawn
=
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.add<{
color: v4f;
}, {
position: v4f;
world: v3f;
normal: v3f;
uv: v2f;
uv1: v2f;
row: number;
}>(this: passes.Passes<passes.Fed<{
color: v4f;
}, {
position: v4f;
world: v3f;
normal: v3f;
uv: v2f;
uv1: v2f;
row: number;
}>>, drawing: passes.Compound<{
color: v4f;
}, {
position: v4f;
world: v3f;
normal: v3f;
uv: v2f;
uv1: v2f;
row: number;
}>): passes.Drawn (+1 overload)

Builds a pipeline for drawing that writes every described output and returns its draw methods. If the drawing cannot supply the fields an output needs, type checking fails with that output's name. Use without to leave it out or instead to replace it.

add
(
const renderer: instances.Renderer
renderer
); // colour and depth, seen by the camera
const
const cast: passes.Drawn
cast
=
const light: lighting.LightingOf<{
readonly shadow: {
readonly extent: 9;
};
}>
light
.
shadow: Shadow | (Cascades & Shadow)

Provides the sun shadow when enabled in the options. Throws after destroy.

shadow
.
ShadowPasses.add<{
color: v4f;
}, {
position: v4f;
world: v3f;
normal: v3f;
uv: v2f;
uv1: v2f;
row: number;
}>(drawing: passes.Compound<{
color: v4f;
}, {
position: v4f;
world: v3f;
normal: v3f;
uv: v2f;
uv1: v2f;
row: number;
}>): passes.Drawn (+1 overload)

Builds a caster's depth pipeline with the shadow's bias and returns its draw methods.

add
(
const renderer: instances.Renderer
renderer
); // depth alone, seen by the sun
import frames
frames
.
function animate(loop: Pick<frames.Frames, "run"> & Partial<Pick<frames.Frames, "seconds">>, options?: frames.AnimateOptions): frames.Animation
export animate

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
(
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; }; }>.fit(): boolean

Rebuilds all textures if the source size changed, destroying the old textures. Updates regions for the current resolution. Returns true after a rebuild so callers can update bind groups that hold old textures. Call before recording passes that use the targets. Throws after destroy.

fit
();
const things: instances.Instances<Plain>
things
.
Instances<Plain>.flush(encoder?: TgpuCommandEncoder): void

Uploads changed rows. When instances are added or removed, it also uploads live rows per mesh and updates draw counts. Call this once per frame before drawing. HistoryInstances take the frame's encoder and throw without it.

flush
();
const light: lighting.LightingOf<{
readonly shadow: {
readonly extent: 9;
};
}>
light
.
shadow: Shadow | (Cascades & Shadow)

Provides the sun shadow when enabled in the options. Throws after destroy.

shadow
.
SunShadow.record(frame: frames.Frame | TgpuCommandEncoder, casters: passes.Drawn | readonly passes.Drawn[], options?: ShadowRecordOptions): void

Clears the map in a depth pass per cascade that redraw picks and draws the casters built by add. Call this while recording each frame, before drawing shaded surfaces. When disabled, it draws nothing and makes every point sunlit. A frame that draws no cascade writes a timer's timestamps from an empty compute pass.

record
(
frame: frames.Frame
frame
,
const cast: passes.Drawn
cast
);
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.record(frame: frames.Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}> | undefined): void

Begins a pass, draws drawn or each drawing in a list in order, then ends the pass. Reuse the list to avoid allocating each frame. Call begin yourself when you need to choose drawings per frame. Records nothing while enabled is false.

record
(
frame: frames.Frame
frame
,
const drawn: passes.Drawn
drawn
);
}),
);

Instances, particles, trails, lines and text each give a drawing and hold no pipeline. Each description they are added to builds the pipelines it needs, for its targets, sample count and fills:

  • One drawing can go into several descriptions. A particle system added to the scene and to the canvas draws the same particles into both.
  • A drawing can be a compound of several pipelines with resources for each view, such as the list of objects a camera sees, so the shadow and the scene cull separately.
  • Your own drawing is a plain object, typed passes.Drawing<Vertex, Surface>. passes.fullScreen gives the vertex for one that covers the screen, such as a sky.

An output is a value each drawing writes at every pixel, beside its colour. The description runs every output for every drawing, so adding one changes the description and no renderer:

  • TAA needs to know where each pixel was last frame. taa.motion computes it for every drawing from its vertices this frame and last.
  • An output whose write takes the surface’s id writes an id at every pixel, for a pick of your own. The picker instead projects candidates.
  • A drawing that lacks the data an output needs fails to type-check in scene.add. Add it through scene.without('id'), or give it an output of its own with scene.instead.

Motion needs each vertex where it was last frame. State that moves vertices lives in history, which keeps last frame’s copy, and a drawing’s vertex code runs once more with history.previously to read it. A table created with history: true keeps its rows that way, so instances moved by your kernels write correct motion.

A description draws through the camera piped into it, so another view is another description over the same drawings:

  • The sun’s shadow and each point and spot shadow are descriptions of depth alone, from the light.
  • Ambient occlusion reads a depth prepass, a description of depth alone, often at half resolution.
  • A description drawn into layers draws the same drawings into each layer of one texture through its own view, as the sun’s shadow cascades are.

Post-processing reads the targets the scene filled. Bloom, TAA, depth of field, FXAA and the final pass each take the frame, or its encoder, and open passes of their own. Each reads its source on every record, through a function or another module’s picture, such as source: aa, so a target rebuilt on resize keeps working. A region option reads part of the source when the scene draws below full resolution with dynamic resolution, and a module’s picture brings its region along.