Skip to content

@typegpu/kittaa

Temporal antialiasing

Smooth stepped edges and shimmer by gathering samples over several frames.

kit.taa

module

taa.create(root, { source, motion?, depth?, camera?, … }) → Taa

rootborrows
the root
sourcetakes
this frame's picture, drawn through jitter, read at every record
motion?takes
how far each pixel's surface moved since last frame, read at every record; without it, pixels move only with the camera
depth?takes
the scene's depth, for the motion of the nearest surface around each pixel and for motion from the camera
camera?needs
cameras
Taareturns
a resolve you record every frame and destroy
GPU functions
written
outputs
motion
schemas
Settings
constants
defaults
types
Taa
Optionscreate · 6 fields
source

(() => FloatTexture) | Rendered

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

motion?

() => FloatTexture= none, which moves every pixel as camera moves depth, or keeps it in place without both

Returns the motion texture written by motion.

depth?

() => Depth= none, which uses motion at each pixel

Returns sampled scene depth without stencil.

camera?

CameraBase= none

Supplies the scene camera with its preceding view, such as cameras.orbit.

region?

SizeSource= the region of a rendered source, else the whole texture

Selects the source region from its top-left corner, such as regions.hdr when drawing below full resolution.

settings?

Partial<d.InferInput<typeof Settings>>= defaults, with blend 0.1 and spread 1

Sets initial values over defaults.

records
  • recordencoder

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

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.

allocates

on resize

Targets for the new size and the bind groups of its passes; so does a new source, motion or depth.

lifecycle

initAsync()destroy()[Symbol.dispose]

binds
  • 1 uniform

    jitter, this frame's offset.

needs
  • cameras

    As options.camera; fills a hook in its pipelines.

latticeThin beams turning over a sky, embers rising through them, smoothed over frames with TAA.
Hold the pointer over distant beams and turn TAA off. The loupe shows thin beams stepping and shimmering as they turn.

With one sample per pixel, angled edges form steps, and thin lines shimmer as they move. MSAA takes multiple samples in each drawing pass, and FXAA smooths the finished picture. TAA distributes samples across frames. Each frame offsets the scene by a fraction of a pixel. The resolve blends it with the previous image using motion to find each surface’s previous position. Every scene pass must jitter, and drawings that move must write motion. Use pass descriptions for those passes:

import {
import finish
finish
,
import taa
taa
} from '@typegpu/kit';
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly motion: {
readonly format: "rg16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
=
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly motion: {
readonly format: "rg16float";
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 motion: {
readonly format: "rg16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<...>

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

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

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

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

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

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true },
motion: {
readonly format: "rg16float";
readonly sampled: true;
}
motion
: {
format: "rg16float"
format
: 'rg16float',
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
=
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
(
const root: TgpuRoot
root
, {
Options.source: finish.Rendered | (() => finish.FloatTexture)

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 motion: {
readonly format: "rg16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly motion: { readonly format: "rg16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly motion: targets.Texture<{
readonly format: "rg16float";
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.motion?: (() => finish.FloatTexture) | undefined

Returns the motion texture written by motion. Each vector is the preceding UV position minus the current UV position. record reads it on every call. Default none, which moves every pixel as camera moves depth, or keeps it in place without both.

motion
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly motion: {
readonly format: "rg16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly motion: { readonly format: "rg16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly motion: targets.Texture<{
readonly format: "rg16float";
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
.
motion: targets.Texture<{
readonly format: "rg16float";
readonly sampled: true;
}>
motion
,
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 motion: {
readonly format: "rg16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly motion: { readonly format: "rg16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly motion: targets.Texture<{
readonly format: "rg16float";
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
:
const view: cameras.OrbitCamera
view
,
});
const
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly motion: passes.Output<unknown, {
position: d.v4f;
}>;
readonly depth: typeof passes.depth;
}>
scene
=
import passes
passes
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly motion: {
readonly format: "rg16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}, {
readonly hdr: typeof passes.color;
readonly motion: passes.Output<unknown, {
position: d.v4f;
}>;
readonly depth: typeof passes.depth;
}>(root: TgpuRoot, screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly motion: {
readonly format: "rg16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>, writes: {
readonly hdr: typeof passes.color;
readonly motion: passes.Output<unknown, {
position: d.v4f;
}>;
readonly depth: typeof passes.depth;
}, options?: passes.TargetOptions<...> | undefined): passes.Passes<...> (+1 overload)

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

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

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

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

create
(
const root: TgpuRoot
root
,
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly motion: {
readonly format: "rg16float";
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
,
motion: passes.Output<unknown, {
position: d.v4f;
}>
motion
:
import taa
taa
.
const motion: passes.Output<unknown, {
position: d.v4f;
}>

This output writes surface motion into an rg16float target. It stores preceding UV position minus current UV position. It computes the preceding position by compiling renderer vertex code with history.previously, which reads the preceding camera and values of other temporal fills.

motion
,
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
},
)
.
Passes<{ readonly hdr: typeof color; readonly motion: Output<unknown, { position: d.v4f; }>; readonly depth: typeof depth; }>.pipe(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly motion: passes.Output<unknown, {
position: d.v4f;
}>;
readonly depth: typeof passes.depth;
}>

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

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

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

inject
());
const
const drawn: passes.Drawn
drawn
=
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly motion: passes.Output<unknown, {
position: d.v4f;
}>;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly motion: Output<unknown, { position: d.v4f; }>; readonly depth: typeof depth; }>.add<d.WgslStruct<{
position: d.Vec4f;
}>, {
color: d.v4f;
}>(this: passes.Passes<passes.Fed<{
color: d.v4f;
}, {
position: d.v4f;
}>>, drawing: passes.Drawing<d.WgslStruct<{
position: d.Vec4f;
}>, {
color: d.v4f;
}>): passes.Drawn (+1 overload)

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

add
(
const beams: passes.Drawing<d.WgslStruct<{
position: d.Vec4f;
}>, {
color: d.v4f;
}>
beams
); // a drawing, such as the lattice's beams
const
const final: finish.Finish
final
=
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
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
const surface: web.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
});
// Each frame
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly motion: {
readonly format: "rg16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly motion: { readonly format: "rg16float"; 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 scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly motion: passes.Output<unknown, {
position: d.v4f;
}>;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly motion: Output<unknown, { position: d.v4f; }>; readonly depth: typeof depth; }>.record(frame: Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
readonly hdr: typeof passes.color;
readonly motion: passes.Output<unknown, {
position: d.v4f;
}>;
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
(
const frame: Frame
frame
,
const drawn: passes.Drawn
drawn
);
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
(
const frame: Frame
frame
);
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
(
const frame: Frame
frame
,
const surface: web.Surface
surface
.
Surface.view(frame: Frame): GPUTextureView

Returns a view of the current canvas texture, created once per texture. Applies pending size changes first, which a pass using context would skip.

view
(
const frame: Frame
frame
));
  • taa.motion writes each surface’s movement since the previous frame in UV coordinates. It reruns vertex code with history.previously to read the previous camera and other temporal fills.
  • All renderers include camera motion. Object motion requires vertex code to read temporal state. Particles use velocity, and the lattice stores its turn in a history. Skinned poses keep last frame’s joints, so posed joints show their own motion. By default, instances, trails and text include only camera motion. A set of instances or a table created with history: true gives each row that moves its own motion, whether write or a kernel moved it.
  • Renderers with stationary vertices, such as a full-screen sky, use scene.without('motion'). See Passes for their outputs.
  • Unwritten motion pixels retain the taa.motion clear value, and taa.written(motion) is false for them in a shader of yours. With camera and depth, the resolve reconstructs surface position from depth and applies camera motion. Otherwise those pixels stay fixed.
  • Omit motion when no drawing’s vertices move, as in the ray-marched jelly slider. The resolve then binds no motion texture, and the scene writes no motion target. With camera and depth, every pixel follows the camera. Without them, history stays at each pixel, which suits a still camera.
  • Jitter cycles through 8 subpixel offsets. GPU copies advance it after each resolve without per-frame CPU writes. aa.offset holds the current clip-space offset for custom jitter shaders.
  • The resolve clamps previous colour within spread standard deviations of nearby current colours. It takes blend of the current frame and downweights bright values in both frames. With depth, each pixel uses motion from the nearest surrounding surface.
  • Call reset() after a camera cut. Resizing, region changes and enable(true) also reset history. After enable(false), jitter is zero, record records nothing and output is the source, so keep calling record and keep aa as the next pass’s source.
  • Give bloom source: aa to bloom the antialiased picture. Every post-processing source takes aa and reads its output and region at each call, because output alternates between two textures.
  • record takes the frame or an encoder. record(frame, { timestampWrites }) times the resolve under a profiler timer.

A finished look for model scenes runs it after a scene lit with shadows and ambient occlusion, before bloom and the final pass.

Part
aa.settings Blend and spread, a uniform.
aa.jitter, aa.offset The jitter for passes.create, and this frame’s offset in clip space, a uniform.
aa.output, aa.region Antialiased rgba16float image and its region, or the source while off. Read each frame because output alternates between two textures.
aa.enabled Whether enable last turned it on.
  • TAA’s output is the source’s size; it doesn’t upscale.
  • TAA writes alpha 1, so it doesn’t suit a canvas over a page.
  • A renderer that leaves out motion, such as an additive glow, takes the motion of what is under it.
  • Moving instances, trails, text and skinned poses need their own motion to avoid dimming. Their default motion includes only the camera.
  • Lines thinner than one pixel can lose most of their brightness, even while stationary.