Skip to content

Connecting modules

How one module's shaders read another module's resources, and how the kit's own modules fill each other.

A particle kernel reads the camera. A fish’s steering reads the neighbour grid. Your lighting reads ambient occlusion. Each time, a shader reads resources another module owns. It reads them through an accessor, the owner fills the accessor with its inject(), and the pipeline is created from root.pipe(owner.inject()), as with the ripple in How the kit works.

A weather module keeps the wind in a uniform and offers windAt to other shaders. A snow module’s kernel calls it, so the snow takes the weather as an argument and pipes its fill:

export function
function createWeather(root: TgpuRoot): {
state: TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>;
windAt: () => d.v2f;
inject: () => Inject;
}
createWeather
(
root: TgpuRoot
root
:
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
) {
const
const state: TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>
state
=
root: TgpuRoot
root
.
TgpuRoot.createUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>(typeSchema: d.WgslStruct<{
wind: d.Vec2f;
}>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslStruct<{
wind: d.Vec2f;
}>>>) => void) | d.InferInput<NoInfer<d.WgslStruct<{
wind: d.Vec2f;
}>>> | undefined): TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader. Read-only on the GPU, optimized for small data. For a general-purpose buffer, use

TgpuRoot.createBuffer

.

@param ― typeSchema The type of data that this buffer will hold.

@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)

createUniform
(
const WeatherState: d.WgslStruct<{
wind: d.Vec2f;
}>
WeatherState
);
const
const weather: TgpuAccessor<d.WgslStruct<{
wind: d.Vec2f;
}>>
weather
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
accessor: <d.WgslStruct<{
wind: d.Vec2f;
}>>(schema: d.WgslStruct<{
wind: d.Vec2f;
}>, defaultValue?: DataAccessorIn<NoInfer<d.WgslStruct<{
wind: d.Vec2f;
}>>> | undefined) => TgpuAccessor<d.WgslStruct<{
wind: d.Vec2f;
}>> (+1 overload)
accessor
(
const WeatherState: d.WgslStruct<{
wind: d.Vec2f;
}>
WeatherState
);
return {
state: TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>
state
,
windAt: () => d.v2f
windAt
: () => {
'use gpu';
return
const weather: TgpuAccessor<d.WgslStruct<{
wind: d.Vec2f;
}>>
weather
.
TgpuAccessor<WgslStruct<{ wind: Vec2f; }>>.$: {
wind: d.v2f;
}
$
.
wind: d.v2f
wind
;
},
inject: () => Inject
inject
: ():
type Inject = (cfg: Configurable) => Configurable

Fills module accessors for pipelines created from the configuration it is piped into. Modules return it from inject(), as TypeGPU caches do. Use root.pipe(table.inject()).

Inject
=> (
cfg: Configurable
cfg
) =>
cfg: Configurable
cfg
.
Withable<Configurable>.with<d.WgslStruct<{
wind: d.Vec2f;
}>>(accessor: TgpuAccessor<d.WgslStruct<{
wind: d.Vec2f;
}>>, value: DataAccessorIn<NoInfer<d.WgslStruct<{
wind: d.Vec2f;
}>>>): Configurable (+2 overloads)
with
(
const weather: TgpuAccessor<d.WgslStruct<{
wind: d.Vec2f;
}>>
weather
,
const state: TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>
state
),
};
}
export function
function createSnow(root: TgpuRoot, weather: ReturnType<typeof createWeather>): TgpuComputePipeline
createSnow
(
root: TgpuRoot
root
:
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
,
weather: {
state: TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>;
windAt: () => d.v2f;
inject: () => Inject;
}
weather
:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<typeof
function createWeather(root: TgpuRoot): {
state: TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>;
windAt: () => d.v2f;
inject: () => Inject;
}
createWeather
>) {
const
const drift: TgpuMutable<d.WgslArray<d.Vec2f>>
drift
=
root: TgpuRoot
root
.
TgpuRoot.createMutable<d.WgslArray<d.Vec2f>>(typeSchema: d.WgslArray<d.Vec2f>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslArray<d.Vec2f>>>) => void) | d.InferInput<NoInfer<d.WgslArray<d.Vec2f>>> | undefined): TgpuMutable<d.WgslArray<d.Vec2f>> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader. Can be mutated in-place on the GPU. For a general-purpose buffer, use

TgpuRoot.createBuffer

.

@param ― typeSchema The type of data that this buffer will hold.

@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)

createMutable
(
import d
d
.
arrayOf<d.Vec2f>(elementType: d.Vec2f, elementCount: number): d.WgslArray<d.Vec2f> (+2 overloads)
export arrayOf

@location ― . Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).

arrayOf
(
import d
d
.
const vec2f: d.Vec2f
vec2f
, 1024));
const
const fall: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
fall
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [64],
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly clipDistances: d.BuiltinClipDistances;
readonly position: d.BuiltinPosition;
readonly frontFacing: d.BuiltinFrontFacing;
readonly fragDepth: d.BuiltinFragDepth;
readonly primitiveIndex: d.BuiltinPrimitiveIndex;
readonly sampleIndex: d.BuiltinSampleIndex;
... 11 more ...;
readonly numSubgroups: d.BuiltinNumSubgroups;
}
export builtin
builtin
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
} })(({
gid: d.v3u
gid
,
}) => {
'use gpu';
if (
gid: d.v3u
gid
.
v3u.x: number
x
>= 1024) return;
const drift: TgpuMutable<d.WgslArray<d.Vec2f>>
drift
.
TgpuMutable<WgslArray<Vec2f>>.$: d.v2f[]
$
[
gid: d.v3u
gid
.
v3u.x: number
x
] +=
weather: {
state: TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>;
windAt: () => d.v2f;
inject: () => Inject;
}
weather
.
windAt: () => d.v2f
windAt
();
});
return
root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
weather: {
state: TgpuUniform<d.WgslStruct<{
wind: d.Vec2f;
}>>;
windAt: () => d.v2f;
inject: () => Inject;
}
weather
.
inject: () => Inject
inject
()).
WithBinding.createComputePipeline<{
gid: d.BuiltinGlobalInvocationId;
}>(descriptor: TgpuComputePipeline.Descriptor<{
gid: d.BuiltinGlobalInvocationId;
}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
compute
:
const fall: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
fall
});
}
  • Chain fills for a pipeline that reads several modules: root.pipe(enemies.inject()).pipe(weather.inject()).
  • An accessor no shader reads adds no binding, so piping more fills than a pipeline uses costs nothing.
  • A factory takes each module it reads as a named argument, never a list of modules.

Where an accessor is made decides what a pipeline can read through it:

Kind Made Two in one pipeline In the kit
Per instance in create both are read particles’ emit, the grid’s walk, events’ add, live lists’ slot, text’s setLabel, the picker’s found
Definition beside a definition the later pipe wins a table definition’s rows, a machine’s rows and events, the mesh pool’s vertexAt, the environment’s cube, the sun
Hook at module scope one, chosen on purpose cameras.current, the lighting’s reflect, ambient, fog and sunShadow, ao.at, dof.circle, video.sample
  • A per-instance accessor, like the weather’s, reads one instance’s resources, so one kernel can emit into two particle systems or walk two grids.
  • A definition accessor exists before any instance, so a shader written against Enemy.position(i) works for whichever table of enemies it is piped with.
  • A hook is a piece each pipeline chooses, so two descriptions of passes can draw the same drawings through two cameras, such as a scene and a reflection.

The figure resolves a pipeline piped two instances of each kind, in either order, one or none, and shows whose resources it binds.

Choose which instances fill the pipeline's root. The diagram shows which resources it binds after TypeGPU resolves it on a recording root.

Each module’s card lists what it injects and of which kind.

A module that takes a 'use gpu' function, such as particles’ update or bloom’s bright, builds its pipelines without knowing what the function reads. Pipe the fills into the function itself:

const warm = tgpu.fn(glowOf).pipe(weather.inject()); // reads the weather
const glow = bloom.create(root, { source, bright: warm });

Particles’ update, born, died and place, a trail’s width and shade, and the lighting’s shading hooks all take functions this way. A callback can read cameras.current.$ without a fill, because the description of passes fills the camera.

A fill binds a fixed resource, so replacing the resource would need a new pipeline. A resource that is replaced, such as a texture that follows the screen size or each new camera frame, goes in a bindings part instead. Your program shares every part into one layout before creating the pipelines that read them, and sets that layout’s group on each pass.

  • Ambient occlusion and depth of field keep their textures in parts, because they follow the depth’s size.
  • A live video binds each camera frame through its part.
  • passes.create(…, { shared }) sets the shared group on every pass the description opens.

A pipeline that reads an accessor nothing filled fails at first use or in initAsync, so warm-up catches it while loading. The kit’s accessors default to unfilled, which names the inject() to add, as in Nothing filled `cameras.current` . Yours can do the same:

const weather = tgpu.accessor(
WeatherState,
unfilled('the weather', 'weather', 'a `createWeather`'),
);

A pipeline that reads a part nobody shared throws as it builds and names the call to add.

Ambient occlusion and lighting use every way of connecting. The occlusion reaches the lighting through a callback that carries its fill, both read the camera through a hook, and the occlusion’s texture sits in a shared part because it is replaced when the depth resizes:

import {
import ao
ao
,
import bindings
bindings
,
import lighting
lighting
,
import passes
passes
,
import shading
shading
} from '@typegpu/kit';
const
const occlusion: ao.Ao
occlusion
=
import ao
ao
.
function create(root: TgpuRoot, view: cameras.CameraBase, options: ao.Options): ao.Ao

Creates ground-truth ambient occlusion (GTAO) from a depth texture drawn before shading. As Jimenez and others describe it in 2016, a trace walks slices directions per pixel with 3 steps each way and XeGTAO's falloff, and a denoise blurs 3 × 3 pixels of the same surface. Shading reads the result through ao.at, usually in an ambient hook: (material, point) => shading.hemisphere(material, point) * ao.at(point.position), piped with ao.inject().

@injects ― hook the texture and region ao.at reads, from the part

@binds ― 0 storage, 1 uniform: ao.at, the region and frame, beside the texture

@allocates ― on resize: two textures for a new depth size and the bind groups of its passes; so does a new depth or normal texture

@owns ― targets, 2 or 3 buffers, 2 pipelines, 2 bind groups: the raw and the denoised occlusion; the settings, the region and frame, and with temporal the frame indices; the trace and the denoise, with a group each

@records ― record after the pass that draws depth, before the passes that shade; with temporal, a copy advances the frame.

@param ― view the camera that drew depth, which shading's cameras.current must match

@param ― options.depth the depth drawn before shading, read at every record

@param ― options.normal normals from ao.normal drawn with depth, read at every record

@param ― options.region the part of the depth that holds the picture, read at every record

@returns ― occlusion you record every frame and destroy

create
(
const root: TgpuRoot
root
,
const view: cameras.OrbitCamera
view
, {
Options.depth: () => targets.Depth

Returns a sampled depth texture drawn before shading, such as a half-resolution prepass. record reads it on every call. Its size sets the size of the occlusion.

depth
: () =>
const early: targets.Targets<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>
early
.
Targets<{ readonly depth: { readonly format: "depth32float"; readonly scale: 0.5; readonly sampled: true; }; }>.textures: {
readonly depth: targets.Texture<{
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
depth: targets.Texture<{
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
}>
depth
});
const
const shared: bindings.Shared
shared
=
import bindings
bindings
.
function share(root: TgpuRoot, parts: Record<string, bindings.Part>): bindings.Shared

Combines parts from modules or your own code in one layout. Call it before creating pipelines. Set its group on each pass with pass.setBindGroup(shared.layout, shared.group()), or give it to passes.create as shared, which sets it in every pass it begins. Every pipeline in that pass that reads a part then uses the shared group.

@param ― parts parts from any modules and your own, by key

@returns ― a layout you set on each pass, with the group for the parts' resources

share
(
const root: TgpuRoot
root
, {
ao: bindings.Part<bindings.Entries>
ao
:
const occlusion: ao.Ao
occlusion
.
Ao.part: bindings.Part<bindings.Entries>

Provides the texture and its region for ao.at. Share it before creating the pipelines that shade, bindings.share(root, { ao: occlusion.part }), so a new depth size replaces the texture without replacing pipelines. A pipeline that reads ao.at without the share throws as it builds.

part
}); // a part
const
const sky: (material: shading.Material, point: shading.Point) => d.v3f
sky
= (
material: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}
material
:
import shading
shading
.
type Material = {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}

This schema describes a surface with the glTF metallic-roughness model.

Material
,
point: {
position: d.v3f;
normal: d.v3f;
view: d.v3f;
uv: d.v2f;
uv1: d.v2f;
}
point
:
import shading
shading
.
type Point = {
position: d.v3f;
normal: d.v3f;
view: d.v3f;
uv: d.v2f;
uv1: d.v2f;
}

This schema describes a point being shaded in world space.

Point
) => {
'use gpu';
return
import shading
shading
.
const hemisphere: (material: shading.Material, point: shading.Point) => d.v3f

Computes ambient light by blending sky and ground colours according to the surface normal. Reflections sample the sky in the reflection direction. Roughness blends them towards diffuse light, and skyReflection scales them. The material's occlusion darkens both.

hemisphere
(
material: {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}
material
,
point: {
position: d.v3f;
normal: d.v3f;
view: d.v3f;
uv: d.v2f;
uv1: d.v2f;
}
point
) *
import ao
ao
.
const at: (position: d.v3f) => number

Returns the visibility of sky light at a world position, from 0 in a closed crease to 1 in the open. It projects the position through cameras.current and blends the 4 nearest texels whose eye distance matches. Where none matches, such as on a translucent surface in front of the opaque one, it returns 1. Call it from shading in a pipeline piped with ao.inject(), such as an ambient hook.

at
(
point: {
position: d.v3f;
normal: d.v3f;
view: d.v3f;
uv: d.v2f;
uv1: d.v2f;
}
point
.
position: d.v3f
position
);
};
const
const light: lighting.Lighting
light
=
import lighting
lighting
.
function create<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
}>(root: TgpuRoot, options?: ({
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
} & lighting.Options & {
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
}) | undefined): lighting.Lighting

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
, {
shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
} & lighting.Shading & {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
}

Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog, filtered and reaching.

shading
: {
ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f> & shading.AmbientHook

Computes ambient light. Default shading.hemisphere.

ambient
:
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
fn: <(material: shading.Material, point: shading.Point) => d.v3f>(inner: (material: shading.Material, point: shading.Point) => d.v3f) => TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f> (+2 overloads)
fn
(
const sky: (material: shading.Material, point: shading.Point) => d.v3f
sky
).
TgpuGenericFn<(material: shading.Material, point: shading.Point) => v3f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>

Applies a configuration transform to the slots and accessors this function reads, as root.pipe does to a pipeline's.

pipe
(
const occlusion: ao.Ao
occlusion
.
Ao.inject(): Inject

Fills ao.at for pipelines created from the configuration it is piped into.

inject
()) }, // a callback
});
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
}, {
Options.shared?: bindings.Shared | undefined

Binds a layout from bindings.share in every pass begin and record start, and fills its parts in every pipeline the description builds. Call bindings.share before passes.create and before any other pipeline that reads its parts. Default none.

shared
})
.
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
()) // a hook
.
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 light: lighting.Lighting
light
.
Lighting.inject(): Inject

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

inject
()); // definitions: the sun, the lights, the shadow

Ambient occlusion runs this scene in full, and Write a module builds a module of your own.