Sun shadows, ambient occlusion, TAA, bloom and a grade for a scene of many instanced glTF models, in a file another program can copy.
24 × 24 px
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.tscreateScene with the camera, the props, the look and the frame
look.tscreateLook with the lighting, the passes and the post-processing
props.tscreateProps 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,
functionowner(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.
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. */
Construct a type with a set of properties K of type T
Record<string,
import meshPool
meshPool.
interfaceModelData<EextendsmeshPool.Extras={}>
This type describes a model returned by gltf.read, with one mesh per material and its
images.
ModelData|
import meshPool
meshPool.
typeShape=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
interfacePlacement
A piece of the scene: a model by name, and its row's values.
Placement[],
){
const
constown:Owner
own=
functionowner(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.
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.
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
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.
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.
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
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.
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.
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+
constmodelOf:(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.
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.
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,
constpool: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:
constrows:number
rows}));
for(const{
constmodel:string
model,
constvalues:{
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.
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(
constmodelOf:(name:string)=>meshPool.Model
modelOf(
constmodel:string
model),
constvalues:{
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.
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
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.
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:
constown: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. */
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. */
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.
constat:(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`. */
The lighting and every pass from the prepass to the canvas, seen through view.
createLook(
root: TgpuRoot
root:
(alias)interfaceTgpuRoot
importTgpuRoot
TgpuRoot,
surface:targets.Surface
surface:
(alias)interfaceSurface
importSurface
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.
interfaceOrbitCamera
OrbitCamera,
options: LookOptions
options:
interfaceLookOptions
LookOptions,
){
const{
conststages:Stages
The stages that run, read every frame. Call apply after changing taa or bloom.
stages}=
options: LookOptions
options;
const
constown:Owner
own=
functionowner(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.
@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
constearly:targets.Targets<{
readonly depth:{
readonly format:"depth32float";
readonly scale:0.5;
readonly sampled:true;
};
}>
early=
constown: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.
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
constprepass:passes.Passes<{
readonly depth:typeofpasses.depth;
}>
prepass=
import passes
passes.
functioncreate<{
readonly depth:{
readonly format:"depth32float";
readonly scale:0.5;
readonly sampled:true;
};
},{
readonly depth:typeofpasses.depth;
}>(root:TgpuRoot,screen:targets.Targets<{
readonly depth:{
readonly format:"depth32float";
readonly scale:0.5;
readonly sampled:true;
};
}>,writes:{
readonly depth:typeofpasses.depth;
},options?:passes.TargetOptions<{
readonly depth:{
readonly format:"depth32float";
readonly scale:0.5;
readonly sampled:true;
};
}>|undefined):passes.Passes<{
readonly depth:typeofpasses.depth;
}>(+1overload)
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
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
constocclusion:ao.Ao
occlusion=
constown: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.
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.
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:()=>
conststages:Stages
The stages that run, read every frame. Call apply after changing taa or 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.
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
Applies a configuration transform to the slots and accessors this function reads,
as root.pipe does to a pipeline's.
pipe(
constocclusion: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:()=>
conststages:Stages
The stages that run, read every frame. Call apply after changing taa or bloom.
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.
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
constdusk:(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
constlight:number
light=
import std
std.
functionsaturate(value:number):number(+1overload)
export saturate
saturate(
import tone
tone.
constluminance:(color:d.v3f)=>number
Returns the relative luminance of a linear colour.
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.
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
constaa:taa.Taa
aa=
constown: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.
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
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.
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.
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
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:
constocclusion: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
constscene:passes.Passes<{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
}>
scene=
import passes
passes
.
functioncreate<{
readonly hdr:{
readonly format:"rgba16float";
readonly sampled:true;
};
readonly depth:{
readonly format:"depth24plus";
readonly sampled:true;
};
},{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.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:typeofpasses.color;
readonly depth:typeofpasses.depth;
},options?:passes.TargetOptions<{
readonly hdr:{
readonly format:"rgba16float";
readonly sampled:true;
};
readonly depth:{
readonly format:"depth24plus";
readonly sampled:true;
};
}>|undefined):passes.Passes<...>(+1overload)
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,
constscreen:targets.Targets<{
readonly hdr:{
readonly format:"rgba16float";
readonly sampled:true;
};
readonly depth:{
readonly format:"depth24plus";
readonly sampled:true;
};
}>
screen,{
hdr:typeofpasses.color
hdr:
import passes
passes.
constcolor:typeofpasses.color
Marks the target that receives each renderer's colour.
color,
depth:typeofpasses.depth
depth:
import passes
passes.
constdepth:typeofpasses.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:
constaa: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.
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.
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().
Fills the shading hooks and resources for pipelines created from the configuration you pipe
it into.
inject());
const
constglow:bloom.Bloom
glow=
constown: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.
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
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.
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.
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.
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:
constaa: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.
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:
constdusk:(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:
/** Draws `renderer` in the prepass, the sun's shadow and the scene. */
functionadd(renderer:instances.Renderer):void
Draws renderer in the prepass, the sun's shadow and the scene.
add(
renderer:instances.Renderer
renderer:
import instances
instances.
interfaceRenderer
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){
constshapes: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(
constown: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(
constprepass:passes.Passes<{
readonly depth:typeofpasses.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(+1overload)
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)));
constcasters: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(
constown: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.
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(+1overload)
Builds a caster's depth pipeline with the shadow's bias and returns its draw methods.
add(
renderer:instances.Renderer
renderer)));
constdrawn: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(
constown: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.
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. */
functionapply():void
Applies stages to TAA and the final pass, which keep their own state.
apply(){
constaa: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(
conststages:Stages
The stages that run, read every frame. Call apply after changing taa or bloom.
stages.
Stages.taa: boolean
taa);
constfinal: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.
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
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.
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.
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,
constcasters:passes.Drawn[]
casters);// a depth pass per cascade
if(
conststages:Stages
The stages that run, read every frame. Call apply after changing taa or bloom.
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.
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.
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.
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(
conststages:Stages
The stages that run, read every frame. Call apply after changing taa or bloom.
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:
constown: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,
functionowner(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.
@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)interfaceFrame
importFrame
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)interfaceSurface
importSurface
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.
Construct a type with a set of properties K of type T
Record<string,
import gltf
gltf.
interfaceModelData<Nameextendsstring=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)interfacePlacement
importPlacement
A piece of the scene: a model by name, and its row's values.
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:
interfaceSceneOptions
SceneOptions){
const
constown:Owner
own=
functionowner(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.
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.
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
constprops:{
renderer:Renderer;
ready:Promise<void[]>;
flush:()=>void;
destroy:()=>void;
}
props=
constown: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.
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.
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.