Skip to content

A finished look for model scenes

Sun shadows, ambient occlusion, TAA, bloom and a grade for a scene of many instanced glTF models, in a file another program can copy.

Drag to look round, and turn each stage off to see what it adds. Shadows ground the houses, occlusion darkens the sky light under the trees and eaves, TAA smooths the roof edges in the loupe, and bloom spreads the lanterns' light.

A lighting and a set of instances draw glTF models lit by the sun and the sky. On their own they look flat. Every surface gets the whole sky, nothing seems to rest on the ground, and edges step and shimmer as the view turns. Four stages finish the picture:

  • The sun’s shadows, in cascades fitted to the view, set each model on the ground.
  • Ambient occlusion darkens the sky light where nearby surfaces hide the sky: under eaves, between trees and where a wall meets the grass.
  • TAA smooths edges and thin parts by taking a sample at a different offset each frame.
  • Bloom spreads light brighter than white, and the final pass tone maps and grades the picture.

The program splits into three files. look.ts holds the lighting and every pass from the prepass to the canvas, so another program can copy it whole. The package ships it and props.ts whole in docs/recipes/lit-models/:

  • scene.ts createScene with the camera, the props, the look and the frame
  • look.ts createLook with the lighting, the passes and the post-processing
  • props.ts createProps with the mesh pool and the instances

Put every model in one mesh pool and every piece in one set of instances. The set draws each mesh once, however many pieces use it, so a thousand houses cost what one does on the CPU:

props.ts
import {
import instances
instances
,
import meshPool
meshPool
,
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
} from '@typegpu/kit';
import type {
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
} from 'typegpu';
/** A piece of the scene: a model by name, and its row's values. */
export interface
interface Placement

A piece of the scene: a model by name, and its row's values.

Placement
{
readonly
Placement.model: string
model
: string;
/** Its position, rotation, scale and any material field, such as `emission`. */
readonly
Placement.values: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}

Its position, rotation, scale and any material field, such as emission.

values
:
import instances
instances
.
type Values<K extends instances.AnyKind = Plain> = {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
} & ([keyof OwnOf<K>] extends [...] ? unknown : Partial<...>)

These values set the transform, material and custom fields in add and write. All are optional. Vectors also take any array of numbers, such as a Float32Array from a math library. You can spread in a value from materials. Omitted fields keep their values. On creation, the transform is the identity and material factors come from the mesh, with white chalk for shapes. Mesh textures multiply these factors.

Values
;
}
/** One pool of every model and shape, and one set of instances placing them. */
export function
function createProps(root: TgpuRoot, models: Readonly<Record<string, meshPool.ModelData | meshPool.Shape>>, placements: readonly Placement[]): {
renderer: instances.Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}

One pool of every model and shape, and one set of instances placing them.

createProps
(
root: TgpuRoot
root
:
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
,
models: Readonly<Record<string, meshPool.ModelData<{}> | meshPool.Shape>>
models
:
type Readonly<T> = { readonly [P in keyof T]: T[P]; }

Make all properties in T readonly

Readonly
<
type Record<K extends keyof any, T> = { [P in K]: T; }

Construct a type with a set of properties K of type T

Record
<string,
import meshPool
meshPool
.
interface ModelData<E extends meshPool.Extras = {}>

This type describes a model returned by gltf.read, with one mesh per material and its images.

ModelData
|
import meshPool
meshPool
.
type Shape = Geometry<WgslStruct<{
position: Vec3f;
normal: Vec3f;
uv: Vec2f;
}>>

This type accepts indexed or unindexed geometry from @typegpu/kit/geometry with the standard vertex struct.

Shape
>>,
placements: readonly Placement[]
placements
: readonly
interface Placement

A piece of the scene: a model by name, and its row's values.

Placement
[],
) {
const
const own: Owner
own
=
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
();
const
const pool: meshPool.MeshPool<{}>
pool
=
const own: Owner
<meshPool.MeshPool<{}>>(item: meshPool.MeshPool<{}>) => meshPool.MeshPool<{}>

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import meshPool
meshPool
.
function create<{}>(root: TgpuRoot, options: meshPool.Options<{}>): meshPool.MeshPool<{}>

Creates shared vertex storage and an index buffer, with bounds and textures per mesh. One pipeline can draw any mesh. Each vertex uses 12 bytes for position and 8 for normal and UV coordinates, and 8 for a second uv set with uvs1. Skinned models add 8 bytes for joints and weights. Extras add the size of their struct per vertex.

@injects ― definition the vertices vertexAt, positionAt and uv1At read, the joints and weights influencesAt reads, and the boxes boundsOf reads

@injects ― per-instance the extras pool.extra reads

@binds ― 1 storage: positionAt, the positions

@binds ― 2 storage: vertexAt, the positions and the normals and uvs

@binds ― 1 storage: uv1At, the second uvs, in a pool made with uvs1

@binds ― 1 storage: influencesAt, the joints and weights

@binds ― 1 storage: boundsOf, every mesh's box

@binds ― 1 storage: pool.extra, every vertex's extras

@binds ― 1 uniform: pool.texturesOf(index) for materials, beside its textures and sampler

@submits ― setup a shape added without an encoder bakes in a compute pass of its own, submitted at once, and a model's images generate their mips as they decode

@owns ― 5 to 7 buffers, a uniform per way of reading textures, a pipeline per shape, textures, samplers, bind groups, what images makes: positions, normals and uvs, indices and boxes, second uvs with uvs1, joints and weights once a skinned model comes, and the extras when extra names any; each image decoded once per format it is read in, and a group per set of a mesh's textures

@returns ― a pool you add meshes and models to and destroy

create
(
root: TgpuRoot
root
,
import meshPool
meshPool
.
function roomFor(items: readonly (meshPool.ModelData | meshPool.MeshData | meshPool.Shape | meshPool.Room)[]): meshPool.Sized

Sizes a pool to hold exactly items: models such as impostors.CARD, their meshes, shapes, and counts. Spread it into the options: meshPool.create(root, meshPool.roomFor([...])). It keeps a second uv set when one of the meshes carries uvs1.

@param ― items what the pool will hold

@returns ― the capacity, vertices and indices they take, and uvs1: true when a mesh needs it

roomFor
(
var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.values<meshPool.ModelData<{}> | meshPool.Shape>(o: ArrayLike<meshPool.ModelData<{}> | meshPool.Shape> | {
[s: string]: meshPool.ModelData<{}> | meshPool.Shape;
}): (meshPool.ModelData<{}> | meshPool.Shape)[] (+1 overload)

Returns an array of values of the enumerable own properties of an object

@param ― o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.

values
(
models: Readonly<Record<string, meshPool.ModelData<{}> | meshPool.Shape>>
models
))));
// A shape, such as `meshes.cylinder()`, is one mesh with nothing to decode
const
const add: (item: meshPool.ModelData | meshPool.Shape) => meshPool.Model
add
= (
item: meshPool.ModelData<{}> | meshPool.Shape
item
:
import meshPool
meshPool
.
interface ModelData<E extends meshPool.Extras = {}>

This type describes a model returned by gltf.read, with one mesh per material and its images.

ModelData
|
import meshPool
meshPool
.
type Shape = Geometry<WgslStruct<{
position: Vec3f;
normal: Vec3f;
uv: Vec2f;
}>>

This type accepts indexed or unindexed geometry from @typegpu/kit/geometry with the standard vertex struct.

Shape
) =>
'meshes' in
item: meshPool.ModelData<{}> | meshPool.Shape
item
?
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

Uploads each mesh into the next free ranges and decodes images in the background. Each image decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer for the full vertex capacity. Throws if the pool is full, the model has no meshes, its arrays disagree or encoded images have no decoder.

add
(
item: meshPool.ModelData<{}>
item
) : {
meshes: meshPool.Mesh[]
meshes
: [
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(shape: meshPool.Shape, encoder?: TgpuCommandEncoder): meshPool.Mesh (+1 overload)

Bakes a shape into the next free ranges in a compute pass. With encoder, it records there; otherwise, it submits at once. Each shape needs its own pipeline, so add shapes while loading. Throws when the pool is full.

add
(
item: meshPool.Shape
item
)],
ready: Promise<void>
ready
:
var Promise: PromiseConstructor

Represents the completion of an asynchronous operation

Promise
.
PromiseConstructor.resolve(): Promise<void> (+2 overloads)

Creates a new resolved promise.

@returns ― A resolved promise.

resolve
() };
const
const added: Map<string, meshPool.Model>
added
= new
var Map: MapConstructor
new <string, meshPool.Model>(iterable?: Iterable<readonly [string, meshPool.Model]> | null | undefined) => Map<string, meshPool.Model> (+3 overloads)
Map
(
var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.entries<meshPool.ModelData<{}> | meshPool.Shape>(o: {
[s: string]: meshPool.ModelData<{}> | meshPool.Shape;
} | ArrayLike<meshPool.ModelData<{}> | meshPool.Shape>): [string, meshPool.ModelData<{}> | meshPool.Shape][] (+1 overload)

Returns an array of key/values of the enumerable own properties of an object

@param ― o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.

entries
(
models: Readonly<Record<string, meshPool.ModelData<{}> | meshPool.Shape>>
models
).
Array<[string, ModelData<{}> | Shape]>.map<[string, meshPool.Model]>(callbackfn: (value: [string, meshPool.ModelData<{}> | meshPool.Shape], index: number, array: [string, meshPool.ModelData<{}> | meshPool.Shape][]) => [string, meshPool.Model], thisArg?: any): [string, meshPool.Model][]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
(([
name: string
name
,
item: meshPool.ModelData<{}> | meshPool.Shape
item
]) => [
name: string
name
,
const add: (item: meshPool.ModelData | meshPool.Shape) => meshPool.Model
add
(
item: meshPool.ModelData<{}> | meshPool.Shape
item
)]));
const
const modelOf: (name: string) => meshPool.Model
modelOf
= (
name: string
name
: string) =>
const added: Map<string, meshPool.Model>
added
.
Map<string, Model>.get(key: string): meshPool.Model | undefined

Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.

@returns ― Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.

get
(
name: string
name
)!;
// A model takes a row for each of its meshes
const
const rows: number
rows
=
placements: readonly Placement[]
placements
.
ReadonlyArray<Placement>.reduce<number>(callbackfn: (previousValue: number, currentValue: Placement, currentIndex: number, array: readonly Placement[]) => number, initialValue: number): number (+2 overloads)

Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.

@param ― callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.

@param ― initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.

reduce
((
sum: number
sum
, {
model: string
model
}) =>
sum: number
sum
+
const modelOf: (name: string) => meshPool.Model
modelOf
(
model: string
model
).
Model.meshes: readonly meshPool.Mesh[]
meshes
.
ReadonlyArray<Mesh>.length: number

Gets the length of the array. This is a number one higher than the highest element defined in an array.

length
, 0);
const
const props: instances.Instances<Plain>
props
=
const own: Owner
<instances.Instances<Plain>>(item: instances.Instances<Plain>) => instances.Instances<Plain>

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import instances
instances
.
function create<Plain>(root: TgpuRoot, pool: meshPool.MeshPool, options: instances.Options<Plain>): instances.Instances<Plain> (+1 overload)

Creates a set of mesh instances with transforms, materials and optional custom fields. Each instance uses a row, and renderers draw one list of live rows per mesh.

@param ― pool the mesh pool whose meshes it draws

@param ― options.kind a kind from instances.define

@returns ― a set of instances you flush every frame and destroy

create
(
root: TgpuRoot
root
,
const pool: meshPool.MeshPool<{}>
pool
, {
Options<K extends AnyKind = Plain>.capacity: number

Limits the number of rows. A mesh instance uses one row; a model instance uses one row per mesh.

capacity
:
const rows: number
rows
}));
for (const {
const model: string
model
,
const values: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}

Its position, rotation, scale and any material field, such as emission.

values
} of
placements: readonly Placement[]
placements
)
const props: instances.Instances<Plain>
props
.
Instances<Plain>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
} | ((mesh: meshPool.Mesh, index: number) => {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}) | undefined): IdOf<...>

Adds an instance of a mesh, model or group of meshes from the pool. Each mesh uses a row, and all rows move and change together under one id. values may be a function of each mesh, to give a model's meshes values of their own. Throws if a mesh belongs to another pool, the group is empty, a posed kind receives a mesh without joints or the set is full.

add
(
const modelOf: (name: string) => meshPool.Model
modelOf
(
const model: string
model
),
const values: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}

Its position, rotation, scale and any material field, such as emission.

values
);
return {
renderer: instances.Renderer
renderer
:
const props: instances.Instances<Plain>
props
.
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
(),
/** Settles once every model's textures are on the GPU. */
ready: Promise<void[]>

Settles once every model's textures are on the GPU.

ready
:
var Promise: PromiseConstructor

Represents the completion of an asynchronous operation

Promise
.
PromiseConstructor.all<Promise<void>[]>(values: Promise<void>[]): Promise<void[]> (+1 overload)

Creates a Promise that is resolved with an array of results when all of the provided Promises resolve, or rejected when any Promise is rejected.

@param ― values An array of Promises.

@returns ― A new Promise.

all
([...
const added: Map<string, meshPool.Model>
added
.
Map<string, Model>.values(): MapIterator<meshPool.Model>

Returns an iterable of values in the map

values
()].
Array<Model>.map<Promise<void>>(callbackfn: (value: meshPool.Model, index: number, array: meshPool.Model[]) => Promise<void>, thisArg?: any): Promise<void>[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
((
model: meshPool.Model
model
) =>
model: meshPool.Model
model
.
Model.ready: Promise<void>

Resolves when all textures have decoded and uploaded, at once for a model whose materials name none. Until then, meshes draw without textures. Rejects if image decoding fails or the pool is destroyed before upload. A destroyed pool uploads nothing.

ready
)),
flush: () => void
flush
: () =>
const props: instances.Instances<Plain>
props
.
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
(),
destroy: () => void
destroy
:
const own: Owner
own
.
Owner.destroy(): void

Frees everything it took, the last taken first, once; later calls do nothing. When one throws, frees the rest and then rethrows. Also [Symbol.dispose].

destroy
,
};
}
  • meshPool.roomFor sizes the pool for every model’s vertices and indices. A shape such as meshes.cylinder() goes in models beside the glTF models, as the figure’s lantern posts do.
  • A row starts with its mesh’s material from the file. Values you give replace it, so a lantern’s glass takes an emission above 1 and glows through the bloom.
  • Pieces with shaders of their own, such as turning sails or lit windows, take a kind in a second set from the same pool. The look draws its renderer the same way.

Each placement carries the values its row starts with:

/** A turn of `yaw` radians round +y, as a quaternion. */
const
const turned: (yaw: number) => readonly [0, number, 0, number]

A turn of yaw radians round +y, as a quaternion.

turned
= (
yaw: number
yaw
: number) => [0,
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.sin(x: number): number

Returns the sine of a number.

@param ― x A numeric expression that contains an angle measured in radians.

sin
(
yaw: number
yaw
/ 2), 0,
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.cos(x: number): number

Returns the cosine of a number.

@param ― x A numeric expression that contains an angle measured in radians.

cos
(
yaw: number
yaw
/ 2)] as
type const = readonly [0, number, 0, number]
const
;
const
const placements: Placement[]
placements
:
interface Placement
Placement
[] = [
{
Placement.model: string
model
: 'church',
Placement.values: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}
values
: {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [0, 0, 0] } },
{
Placement.model: string
model
: 'house',
Placement.values: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}
values
: {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [2, 0, 0],
rotation?: ArrayLike<number> | InferInput<Vec4f> | undefined

Sets rotation as a unit quaternion [x, y, z, w].

rotation
:
const turned: (yaw: number) => readonly [0, number, 0, number]

A turn of yaw radians round +y, as a quaternion.

turned
(
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.PI: number

Pi. This is the ratio of the circumference of a circle to its diameter.

PI
/ 3) } },
{
Placement.model: string
model
: 'house',
Placement.values: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}
values
: {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [-2, 0, 0],
rotation?: ArrayLike<number> | InferInput<Vec4f> | undefined

Sets rotation as a unit quaternion [x, y, z, w].

rotation
:
const turned: (yaw: number) => readonly [0, number, 0, number]

A turn of yaw radians round +y, as a quaternion.

turned
(
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.PI: number

Pi. This is the ratio of the circumference of a circle to its diameter.

PI
),
scale?: number | ArrayLike<number> | InferInput<Vec3f> | undefined

Sets one scale factor for all axes or a separate factor per axis.

scale
: 0.9 } },
{
Placement.model: string
model
: 'tree',
Placement.values: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}
values
: {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [1, 0, 1.73],
scale?: number | ArrayLike<number> | InferInput<Vec3f> | undefined

Sets one scale factor for all axes or a separate factor per axis.

scale
: 1.2 } },
{
Placement.model: string
model
: 'lantern',
Placement.values: {
readonly position?: FieldInput<Vec3f>;
readonly rotation?: FieldInput<Vec4f>;
readonly scale?: number | FieldInput<Vec3f>;
readonly color?: FieldInput<Vec3f>;
readonly roughness?: number;
readonly metallic?: number;
readonly emission?: FieldInput<Vec3f>;
readonly opacity?: number;
}
values
: {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [1, 0, 0.58],
emission?: ArrayLike<number> | InferInput<Vec3f> | undefined
emission
: [6, 3.6, 1.8] } },
];

Ambient occlusion reads a depth texture drawn before the scene shades. The look draws every solid prop into a half-resolution prepass, traces the occlusion from it and dims the sky light by it in the lighting’s ambient hook. The sun’s shadow fits two cascades to the view:

look.ts
/** Sky light, dimmed where nearby surfaces hide the sky. */
const
const occludedSky: (material: shading.Material, point: shading.Point) => d.v3f

Sky light, dimmed where nearby surfaces hide the sky.

occludedSky
= (
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
);
};
/** The lighting and every pass from the prepass to the canvas, seen through `view`. */
export function
function createLook(root: TgpuRoot, surface: Surface, view: cameras.OrbitCamera, options: LookOptions): void

The lighting and every pass from the prepass to the canvas, seen through view.

createLook
(
root: TgpuRoot
root
:
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
,
surface: targets.Surface
surface
:
(alias) interface Surface
import Surface

Provides a destination for frames, such as a canvas from web.surface or native.surface, or a texture from offscreenSurface in a test. Pass the frame to view for code that works with every destination.

Surface
,
view: cameras.OrbitCamera
view
:
import cameras
cameras
.
interface OrbitCamera
OrbitCamera
,
options: LookOptions
options
:
interface LookOptions
LookOptions
,
) {
const {
const stages: Stages

The stages that run, read every frame. Call apply after changing taa or bloom.

stages
} =
options: LookOptions
options
;
const
const own: Owner
own
=
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
();
// Half-resolution depth of the solid props, drawn first for the occlusion to read
const
const early: targets.Targets<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>
early
=
const own: Owner
<targets.Targets<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>>(item: targets.Targets<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>) => targets.Targets<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import targets
targets
.
function create<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>

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
(
root: TgpuRoot
root
,
surface: targets.Surface
surface
, {
depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
}
depth
: {
format: "depth32float"
format
: 'depth32float',
scale: 0.5
scale
: 0.5,
sampled: true
sampled
: true },
}),
);
const
const prepass: passes.Passes<{
readonly depth: typeof passes.depth;
}>
prepass
=
import passes
passes
.
function create<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}, {
readonly depth: typeof passes.depth;
}>(root: TgpuRoot, screen: targets.Targets<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>, writes: {
readonly depth: typeof passes.depth;
}, options?: passes.TargetOptions<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}> | undefined): passes.Passes<{
readonly depth: typeof passes.depth;
}> (+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
(
root: TgpuRoot
root
,
const early: targets.Targets<{
readonly depth: {
readonly format: "depth32float";
readonly scale: 0.5;
readonly sampled: true;
};
}>
early
, {
depth: typeof passes.depth
depth
:
import passes
passes
.
const depth: typeof passes.depth

Marks the depth target.

depth
}).
Passes<{ readonly depth: typeof depth; }>.pipe(fill: Inject): passes.Passes<{
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
(
view: cameras.OrbitCamera
view
.
CameraBase.inject(): Inject

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

inject
());
const
const occlusion: ao.Ao
occlusion
=
const own: Owner
<ao.Ao>(item: ao.Ao) => ao.Ao

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
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
(
root: TgpuRoot
root
,
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
,
Options.temporal?: boolean | undefined

Turns the sample directions each frame, for TAA to average. Default false, the same directions every frame.

temporal
: true, // turns its directions each frame for TAA to average
Options.settings?: Partial<d.InferInput<d.WgslStruct<{
radius: d.F32;
power: d.F32;
}>>> | undefined

Sets initial values over defaults. Default defaults, with radius 0.5 and power 2.2.

settings
: {
radius: number
radius
:
options: LookOptions
options
.
LookOptions.crease: number

The size of the gaps the occlusion darkens, in world units.

crease
,
power: number
power
: 3 },
Options.enabled?: (() => boolean) | undefined

Controls whether record traces in the current frame. When false, the first record clears output to full visibility and later ones record nothing. Skip the prepass yourself. The textures and pipelines stay allocated. Default always.

enabled
: () =>
const stages: Stages

The stages that run, read every frame. Call apply after changing taa or bloom.

stages
.
Stages.occlusion: boolean
occlusion
,
}),
);
const
const light: lighting.LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>
light
=
const own: Owner
<lighting.LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>>(item: lighting.LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>) => lighting.LightingOf<...>

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import lighting
lighting
.
function create<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>(root: TgpuRoot, options?: ({
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
} & lighting.Options & {
...;
}) | undefined): lighting.LightingOf<...>

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
(
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 occludedSky: (material: shading.Material, point: shading.Point) => d.v3f

Sky light, dimmed where nearby surfaces hide the sky.

occludedSky
).
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
()) },
shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
} & FittedShadowOptions & {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
}

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

shadow
: {
eye: cameras.OrbitCamera & cameras.CpuView

Fits the map to this camera's view, in cascades layers that reach far from the eye. Each record fits them again, so the map follows the camera without follow. Default none, a fixed map.

eye
:
view: cameras.OrbitCamera
view
,
cascades: 2

Splits the view into 1 to 4 cascades, each a layer of the map drawn in a pass of its own. Nearer cascades cover less ground, so their shadows are sharper. Needs an eye. Default 1.

cascades
: 2,
far: number

Sets how far from the eye the cascades reach, in world units. Change it with setFar. Needs an eye. Default 50.

far
:
options: LookOptions
options
.
LookOptions.far: number

How far the sun's shadow reaches, a little past the farthest prop the view shows.

far
,
filter: 9

Sets the number of comparisons per shaded point to 1, 4 or 9. Each comparison filters four texels. Default 4.

filter
: 9,
enabled: (() => boolean) & (() => boolean)

Controls whether record draws the shadow in the current frame. When false, it records no depth pass and shading treats every point as sunlit. The map and pipelines stay allocated. Default always.

enabled
: () =>
const stages: Stages

The stages that run, read every frame. Call apply after changing taa or bloom.

stages
.
Stages.shadows: boolean
shadows
,
},
}),
);
const light: lighting.LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>
light
.
Lighting.settings: TgpuUniform<d.WgslStruct<{
sunDirection: d.Vec3f;
sunIntensity: d.F32;
sunColor: d.Vec3f;
skyIntensity: d.F32;
skyTop: d.Vec3f;
fogDensity: d.F32;
skyBottom: d.Vec3f;
fogFalloff: d.F32;
fogColor: d.Vec3f;
skyReflection: d.F32;
fogStart: d.F32;
}>>

Holds the sun, sky and fog values from defaults and the settings option. Use patch to upload only the fields you supply. The shadow's next record follows a sunDirection written here.

settings
.
TgpuBufferBindingBase<WgslStruct<{ sunDirection: Vec3f; sunIntensity: F32; sunColor: Vec3f; skyIntensity: F32; skyTop: Vec3f; fogDensity: F32; skyBottom: Vec3f; fogFalloff: F32; fogColor: Vec3f; skyReflection: F32; fogStart: F32; }>>.patch(data: {
sunDirection?: d.InferInput<d.Vec3f> | undefined;
sunIntensity?: number | undefined;
sunColor?: d.InferInput<d.Vec3f> | undefined;
skyIntensity?: number | undefined;
skyTop?: d.InferInput<d.Vec3f> | undefined;
fogDensity?: number | undefined;
skyBottom?: d.InferInput<d.Vec3f> | undefined;
fogFalloff?: number | undefined;
fogColor?: d.InferInput<d.Vec3f> | undefined;
skyReflection?: number | undefined;
fogStart?: number | undefined;
} | undefined): void
patch
(
options: LookOptions
options
.
LookOptions.light: Partial<d.InferInput<d.WgslStruct<{
sunDirection: d.Vec3f;
sunIntensity: d.F32;
sunColor: d.Vec3f;
skyIntensity: d.F32;
skyTop: d.Vec3f;
fogDensity: d.F32;
skyBottom: d.Vec3f;
fogFalloff: d.F32;
fogColor: d.Vec3f;
skyReflection: d.F32;
fogStart: d.F32;
}>>>
light
);
  • Set far a little past the farthest prop the view shows. The cascades spread the same texels over far, so a longer reach blurs every shadow. See cascades fitted to the view.
  • Set crease near the size of the gaps the occlusion should darken. The figure uses 0.7 world units for houses about one unit tall.
  • The occlusion dims only the sky light. Sunlit faces keep their brightness, and the sun’s shadow keeps its own edge.
  • For reflections of a sky panorama, call environment.occluded with the same ao.at in place of shading.hemisphere, as shading with an environment shows.

The scene shades into an rgba16float target through the lighting, offset by TAA’s jitter. TAA blends each frame with the last into a smooth picture, which the bloom and the final pass both read. Inside createLook, after the lighting:

look.ts
/** Cools the shadows and warms the lights after the tone curve. */
const
const dusk: (color: d.v3f, _uv: d.v2f) => d.v3f

Cools the shadows and warms the lights after the tone curve.

dusk
= (
color: d.v3f
color
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
,
_uv: d.v2f
_uv
:
import d
d
.
export v2f

Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

v2f
) => {
'use gpu';
const
const light: number
light
=
import std
std
.
function saturate(value: number): number (+1 overload)
export saturate
saturate
(
import tone
tone
.
const luminance: (color: d.v3f) => number

Returns the relative luminance of a linear colour.

luminance
(
color: d.v3f
color
) * 2);
return
color: d.v3f
color
*
import std
std
.
mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
export mix
mix
(
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(0.84, 0.93, 1.14),
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(1.02, 1, 0.96),
const light: number
light
);
};
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
=
const own: Owner
<targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>>(item: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>) => targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>

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
(
root: TgpuRoot
root
,
surface: targets.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true },
depth: {
readonly format: "depth24plus";
readonly sampled: true;
}
depth
: {
format: "depth24plus"
format
: 'depth24plus',
sampled: true
sampled
: true },
}),
);
const
const aa: taa.Taa
aa
=
const own: Owner
<taa.Taa>(item: taa.Taa) => taa.Taa

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import taa
taa
.
function create(root: TgpuRoot, options: taa.Options): taa.Taa

Creates temporal antialiasing from subpixel-jittered frames. The resolve blends each frame with history sampled where motion, or depth and camera movement, place the preceding pixel. Without either, it samples history at the same pixel. It clamps history colour to the local colour spread to reduce trails and weights by brightness to reduce bright-pixel flicker. Record its single pass after scene passes and before the final pass reads output.

@allocates ― on resize: targets for the new size and the bind groups of its passes; so does a new source, motion or depth

@owns ― targets, 4 buffers, a sampler, a pipeline, 2 or 3 bind groups: two histories, each frame written into one from the other, the settings, the region and whether to blend, the jitter and its sequence, a group each for the frames that read either history with the source and motion, and one for depth

@binds ― 1 uniform: jitter, this frame's offset

@records ― record after the passes that draw the source and the motion, before what reads output.

@param ― options.source this frame's picture, drawn through jitter, read at every record

@param ― options.motion how far each pixel's surface moved since last frame, read at every record; without it, pixels move only with the camera

@param ― options.depth the scene's depth, for the motion of the nearest surface around each pixel and for motion from the camera

@returns ― a resolve you record every frame and destroy

create
(
root: TgpuRoot
root
, {
Options.source: (() => finish.FloatTexture) | finish.Rendered

Provides the current linear-colour picture, drawn through jitter: a function returning a texture, such as () => screen.textures.hdr, or a rendered picture. record reads it on every call.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly depth: targets.Texture<{
readonly format: "depth24plus";
readonly sampled: true;
}>;
}

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

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
,
Options.depth?: (() => targets.Depth) | undefined

Returns sampled scene depth without stencil. Motion then follows the nearest surrounding surface to avoid smearing moving edges over the background. record reads it on every call. Unused without motion or camera. Default none, which uses motion at each pixel.

depth
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly depth: targets.Texture<{
readonly format: "depth24plus";
readonly sampled: true;
}>;
}

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

textures
.
depth: targets.Texture<{
readonly format: "depth24plus";
readonly sampled: true;
}>
depth
,
Options.camera?: cameras.CameraBase | undefined

Supplies the scene camera with its preceding view, such as cameras.orbit. With depth, pixels without renderer-written motion reconstruct motion from depth and camera movement. Without both, these pixels stay in place. Default none.

camera
:
view: cameras.OrbitCamera
view
,
}),
);
// Before the pipelines that read `ao.at`, so a resize keeps them
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
(
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
});
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 sampled: true;
};
}, {
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";
readonly sampled: true;
};
}>, 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";
readonly sampled: true;
};
}> | 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
(
root: TgpuRoot
root
,
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
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.jitter?: Position | undefined

Moves clip positions for rasterisation in a 'use gpu' function. Outputs read the original vertex positions. Use for a sub-pixel offset in temporal antialiasing. Default none.

jitter
:
const aa: taa.Taa
aa
.
Taa.jitter: (position: d.v4f) => d.v4f

Offsets vertices by a different fraction of a pixel each frame so frames sample the whole pixel together. Supply this as jitter in passes.create.

jitter
,
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
(
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 shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>
light
.
Lighting.inject(): Inject

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

inject
());
const
const glow: bloom.Bloom
glow
=
const own: Owner
<bloom.Bloom>(item: bloom.Bloom) => bloom.Bloom

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import bloom
bloom
.
function create(root: TgpuRoot, options: bloom.Options): bloom.Bloom

Creates bloom from a chain of half-size textures. Each level downsamples with 13 taps, then a tent filter adds it to the next larger level. The result is mip 0 of output, at half the source size. With region, passes draw and sample only that region; changing the region allocates nothing.

@allocates ― on resize: a new chain and the bind groups of its passes; so does a new source

@owns ― targets, a sampler, 3 pipelines, 2 bind groups and a buffer per level: the chain of glow levels, the settings, a group for each pass, and the region each pass reads

@records ― record after whatever wrote the source; then output holds the glow.

@param ― options.source the HDR picture, read at every record

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

@returns ― a bloom you record every frame and destroy

create
(
root: TgpuRoot
root
, {
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the HDR picture: a rendered picture, such as aa, or a function returning a texture, such as () => screen.textures.hdr. record reads it on every call, so the texture can be rebuilt.

source
:
const aa: taa.Taa
aa
}));
const
const final: finish.Finish
final
=
const own: Owner
<finish.Finish>(item: finish.Finish) => finish.Finish

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

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

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
surface: targets.Surface
surface
.
Surface.format: GPUTextureFormat
format
,
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
:
const aa: taa.Taa
aa
,
Options.glow?: finish.Rendered | undefined

Adds a bloom, or a glow of your own such as an emission target, before tone mapping. draw reads its output and region on every call. Default none.

glow
,
Options.grade?: ((color: d.v3f, uv: d.v2f) => d.v3f) | undefined

Grades or tints the colour after tone mapping and before the vignette, encoding and dither. It receives the colour and output UV coordinates from 0 to 1. Read other pixels with finish.picture(uv) and glow with finish.glow(uv). Default none.

grade
:
const dusk: (color: d.v3f, _uv: d.v2f) => d.v3f

Cools the shadows and warms the lights after the tone curve.

dusk
}));
  • TAA reprojects last frame’s picture by the camera’s motion and the scene’s depth, so its depth target is sampled. Props that hold still need nothing more.
  • Props that move need motion of their own. Give the scene a motion: taa.motion target, TAA its texture as motion, and the moving set history: true.
  • The bloom spreads only light above its threshold, 1 by default. A lit wall stays below it, and a lantern’s glass at emission 6 glows.
  • grade runs inside the final pass, after the tone curve, on colours in display range. See grading.

The look adds each renderer to the three passes that draw it, and records the frame in the order each stage reads the one before. Inside createLook, after the final pass:

look.ts
const
const shapes: passes.Drawn[]
shapes
:
import passes
passes
.
interface Drawn

Provides a drawing built for one description.

Drawn
[] = [];
const
const casters: passes.Drawn[]
casters
:
import passes
passes
.
interface Drawn

Provides a drawing built for one description.

Drawn
[] = [];
const
const drawn: passes.Drawn[]
drawn
:
import passes
passes
.
interface Drawn

Provides a drawing built for one description.

Drawn
[] = [];
return {
light: lighting.LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>
light
,
/** Draws `renderer` in the prepass, the sun's shadow and the scene. */
function add(renderer: instances.Renderer): void

Draws renderer in the prepass, the sun's shadow and the scene.

add
(
renderer: instances.Renderer
renderer
:
import instances
instances
.
interface Renderer

This renderer draws a set into pass descriptions. Add it to a colour description with scene.add(renderer) or a depth-only description with light.shadow.add(renderer). Each description builds pipelines for its camera and, with cull, keeps its own visibility list.

A renderer without culling draws live instances with one direct draw per occupied mesh. Culling uses one indirect draw per mesh in use. Render bundles also draw indirectly so replay reads live instance counts. A bundle draws the meshes that had instances when it was recorded; record it again after a mesh's first instance, and once a model's ready resolves. Meshes with the same back-face, alpha-mask and texture settings share pipelines. Indirect draws without indirect-first-instance use a pipeline of their own that reads the mesh from a bind group, or one per mesh where the pipeline has no group left.

Renderer
) {
const shapes: passes.Drawn[]
shapes
.
Array<Drawn>.push(...items: passes.Drawn[]): number

Appends new elements to the end of an array, and returns the new length of the array.

@param ― items New elements to add to the array.

push
(
const own: Owner
<passes.Drawn>(item: passes.Drawn) => passes.Drawn

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
const prepass: passes.Passes<{
readonly depth: typeof passes.depth;
}>
prepass
.
Passes<{ readonly depth: typeof depth; }>.add<{
color: d.v4f;
}, {
position: d.v4f;
world: d.v3f;
normal: d.v3f;
uv: d.v2f;
uv1: d.v2f;
row: number;
}>(this: passes.Passes<passes.Fed<{
color: d.v4f;
}, {
position: d.v4f;
world: d.v3f;
normal: d.v3f;
uv: d.v2f;
uv1: d.v2f;
row: number;
}>>, drawing: passes.Compound<{
color: d.v4f;
}, {
position: d.v4f;
world: d.v3f;
normal: d.v3f;
uv: d.v2f;
uv1: d.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
(
renderer: instances.Renderer
renderer
)));
const casters: passes.Drawn[]
casters
.
Array<Drawn>.push(...items: passes.Drawn[]): number

Appends new elements to the end of an array, and returns the new length of the array.

@param ― items New elements to add to the array.

push
(
const own: Owner
<passes.Drawn>(item: passes.Drawn) => passes.Drawn

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
const light: lighting.LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>
light
.
shadow: Cascades | (Shadow & Cascades)

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

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

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

add
(
renderer: instances.Renderer
renderer
)));
const drawn: passes.Drawn[]
drawn
.
Array<Drawn>.push(...items: passes.Drawn[]): number

Appends new elements to the end of an array, and returns the new length of the array.

@param ― items New elements to add to the array.

push
(
const own: Owner
<passes.Drawn>(item: passes.Drawn) => passes.Drawn

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
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: d.v4f;
}, {
position: d.v4f;
world: d.v3f;
normal: d.v3f;
uv: d.v2f;
uv1: d.v2f;
row: number;
}>(this: passes.Passes<passes.Fed<{
color: d.v4f;
}, {
position: d.v4f;
world: d.v3f;
normal: d.v3f;
uv: d.v2f;
uv1: d.v2f;
row: number;
}>>, drawing: passes.Compound<{
color: d.v4f;
}, {
position: d.v4f;
world: d.v3f;
normal: d.v3f;
uv: d.v2f;
uv1: d.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
(
renderer: instances.Renderer
renderer
)));
},
/** Applies `stages` to TAA and the final pass, which keep their own state. */
function apply(): void

Applies stages to TAA and the final pass, which keep their own state.

apply
() {
const aa: taa.Taa
aa
.
Taa.enable(on: boolean): void

Enables or disables TAA. When disabled, jitter is zero from the next frame, record records nothing and output returns the source. Enabling it again resets history.

enable
(
const stages: Stages

The stages that run, read every frame. Call apply after changing taa or bloom.

stages
.
Stages.taa: boolean
taa
);
const final: finish.Finish
final
.
Finish.settings: TgpuUniform<d.WgslStruct<{
exposure: d.F32;
vignette: d.F32;
dither: d.F32;
glowIntensity: d.F32;
}>>

Holds defaults with overrides from settings. Use patch to upload only the fields you supply.

settings
.
TgpuBufferBindingBase<WgslStruct<{ exposure: F32; vignette: F32; dither: F32; glowIntensity: F32; }>>.patch(data: {
exposure?: number | undefined;
vignette?: number | undefined;
dither?: number | undefined;
glowIntensity?: number | undefined;
} | undefined): void
patch
({
glowIntensity?: number | undefined

Scales the glow from options.glow added to the source.

glowIntensity
:
const stages: Stages

The stages that run, read every frame. Call apply after changing taa or bloom.

stages
.
Stages.bloom: boolean
bloom
?
const GLOW: 0.2

How strongly the glow adds to the picture.

GLOW
: 0 });
},
initAsync: () => Promise<void>
initAsync
: () =>
import pipelines
pipelines
.
function compile(items: readonly pipelines.Warmable[], options?: pipelines.CompileOptions): Promise<void>

Starts compiling every item, yielding to the event loop past the time budget so the page's other work, such as a loading animation, keeps running. Use before the frame loop starts. Waits for every item to finish, then rejects if any failed.

@param ― items pipelines, and modules that compile their own

@returns ― a promise that resolves once every item has compiled

compile
([
const prepass: passes.Passes<{
readonly depth: typeof passes.depth;
}>
prepass
,
const occlusion: ao.Ao
occlusion
,
const light: lighting.LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>
light
.
shadow: Cascades | (Shadow & Cascades)

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

shadow
,
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
,
const aa: taa.Taa
aa
,
const glow: bloom.Bloom
glow
,
const final: finish.Finish
final
]),
function record(frame: Frame): void
record
(
frame: Frame
frame
:
(alias) interface Frame
import Frame

Represents one frame being recorded. Each run creates a separate object, so modules can compare frames by identity.

Frame
) {
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; }; }>.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 screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.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 light: lighting.LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: shading.Material, point: shading.Point) => d.v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>
light
.
shadow: Cascades | (Shadow & Cascades)

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

shadow
.
SunShadow.record(frame: 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: Frame
frame
,
const casters: passes.Drawn[]
casters
); // a depth pass per cascade
if (
const stages: Stages

The stages that run, read every frame. Call apply after changing taa or bloom.

stages
.
Stages.occlusion: boolean
occlusion
)
const prepass: passes.Passes<{
readonly depth: typeof passes.depth;
}>
prepass
.
Passes<{ readonly depth: typeof depth; }>.record(frame: Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
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: Frame
frame
,
const shapes: passes.Drawn[]
shapes
);
const occlusion: ao.Ao
occlusion
.
Ao.record(target: Frame | TgpuCommandEncoder, options?: Timed): void

Records the trace and the denoise after the pass that draws depth, before the passes that shade, into a frame's encoder, read at the call, or into an encoder. Optional timer writes measure both passes. When disabled, it writes no timestamps, so use a profiler timer around it only while enabled is true.

record
(
frame: Frame
frame
); // reads the prepass
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: 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: Frame
frame
,
const drawn: passes.Drawn[]
drawn
); // reads the shadow and the occlusion
const aa: taa.Taa
aa
.
Taa.record(target: Frame | TgpuCommandEncoder, options?: Timed): void

Records the resolve after source and motion passes, then sets jitter for the next frame. Records nothing while disabled. Optional timer writes measure the resolve.

record
(
frame: Frame
frame
);
if (
const stages: Stages

The stages that run, read every frame. Call apply after changing taa or bloom.

stages
.
Stages.bloom: boolean
bloom
)
const glow: bloom.Bloom
glow
.
Bloom.record(target: Frame | TgpuCommandEncoder, options?: Timed): void

Records bloom passes after the source is written. Optional timer writes measure the passes together.

record
(
frame: Frame
frame
); // reads TAA's picture
const final: finish.Finish
final
.
Finish.record(target: Frame | TgpuCommandEncoder, view: GPUTextureView, options?: finish.RecordOptions): void

Opens a pass on view, such as surface.view(frame), draws into it and ends it. A viewport draws the picture into that rectangle and clears the rest, transparent with alpha: 'premultiplied' and black otherwise. Optional timer writes measure the pass.

record
(
frame: Frame
frame
,
surface: targets.Surface
surface
.
Surface.view(frame: Frame): GPUTextureView

Returns a view of the current texture, created once per texture. React Native requires the frame to present after submission.

view
(
frame: Frame
frame
));
},
destroy: () => void
destroy
:
const own: Owner
own
.
Owner.destroy(): void

Frees everything it took, the last taken first, once; later calls do nothing. When one throws, frees the rest and then rethrows. Also [Symbol.dispose].

destroy
,
};

own frees everything the look made, the last first, including the draws add built.

scene.ts composes the parts. It owns the camera, hands it to the look, and adds the props’ renderer:

scene.ts
import {
import cameras
cameras
,
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
, type
(alias) interface Frame
import Frame

Represents one frame being recorded. Each run creates a separate object, so modules can compare frames by identity.

Frame
, type
import gltf
gltf
, type
(alias) interface Surface
import Surface

Provides a destination for frames, such as a canvas from web.surface or native.surface, or a texture from offscreenSurface in a test. Pass the frame to view for code that works with every destination.

Surface
} from '@typegpu/kit';
import type {
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
} from 'typegpu';
import {
function createLook(root: TgpuRoot, surface: Surface, view: cameras.OrbitCamera, options: LookOptions): {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<...>;
record(frame: Frame): void;
destroy: () => void;
}

The lighting and every pass from the prepass to the canvas, seen through view.

createLook
} from './look.ts';
import {
function createProps(root: TgpuRoot, models: Readonly<Record<string, ModelData | Shape>>, placements: readonly Placement[]): {
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}

One pool of every model and shape, and one set of instances placing them.

createProps
, type
(alias) interface Placement
import Placement

A piece of the scene: a model by name, and its row's values.

Placement
} from './props.ts';
export interface
interface SceneOptions
SceneOptions
{
/** Each model as `gltf.read` returns it, by name. */
readonly
SceneOptions.models: Readonly<Record<string, gltf.ModelData<never>>>

Each model as gltf.read returns it, by name.

models
:
type Readonly<T> = { readonly [P in keyof T]: T[P]; }

Make all properties in T readonly

Readonly
<
type Record<K extends keyof any, T> = { [P in K]: T; }

Construct a type with a set of properties K of type T

Record
<string,
import gltf
gltf
.
interface ModelData<Name extends string = never>

This type contains binary glTF data as arrays ready for meshPool.add. Materials, textures and samplers hold every field, with glTF defaults for omitted values.

ModelData
>>;
readonly
SceneOptions.placements: readonly Placement[]
placements
: readonly
(alias) interface Placement
import Placement

A piece of the scene: a model by name, and its row's values.

Placement
[];
}
export async function
function createScene(root: TgpuRoot, surface: Surface, options: SceneOptions): Promise<{
view: cameras.OrbitCamera;
frame(frame: Frame): void;
destroy: () => void;
}>
createScene
(
root: TgpuRoot
root
:
(alias) interface TgpuRoot
import TgpuRoot
TgpuRoot
,
surface: Surface
surface
:
(alias) interface Surface
import Surface

Provides a destination for frames, such as a canvas from web.surface or native.surface, or a texture from offscreenSurface in a test. Pass the frame to view for code that works with every destination.

Surface
,
options: SceneOptions
options
:
interface SceneOptions
SceneOptions
) {
const
const own: Owner
own
=
function owner(message?: string): Owner

Collects what a scene or module makes, to free it all in one call, and guards its methods. Pass each thing through it as you make it, and return its destroy as your own. Take a resource that is replaced during the module's life, such as a texture that follows the canvas size, as one cleanup that frees the current one. Methods that only read CPU state or do optional work, such as capture, take or a camera's update, should return early on destroyed instead of calling usable.

const own = owner('The swarm was destroyed.');
const screen = own(targets.create(root, surface, { hdr }));
const settings = own(root.createUniform(Settings));
own(() => {
for (const buffer of reads) buffer.destroy();
});
return { step(pass) { own.usable(); … }, destroy: own.destroy, [Symbol.dispose]: own.destroy };

@param ― message what usable throws, naming the module: "The table was destroyed."

@returns ― an owner you pass each thing to and destroy

owner
();
const
const view: cameras.OrbitCamera
view
=
const own: Owner
<cameras.OrbitCamera>(item: cameras.OrbitCamera) => cameras.OrbitCamera

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
import cameras
cameras
.
function orbit(root: TgpuRoot, options: cameras.OrbitOptions): cameras.OrbitCamera

Creates a perspective camera that orbits a target and eases towards a goal set by input.

@returns ― a camera you update every frame and destroy

orbit
(
root: TgpuRoot
root
, {
OrbitOptions.size: SizeSource

Supplies the size used for the aspect ratio, such as the canvas surface.

size
:
surface: Surface
surface
,
OrbitOptions.target: readonly [number, number, number]

Sets the initial point that the camera orbits and looks at. Change it through goal.target.

target
: [0, 0.2, 0.5],
OrbitOptions.distance: number

Sets the initial distance from the target to the eye, in world units. Change it through goal.distance.

distance
: 14,
OrbitOptions.pitch?: number | undefined

Sets the initial pitch in radians above the horizon. Default 0.6.

pitch
: 0.6 }),
);
const
const props: {
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}
props
=
const own: Owner
<{
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}>(item: {
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}) => {
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
function createProps(root: TgpuRoot, models: Readonly<Record<string, ModelData | Shape>>, placements: readonly Placement[]): {
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}

One pool of every model and shape, and one set of instances placing them.

createProps
(
root: TgpuRoot
root
,
options: SceneOptions
options
.
SceneOptions.models: Readonly<Record<string, gltf.ModelData<never>>>

Each model as gltf.read returns it, by name.

models
,
options: SceneOptions
options
.
SceneOptions.placements: readonly Placement[]
placements
));
const
const look: {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<void>;
record(frame: Frame): void;
destroy: () => void;
}
look
=
const own: Owner
<{
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<void>;
record(frame: Frame): void;
destroy: () => void;
}>(item: {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<void>;
record(frame: Frame): void;
destroy: () => void;
}) => {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<void>;
record(frame: Frame): void;
destroy: () => void;
}

Takes item to free at destroy and returns it. Passes undefined through. Frees a fixed resource, such as one from root.createUniform, through its buffer, and runs a cleanup function. After destroy, frees item at once.

own
(
function createLook(root: TgpuRoot, surface: Surface, view: cameras.OrbitCamera, options: LookOptions): {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<...>;
record(frame: Frame): void;
destroy: () => void;
}

The lighting and every pass from the prepass to the canvas, seen through view.

createLook
(
root: TgpuRoot
root
,
surface: Surface
surface
,
const view: cameras.OrbitCamera
view
, {
LookOptions.far: number

How far the sun's shadow reaches, a little past the farthest prop the view shows.

far
: 28,
LookOptions.crease: number

The size of the gaps the occlusion darkens, in world units.

crease
: 0.7,
LookOptions.light: Partial<InferInput<WgslStruct<{
sunDirection: Vec3f;
sunIntensity: F32;
sunColor: Vec3f;
skyIntensity: F32;
skyTop: Vec3f;
fogDensity: F32;
skyBottom: Vec3f;
fogFalloff: F32;
fogColor: Vec3f;
skyReflection: F32;
fogStart: F32;
}>>>
light
: {
sunDirection: [number, number, number]
sunDirection
: [-0.75, 0.4, -0.45],
sunColor: [number, number, number]
sunColor
: [1, 0.66, 0.4],
sunIntensity: number
sunIntensity
: 1.6 },
LookOptions.stages: Stages

The stages that run, read every frame. Call apply after changing taa or bloom.

stages
: {
Stages.shadows: boolean
shadows
: true,
Stages.occlusion: boolean
occlusion
: true,
Stages.taa: boolean
taa
: true,
Stages.bloom: boolean
bloom
: true },
}),
);
const look: {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<void>;
record(frame: Frame): void;
destroy: () => void;
}
look
.
function add(renderer: Renderer): void

Draws renderer in the prepass, the sun's shadow and the scene.

add
(
const props: {
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}
props
.
renderer: Renderer
renderer
);
const look: {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<void>;
record(frame: Frame): void;
destroy: () => void;
}
look
.
function apply(): void

Applies stages to TAA and the final pass, which keep their own state.

apply
();
await
var Promise: PromiseConstructor

Represents the completion of an asynchronous operation

Promise
.
PromiseConstructor.all<[Promise<void[]>, Promise<void>]>(values: [Promise<void[]>, Promise<void>]): Promise<[void[], void]> (+1 overload)

Creates a Promise that is resolved with an array of results when all of the provided Promises resolve, or rejected when any Promise is rejected.

@param ― values An array of Promises.

@returns ― A new Promise.

all
([
const props: {
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}
props
.
ready: Promise<void[]>

Settles once every model's textures are on the GPU.

ready
,
const look: {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<void>;
record(frame: Frame): void;
destroy: () => void;
}
look
.
initAsync: () => Promise<void>
initAsync
()]);
return {
view: cameras.OrbitCamera
view
,
function frame(frame: Frame): void
frame
(
frame: Frame
frame
:
(alias) interface Frame
import Frame

Represents one frame being recorded. Each run creates a separate object, so modules can compare frames by identity.

Frame
) {
const view: cameras.OrbitCamera
view
.
OrbitCamera.update(dt: number): void

Eases towards the goal by dt seconds and writes the uniform if the view changed. Call once per frame.

update
(
frame: Frame
frame
.
Frame.dt: number

Reports seconds since the previous submitted frame. The first frame has a dt of zero.

dt
);
const props: {
renderer: Renderer;
ready: Promise<void[]>;
flush: () => void;
destroy: () => void;
}
props
.
flush: () => void
flush
();
const look: {
light: LightingOf<{
readonly shading: {
readonly ambient: TgpuGenericFn<(material: Material, point: Point) => v3f>;
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 2;
readonly far: number;
readonly filter: 9;
readonly enabled: () => boolean;
};
}>;
add(renderer: Renderer): void;
apply(): void;
initAsync: () => Promise<void>;
record(frame: Frame): void;
destroy: () => void;
}
look
.
function record(frame: Frame): void
record
(
frame: Frame
frame
);
},
destroy: () => void
destroy
:
const own: Owner
own
.
Owner.destroy(): void

Frees everything it took, the last taken first, once; later calls do nothing. When one throws, frees the rest and then rethrows. Also [Symbol.dispose].

destroy
,
};
}

A game adds its own parts beside the props, such as a picker for the piece under the pointer or lines for a selection outline. Each draws through the same look by its renderer, or records after look.record into the canvas.

  • Shadows draw every caster once per cascade. The map’s 2048-texel layers take 8 MB each in the default depth16unorm, and filter: 9 takes nine comparisons per shaded pixel. One cascade suits a view that shows a small board from one height.
  • Occlusion draws every solid prop again into the half-resolution prepass, then traces and denoises at half resolution, a quarter of the pixels.
  • TAA takes one full-screen pass and two rgba16float history textures. FXAA costs one pass on the finished 8-bit picture and keeps no history. It smooths steps in a still frame but leaves thin parts shimmering as the view turns, so prefer it on the weakest phones.
  • Bloom draws six levels down and five up, each half the size of the one before.
  • The final pass tone maps, adds the glow and grades in one full-screen pass.

A stage turns off without rebuilding anything. The look reads its stages every frame: the shadow and the occlusion through their enabled options, the prepass and the bloom by skipping their recording. TAA and the glow keep state of their own, so apply passes a change on with aa.enable and the final pass’s glowIntensity. The figure above is this look, with buttons that flip its stages.