Skip to content

@typegpu/kitdof

Depth of field

Blur by distance from a focus, with near objects spreading over what is sharp behind them.

kit.dof

module

dof.create(root, view, { source, depth, pull?, region?, … }) → Dof

rootborrows
the root
viewneeds
the camera that drew depth
sourcetakes
the linear picture, read at every record
depthtakes
the depth of the same picture, read at every record
pull?takes
how fast the focus follows aim, per second
region?takes
the part of the source that holds the picture, read at every record
Dofreturns
depth of field you record every frame and destroy
GPU functions
distanceAtthinLensfocuscircle
schemas
Settings
constants
defaults
types
RadiusOutputDofRecordOptions
Optionscreate · 7 fields
source

(() => FloatTexture) | Rendered

Provides the linear picture: a rendered picture, such as aa, or a function returning a texture.

depth

() => Depth

Returns a sampled depth texture of the same picture, of any size, such as a half-resolution prepass.

radius?

Radius= thinLens at focus()

Returns the signed blur radius at an eye distance and a point of the picture, from 0 to 1 over its region, as a fraction of the picture height: negative in front of the focus, positive behind it.

pull?

number= Infinity, which focuses at once from the record after aim

Sets how fast the focus follows aim, per second: each second closes 1 - e^-pull of the way, evenly in blur rather than in distance.

rings?

2 | 3= 3

Sets the rings of taps in the gather, 3 for 49 taps or 2 for 25.

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 focus 10, aperture 0.05 and limit 0.02

Sets initial values over defaults.

records
  • recordencoder

record after the TAA resolve or whatever wrote the source, before bloom and the final pass; then output holds the picture.

owns

targets ·2 buffers ·a sampler ·4 pipelines ·5 bind groups

The half-resolution picture, the tiles, the blur and the output; the settings and the regions; a group for each pass and one for the depth. With pull or after aim, also the focus and its step, the kernel moving it and its groups.

allocates

on resize

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

lifecycle

initAsync()destroy()[Symbol.dispose]

injects
  • hook

    The half-resolution picture and regions dof.circle reads, from the part.

binds
  • 1 uniform

    dof.circle, the regions, beside the texture.

needs
  • cameras

    As view; fills a hook in its pipelines.

night-marketA seller raises her lantern for a customer out of the rain; the potions are in focus and the market's lights blur into discs.
The potions stay sharp while the lights behind them blur into discs. Tap the seller or the street to pull the focus there: the potions blur and spread over what is behind them. Turn depth of field off to compare.

A lens is sharp at one distance. In front of it and behind it, each point spreads into a disc, the circle of confusion, that grows with the distance from the focus. Blurring by distance needs a radius at each pixel, and a blur that spreads a near object over what is sharp behind it while a far one never bleeds onto what is in front. dof.create reads the picture and a depth of it and records 4 passes:

import {
import bloom
bloom
,
import dof
dof
,
import finish
finish
} from '@typegpu/kit';
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth32float";
readonly sampled: true;
};
}>
screen
=
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth32float";
readonly sampled: true;
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth32float";
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth32float";
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
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true },
depth: {
readonly format: "depth32float";
readonly sampled: true;
}
depth
: {
format: "depth32float"
format
: 'depth32float',
sampled: true
sampled
: true },
});
const
const blur: dof.Dof
blur
=
import dof
dof
.
function create(root: TgpuRoot, view: cameras.CameraBase, options: dof.Options): dof.Dof

Creates depth of field. A prepare pass halves the source and stores a signed blur radius from radius. A tiles pass finds the largest radius in each 16 × 16 pixels. A gather pass scales rings of taps to the largest radius around each tile, keeps the far field from bleeding onto what is sharper and spreads the near field over it, as Jimenez describes it in 2014. A composite pass mixes the blur into the full-size source by the radius at each pixel.

@injects ― hook the half-resolution picture and regions dof.circle reads, from the part

@binds ― 0 storage, 1 uniform: dof.circle, the regions, beside the texture

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

@owns ― targets, 2 buffers, a sampler, 4 pipelines, 5 bind groups: the half-resolution picture, the tiles, the blur and the output; the settings and the regions; a group for each pass and one for the depth. With pull or after aim, also the focus and its step, the kernel moving it and its groups

@records ― record after the TAA resolve or whatever wrote the source, before bloom and the final pass; then output holds the picture.

@param ― view the camera that drew depth

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

@param ― options.depth the depth of the same picture, read at every record

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

@param ― options.pull how fast the focus follows aim, per second

@returns ― depth of field you record every frame and destroy

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

Provides the linear picture: a rendered picture, such as aa, or a function returning a texture. record reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth32float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth32float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly depth: targets.Texture<{
readonly format: "depth32float";
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

Returns a sampled depth texture of the same picture, of any size, such as a half-resolution prepass. record reads it on every call. Its drawn part is the source's share of its texture.

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

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

textures
.
depth: targets.Texture<{
readonly format: "depth32float";
readonly sampled: true;
}>
depth
,
Options.settings?: Partial<InferInput<WgslStruct<{
focus: F32;
aperture: F32;
limit: F32;
}>>> | undefined

Sets initial values over defaults. Default defaults, with focus 10, aperture 0.05 and limit 0.02.

settings
: {
focus: number
focus
: 4,
aperture: number
aperture
: 0.03 },
});
const
const glow: bloom.Bloom
glow
=
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
(
const 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 blur: dof.Dof
blur
});
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 blur: dof.Dof
blur
,
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
});
import frames
frames
.
function animate(loop: Pick<frames.Frames, "run"> & Partial<Pick<frames.Frames, "seconds">>, options?: frames.AnimateOptions): frames.Animation
export animate

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

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

@returns ― an animation you destroy to stop the frames

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

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

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

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

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

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth32float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth32float"; 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
();
// the scene, into hdr and depth
const blur: dof.Dof
blur
.
Dof.record(target: frames.Frame | TgpuCommandEncoder, options?: dof.RecordOptions): void

Records the prepare, tiles, gather and composite passes after the source is written. After an aim, it first moves the focus by dt seconds. Optional timer writes measure the passes together.

record
(
frame: frames.Frame
frame
);
const glow: bloom.Bloom
glow
.
Bloom.record(target: frames.Frame | TgpuCommandEncoder, options?: Timed): void

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

record
(
frame: frames.Frame
frame
);
const final: finish.Finish
final
.
Finish.record(target: frames.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: frames.Frame
frame
,
const surface: web.Surface
surface
.
Surface.view(frame: frames.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
(
frame: frames.Frame
frame
));
}),
);
  • Record it after TAA, with source: aa, and before bloom. Bloom reading blur.output turns out-of-focus highlights into glowing discs.
  • The radius is a fraction of the picture height, negative in front of the focus and positive behind it. By default it is the thin-lens circle, 0.5 × aperture × k × (1 / focus − 1 / distance), where k is half the camera’s vertical projection scale, its focal length over the sensor height. limit caps it.
  • aperture and the distances share world units. A 50 mm lens at f/2 in a scene in metres opens 0.025. Larger openings make a scene look smaller, as a macro lens does.
  • depth can be any size and is read by UV. A multisampled depth can’t be read, so draw a single-sampled depth prepass, as hex isle does at half resolution under its MSAA scene. Radius edges then step at the prepass’s resolution.
  • To turn it off, skip record and give bloom and the final pass the picture it read. Point their source at a function that picks one.
  • record takes the frame or an encoder. record(frame, { timestampWrites }) times the 4 passes under one profiler timer.
Field Default
focus 10 Distance in focus, in world units along the view direction.
aperture 0.05 Diameter of the lens opening, in world units.
limit 0.02 Largest blur radius, as a fraction of the picture height.

blur.aim(x, y) focuses on what the depth holds at a point of the picture, from 0 to 1 over it, such as under a tap. pull sets how fast the focus gets there, per second:

import {
import dof
dof
} from '@typegpu/kit';
const
const blur: dof.Dof
blur
=
import dof
dof
.
function create(root: TgpuRoot, view: cameras.CameraBase, options: dof.Options): dof.Dof

Creates depth of field. A prepare pass halves the source and stores a signed blur radius from radius. A tiles pass finds the largest radius in each 16 × 16 pixels. A gather pass scales rings of taps to the largest radius around each tile, keeps the far field from bleeding onto what is sharper and spreads the near field over it, as Jimenez describes it in 2014. A composite pass mixes the blur into the full-size source by the radius at each pixel.

@injects ― hook the half-resolution picture and regions dof.circle reads, from the part

@binds ― 0 storage, 1 uniform: dof.circle, the regions, beside the texture

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

@owns ― targets, 2 buffers, a sampler, 4 pipelines, 5 bind groups: the half-resolution picture, the tiles, the blur and the output; the settings and the regions; a group for each pass and one for the depth. With pull or after aim, also the focus and its step, the kernel moving it and its groups

@records ― record after the TAA resolve or whatever wrote the source, before bloom and the final pass; then output holds the picture.

@param ― view the camera that drew depth

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

@param ― options.depth the depth of the same picture, read at every record

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

@param ― options.pull how fast the focus follows aim, per second

@returns ― depth of field you record every frame and destroy

create
(
const root: TgpuRoot
root
,
const view: cameras.OrbitCamera
view
, {
Options.source: Rendered | (() => FloatTexture)

Provides the linear picture: a rendered picture, such as aa, or a function returning a texture. record reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth32float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth32float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly depth: targets.Texture<{
readonly format: "depth32float";
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

Returns a sampled depth texture of the same picture, of any size, such as a half-resolution prepass. record reads it on every call. Its drawn part is the source's share of its texture.

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

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

textures
.
depth: targets.Texture<{
readonly format: "depth32float";
readonly sampled: true;
}>
depth
,
Options.pull?: number | undefined

Sets how fast the focus follows aim, per second: each second closes 1 - e^-pull of the way, evenly in blur rather than in distance. With it, each record runs the kernel that moves the focus. Default Infinity, which focuses at once from the record after aim.

pull
: 5,
});
// On a tap, at a point of the picture
const blur: dof.Dof
blur
.
Dof.aim(x: number, y: number): void

Focuses on what the depth holds at a point of the picture, from 0 to 1 over its region, such as under a tap. The next record reads the distance there, and from then on each record moves settings.focus towards it on the GPU at pull.

aim
(0.5, 0.5);
// Each frame
const blur: dof.Dof
blur
.
Dof.record(target: Frame | TgpuCommandEncoder, options?: dof.RecordOptions): void

Records the prepare, tiles, gather and composite passes after the source is written. After an aim, it first moves the focus by dt seconds. Optional timer writes measure the passes together.

record
(
const frame: Frame
frame
);
  • The next record reads the distance at the aimed point, and each record from then on moves settings.focus towards it on the GPU by the frame’s dt, in one more compute pass. Nothing is read back.
  • Each second closes 1 - e^-pull of the way, as clock.ease does, evenly in blur rather than in distance, so a pull from far to near looks as smooth as one from near to far. Without pull, the focus jumps.
  • settings.focus sets the focus until the first aim. A radius of your own reads the moving focus with dof.focus().
  • With an encoder in place of the frame, give the seconds yourself, blur.record(encoder, { dt }).

radius(distance, uv) replaces the thin lens. It is a 'use gpu' function that returns the signed radius, and it carries its fills with pipe. Three functions work only inside it:

  • dof.thinLens(distance, focus) returns the thin-lens radius with the settings’ aperture.
  • dof.focus() returns the distance in focus, which follows aim.
  • dof.distanceAt(uv) returns the distance along the view direction to what the depth holds at a point of the picture, from cameras.distanceOf.

Hex isle uses both, through a uniform that picks one. By default it blurs above and below a band, so the island looks like a miniature:

import {
import dof
dof
} from '@typegpu/kit';
const
const cursor: TgpuUniform<d.Vec2f>
cursor
=
const root: TgpuRoot
root
.
TgpuRoot.createUniform<d.Vec2f>(typeSchema: d.Vec2f, initial?: ((buffer: TgpuBuffer<NoInfer<d.Vec2f>>) => void) | d.InferInput<NoInfer<d.Vec2f>> | undefined): TgpuUniform<d.Vec2f> (+1 overload)

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

TgpuRoot.createBuffer

.

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

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

createUniform
(
import d
d
.
const vec2f: d.Vec2f
vec2f
,
import d
d
.
const vec2f: d.Vec2f
(xy: number) => d.v2f (+3 overloads)
vec2f
(0.5));
// Focus on whatever is under the pointer
const
const underCursor: (distance: number, _uv: d.v2f) => number
underCursor
= (
distance: number
distance
: number,
_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';
return
import dof
dof
.
const thinLens: (distance: number, focus: number) => number

Returns the thin-lens blur radius at an eye distance with focus in focus and the settings' aperture, as a fraction of the picture height. It is negative in front of the focus. It reads the settings, so call it only from the radius callback.

thinLens
(
distance: number
distance
,
import dof
dof
.
const distanceAt: (uv: d.v2f) => number

Returns the distance along the view direction to what the depth holds at a point of the picture, from 0 to 1 over its region. Call it only from the radius callback, such as dof.thinLens(distance, dof.distanceAt(cursor.$)) to focus under a pointer.

distanceAt
(
const cursor: TgpuUniform<d.Vec2f>
cursor
.
TgpuUniform<Vec2f>.$: d.v2f
$
));
};
// A miniature: sharp in a band, blurred above it and in front below it
const
const tiltShift: (_distance: number, uv: d.v2f) => number
tiltShift
= (
_distance: number
_distance
: number,
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 away: number
away
=
uv: d.v2f
uv
.
v2f.y: number
y
- 0.54;
return
import std
std
.
function sign(e: number): number (+1 overload)
export sign
sign
(-
const away: number
away
) *
import std
std
.
function max(fst: number, ...rest: number[]): number (+1 overload)
export max
max
(
import std
std
.
function abs(value: number): number (+1 overload)
export abs
abs
(
const away: number
away
) - 0.07, 0) * 0.06;
};
const
const blur: dof.Dof
blur
=
import dof
dof
.
function create(root: TgpuRoot, view: cameras.CameraBase, options: dof.Options): dof.Dof

Creates depth of field. A prepare pass halves the source and stores a signed blur radius from radius. A tiles pass finds the largest radius in each 16 × 16 pixels. A gather pass scales rings of taps to the largest radius around each tile, keeps the far field from bleeding onto what is sharper and spreads the near field over it, as Jimenez describes it in 2014. A composite pass mixes the blur into the full-size source by the radius at each pixel.

@injects ― hook the half-resolution picture and regions dof.circle reads, from the part

@binds ― 0 storage, 1 uniform: dof.circle, the regions, beside the texture

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

@owns ― targets, 2 buffers, a sampler, 4 pipelines, 5 bind groups: the half-resolution picture, the tiles, the blur and the output; the settings and the regions; a group for each pass and one for the depth. With pull or after aim, also the focus and its step, the kernel moving it and its groups

@records ― record after the TAA resolve or whatever wrote the source, before bloom and the final pass; then output holds the picture.

@param ― view the camera that drew depth

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

@param ― options.depth the depth of the same picture, read at every record

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

@param ― options.pull how fast the focus follows aim, per second

@returns ― depth of field you record every frame and destroy

create
(
const root: TgpuRoot
root
,
const view: cameras.OrbitCamera
view
, {
Options.source: Rendered | (() => FloatTexture)

Provides the linear picture: a rendered picture, such as aa, or a function returning a texture. record reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth32float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth32float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly depth: targets.Texture<{
readonly format: "depth32float";
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

Returns a sampled depth texture of the same picture, of any size, such as a half-resolution prepass. record reads it on every call. Its drawn part is the source's share of its texture.

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

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

textures
.
depth: targets.Texture<{
readonly format: "depth32float";
readonly sampled: true;
}>
depth
,
Options.radius?: dof.Radius | undefined

Returns the signed blur radius at an eye distance and a point of the picture, from 0 to 1 over its region, as a fraction of the picture height: negative in front of the focus, positive behind it. This 'use gpu' function runs once per half-resolution texel and once per pixel. It can call thinLens, distanceAt and focus and carry fills with pipe. Default thinLens at focus().

radius
:
const underCursor: (distance: number, _uv: d.v2f) => number
underCursor
,
});
  • Write the pointer into cursor as it moves, in picture coordinates from 0 to 1. The focus jumps to each new surface. To ease it, pull the focus with aim instead.
  • The radius runs once per half-resolution texel and once per pixel, so keep it cheap.

Depth of field keeps its blur radius where a final pass’s grade can’t read it. dof.circle(uv) reads it through blur.part, shared as in Another target. This grade tints the near field orange and the far field blue:

const
const shared: bindings.Shared
shared
=
import bindings
bindings
.
function share(root: TgpuRoot, parts: Record<string, bindings.Part>): bindings.Shared

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

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

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

share
(
const root: TgpuRoot
root
, {
blur: bindings.Part<bindings.Entries>
blur
:
const blur: dof.Dof
blur
.
Dof.part: bindings.Part<bindings.Entries>

Provides the half-resolution picture and its regions for dof.circle. Share it before creating the pipelines that read it, bindings.share(root, { blur: blur.part }), so a new source size replaces the texture without replacing pipelines.

part
}); // before `finish.create`
const
const focusBand: (color: d.v3f, uv: d.v2f) => d.v3f
focusBand
= (
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 radius: number
radius
=
import dof
dof
.
const circle: (uv: d.v2f) => number

Returns the signed blur radius record last prepared at a point of the picture, from 0 to 1 over its region, as a fraction of the picture height. Call it from a pipeline piped with blur.inject(), such as a final pass's grade showing the focus.

circle
(
uv: d.v2f
uv
);
const
const tint: d.v3f
tint
=
import std
std
.
select<d.v3f>(f: d.v3f, t: d.v3f, cond: boolean | d.v3b): d.v3f (+2 overloads)
export select

Returns t if cond is true, and f otherwise. Component-wise if cond is a vector.

@example select(1, 2, false) // returns 1 select(1, 2, true) // returns 2 select(vec2i(1, 2), vec2i(3, 4), true) // returns vec2i(3, 4) select(vec2i(1, 2), vec2i(3, 4), vec2b(false, true)) // returns vec2i(1, 4)

select
(
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(1, 0.45, 0.1),
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(0.1, 0.45, 1),
const radius: number
radius
> 0);
return
import std
std
.
mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
export mix
mix
(
color: d.v3f
color
,
const tint: d.v3f
tint
,
import std
std
.
function saturate(value: number): number (+1 overload)
export saturate
saturate
(
import std
std
.
function abs(value: number): number (+1 overload)
export abs
abs
(
const radius: number
radius
) * 100));
};
const
const focusView: finish.Finish
focusView
=
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
: 'bgra8unorm',
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 blur: dof.Dof
blur
,
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 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: <(color: d.v3f, uv: d.v2f) => d.v3f>(inner: (color: d.v3f, uv: d.v2f) => d.v3f) => TgpuGenericFn<(color: d.v3f, uv: d.v2f) => d.v3f> (+2 overloads)
fn
(
const focusBand: (color: d.v3f, uv: d.v2f) => d.v3f
focusBand
).
TgpuGenericFn<(color: d.v3f, uv: d.v2f) => v3f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(color: d.v3f, uv: d.v2f) => d.v3f>

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

pipe
(
const blur: dof.Dof
blur
.
Dof.inject(): Inject

Fills dof.circle for pipelines created from the configuration it is piped into.

inject
()),
});
// Set `shared.group()` on the pass before `focusView.draw`
  1. Prepare, at half resolution. Averages 2 × 2 pixels and stores the radius at the nearest of their depths in alpha.
  2. Tiles, at a sixteenth of each side. Each texel holds the largest radius in its 16 × 16 pixels.
  3. Gather, at half resolution. Scales rings of taps, 3 rings for 49 taps or 2 for 25, to the largest radius in the tiles that a circle of limit can reach. A tap behind the focus counts where the smaller of its circle and the centre’s reaches the centre, so what is sharp keeps its edge. A tap in front counts where its own circle reaches, and lays over the rest by how much of the disc such taps cover, so a blurred foreground spreads over a sharp background. Where the largest radius is under half a texel, it copies the centre.
  4. Composite, at full resolution. Adds the source’s detail above the half-resolution picture back onto the blur where the radius is under a pixel, fading out by 3 pixels, and where no near field covers it.
Pass Textures Samplers Uniforms
Prepare source, depth 0 settings, regions, camera
Tiles half-resolution picture 0 regions
Gather half-resolution picture, tiles 0 settings, regions
Composite source, half-resolution picture, blur, depth 1 settings, regions, camera

No pass binds storage. radius adds what it reads, such as the cursor’s uniform, to the prepare and the composite. After an aim, a one-thread compute pass before the prepare reads the depth under the aimed point, moves the focus in a storage buffer and copies it into the settings.

Part
blur.settings Focus, aperture and limit, a uniform.
blur.output, blur.region Full-size rgba16float picture with depth of field and its region. Read each frame because resizing replaces it.
blur.part The half-resolution picture and its regions, for dof.circle.
blur.aim Pulls the focus to what is at a point of the picture.
  • Depth of field reads one depth per pixel. A translucent drawing, which writes no depth, and anything the depth leaves out take the radius of what is behind them.
  • Out-of-focus highlights are always discs.
  • Under cameras.flat, dof.thinLens has no meaning. Give radius a function of your own.