Skip to content

@typegpu/kitimpostors

Impostors

Capture mesh models into an atlas and draw lit, shadowed cards.

kit.impostors

module

impostors.create(root, pool, subject, { card, poses?, … }) → Atlas

impostors.define(name, options?) → Kind

rootborrows
the root
poolneeds
the mesh pool that holds the subject and the card
subjecttakes
a mesh, a model, or a list of pieces, each a mesh of pool
poses?needs
poses
Atlasreturns
an atlas you capture into an encoder and destroy
nametakes
a label for ids and WGSL, as table.define takes it
Kindreturns
a kind to give instances.create as kind
helpers
CARDImpostor
types
PieceSamplingAtlas
Optionscreate · 5 fields
card

Mesh

Selects the card mesh the atlas draws on: impostors.CARD added to the same pool.

poses?

Poses= none

Supplies the poses selected by pieces.

views?

number= 12

Sets the number of views per side of the atlas grid, from 2 to 16.

size?

number= 128

Sets the width and height of each view in texels.

hemisphere?

boolean= true

Limits capture directions to the upper hemisphere when true, for subjects viewed from above or the side, such as trees.

KindOptionsdefine · 5 fields
host?

NonNullable<InstanceKindOptions<H, D, M>['host']> & { readonly phase?: never }= none

Adds CPU-written fields, as in instances.define.

device?

NonNullable<InstanceKindOptions<H, D, M>['device']> & { readonly phase?: never }= none

Adds kernel-written fields beside the built-in phase field.

transform?

M= 'host'

Selects who writes position, rotation and scale, as in instances.define.

place?

(impostor: InstanceOf<H, D & { phase: d.U32 }, M>) => Placement= the card's position, rotation and scale

Returns the card's transform from custom data, such as a simulation buffer, as in instances.define.

visible?

(impostor: InstanceOf<H, D & { phase: d.U32 }, M>) => boolean= always shown

Hides the card in every pass, as in instances.define.

records
  • captureencoder

atlas.capture(encoder) once while loading, after what moves the subject's poses and before the passes that draw the atlas.

owns

5 textures ·3 buffers ·1 or 2 samplers ·5 to 8 pipelines ·mipmaps

The atlas's colours and its normals with depth, the multisampled targets that draw them, the subject's sphere, the extents it is fitted from and its pieces; the atlas's sampler, and the pool's the first time a capture binds its textures; four kernels that fit the sphere, a pipeline for each look of the subject's meshes, one-sided or two-sided, masked or not, that capture it; the mipmaps of each atlas texture.

lifecycle

destroy()[Symbol.dispose]

injects
  • definition

    The atlas that Impostor and kinds made by define read, for a renderer piped atlas.inject(sampling).

binds
  • 1 storage

    The subject's sphere, in the impostors' renderer's pipelines.

  • 1 storage

    The kind's device rows, where its surface reads the captured phase, beside the host rows its transform reads.

needs
  • meshPool

    As pool; fills accessors in its pipelines.

  • poses

    As options.poses; fills accessors in its pipelines.

Drag to compare six tree meshes on the left with one impostor card on the right. Select 'nearest' to see view changes, larger with fewer views; blended and parallax smooth transitions, and the atlas highlights sampled views.

Distant models can spend thousands of triangles on a few pixels. An impostor draws a model as one camera-facing card, using captures nearest the viewing direction. It receives lighting, casts shadows and supports culling. Its drawing cost depends on covered pixels rather than model triangles.

import {
import impostors
impostors
,
import instances
instances
} from '@typegpu/kit';
const
const trunk: meshPool.Mesh
trunk
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(shape: meshPool.Shape, encoder?: TgpuCommandEncoder): meshPool.Mesh (+1 overload)

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

add
(
import meshes
meshes
.
function cylinder({ radius, height, radialSegments, heightSegments, caps, }?: meshes.CylinderOptions): meshes.IndexedGeometry
cylinder
({
CylinderOptions.radius?: number | undefined
radius
: 0.14,
CylinderOptions.height?: number | undefined
height
: 1.8 }));
const
const crown: meshPool.Mesh
crown
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(shape: meshPool.Shape, encoder?: TgpuCommandEncoder): meshPool.Mesh (+1 overload)

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

add
(
import meshes
meshes
.
function icosphere({ segments, ...options }?: meshes.patches.IcosphereOptions & {
segments?: number;
}): meshes.IndexedGeometry<WgslStruct<{
position: Vec3f;
normal: Vec3f;
uv: Vec2f;
}>>
icosphere
({
IcosphereOptions.radius?: number | undefined
radius
: 1.6,
segments?: number | undefined
segments
: 2 }));
const
const card: meshPool.Mesh
card
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

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

add
(
import impostors
impostors
.
const CARD: meshPool.ModelData<{}>

This model is the card every atlas draws on: add it to the pool once, with pool.add(impostors.CARD).meshes[0], give the mesh to each create and instances of it to the cards. Count it in the pool's room, as in meshPool.roomFor([horse, impostors.CARD]).

CARD
).
Model.meshes: readonly meshPool.Mesh[]
meshes
[0]!; // once, for every atlas
const
const atlas: impostors.Atlas
atlas
=
import impostors
impostors
.
function create(root: TgpuRoot, pool: meshPool.MeshPool, subject: meshPool.Model | meshPool.Mesh | readonly impostors.Piece[], options: impostors.Options): impostors.Atlas

Creates an atlas that capture fills with views around subject. Each view stores textured colour, coverage, normal and depth. A model is captured whole; a piece list places each mesh. Back faces receive light from their side on double-sided meshes, and masked meshes apply their alpha cutoff.

Capture after shape baking in the same encoder or an earlier submission. Wait for model.ready to include model textures; otherwise, capture uses untextured factors. Invalid view counts or sizes, empty subjects, meshes or a card from another pool, invalid poses and excess texture layers throw.

@param ― pool the mesh pool that holds the subject and the card

@param ― subject a mesh, a model, or a list of pieces, each a mesh of pool

@returns ― an atlas you capture into an encoder and destroy

create
(
const root: TgpuRoot
root
,
const pool: meshPool.MeshPool<{}>
pool
,
[
{
Piece.mesh: meshPool.Mesh

Selects a mesh from the pool supplied to the capture.

mesh
:
const trunk: meshPool.Mesh
trunk
,
Piece.position?: Triple | undefined

Sets the mesh origin in subject space. Default [0, 0, 0].

position
: [0, 0.9, 0],
Piece.color?: Triple | undefined

Sets the mesh colour in linear colour space. Default the mesh material colour, which is white for shapes.

color
: [0.07, 0.04, 0.02] },
{
Piece.mesh: meshPool.Mesh

Selects a mesh from the pool supplied to the capture.

mesh
:
const crown: meshPool.Mesh
crown
,
Piece.position?: Triple | undefined

Sets the mesh origin in subject space. Default [0, 0, 0].

position
: [0, 3, 0],
Piece.color?: Triple | undefined

Sets the mesh colour in linear colour space. Default the mesh material colour, which is white for shapes.

color
: [0.11, 0.23, 0.02] },
],
{
Options.card: meshPool.Mesh

Selects the card mesh the atlas draws on: impostors.CARD added to the same pool. Atlases share one card.

card
},
);
// Capture while loading, after baking the shapes
const
const loading: TgpuCommandEncoder
loading
=
const root: TgpuRoot
root
.
TgpuRoot.createCommandEncoder(descriptor?: GPUCommandEncoderDescriptor): TgpuCommandEncoder

Creates a

TgpuCommandEncoder

for batching multiple render/compute passes (and draws within them) into a single submission.

@example

const encoder = root.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{ view: msaaTexture, resolveTarget: context }],
});
scenePipeline.with(pass).draw(vertexCount);
skyPipeline.with(pass).draw(3);
pass.end();
encoder.submit();

createCommandEncoder
();
const atlas: impostors.Atlas
atlas
.
Atlas.capture(encoder: TgpuCommandEncoder): void

Records the subject bounds, captured views and atlas mipmaps into encoder. The atlas stays blank until submission executes these commands. Capture again after poses or textures change. Throws after destroy.

capture
(
const loading: TgpuCommandEncoder
loading
);
const loading: TgpuCommandEncoder
loading
.
TgpuCommandEncoder.submit(): void

Finishes the recording and submits the resulting command buffer to the device queue

submit
();
const
const oaks: instances.Instances<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">>
oaks
=
import instances
instances
.
function create<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">>(root: TgpuRoot, pool: meshPool.MeshPool, options: instances.Options<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">>): instances.Instances<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">> (+1 overload)

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

@param ― pool the mesh pool whose meshes it draws

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

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

create
(
const root: TgpuRoot
root
,
const pool: meshPool.MeshPool<{}>
pool
, {
Options<Kind<"Impostor", {}, { phase: U32; }, "host">>.kind?: instances.Kind<"Impostor", {}, {
phase: U32;
}, "host"> | undefined

Selects a kind from instances.define. Default a plain kind with a transform and material.

kind
:
import impostors
impostors
.
const Impostor: instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">

This impostor kind stores its transform and material on the host and its phase on the device.

Impostor
,
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
: 20_000 });
const oaks: instances.Instances<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">>
oaks
.
Instances<Kind<"Impostor", {}, { phase: U32; }, "host">>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: instances.Values<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">> | ((mesh: meshPool.Mesh, index: number) => instances.Values<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">>) | undefined): IdOf<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">>

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

add
(
const card: meshPool.Mesh
card
, {
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [12, 0, -40],
scale?: number | ArrayLike<number> | InferInput<Vec3f> | undefined

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

scale
: 1.2,
roughness?: number | undefined
roughness
: 0.9 });
const
const renderer: instances.Renderer
renderer
=
const oaks: instances.Instances<instances.Kind<"Impostor", {}, {
phase: U32;
}, "host">>
oaks
.
Instances<Kind<"Impostor", {}, { phase: U32; }, "host">>.renderer(options?: instances.RendererOptions): instances.Renderer

Creates a renderer for colour and shadow pass descriptions, lit by the lighting each description gives through beneath unless options.light replaces it. Use pipe to fill resources read by kind callbacks.

renderer
().
Renderer.pipe(fill: Inject): instances.Renderer

Returns this renderer with fill applied to every pipeline and kernel it builds. The fill runs after lighting and before the pass camera. Use it for resources read by kind callbacks, as in school.renderer().pipe(water.inject()).

pipe
(
const atlas: impostors.Atlas
atlas
.
Atlas.inject(sampling?: impostors.Sampling): Inject

Fills atlas resources for pipelines using Impostor or a kind made by define, read with sampling. Default 'blended'.

inject
());
const
const drawn: passes.Drawn
drawn
=
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.add<{
color: v4f;
}, {
position: v4f;
world: v3f;
normal: v3f;
uv: v2f;
uv1: v2f;
row: number;
}>(this: passes.Passes<passes.Fed<{
color: v4f;
}, {
position: v4f;
world: v3f;
normal: v3f;
uv: v2f;
uv1: v2f;
row: number;
}>>, drawing: passes.Compound<{
color: v4f;
}, {
position: v4f;
world: v3f;
normal: v3f;
uv: v2f;
uv1: v2f;
row: number;
}>): passes.Drawn (+1 overload)

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

add
(
const renderer: instances.Renderer
renderer
);
const
const cast: passes.Drawn
cast
=
const light: lighting.LightingOf<{
readonly shadow: {};
}>
light
.
shadow: Shadow | (Cascades & Shadow)

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

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

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

add
(
const renderer: instances.Renderer
renderer
);

The renderer flushes, culls, casts shadows and draws like an instances renderer. Opaque impostors require pass descriptions with depth.

  • Capture a mesh, model or list of pieces. Each piece references a pool mesh with position, rotation, scale and a colour that multiplies its textures.
  • Every atlas draws on one card: add impostors.CARD to the pool once and give its mesh to each impostors.create, which allocates the atlas and pipelines. atlas.capture(encoder) records the bounding-sphere kernels, one pass per view and the atlas’s mipmaps. The atlas stays blank until you submit that encoder.
  • Views form a grid with views entries per side over the upper hemisphere. Use hemisphere: false to cover the whole sphere. Each view stores colour, coverage, normal and depth.
  • Captures preserve mesh rendering settings. Double-sided meshes show back faces lit from that side. Masked meshes keep their cutouts.
  • Capture after shape baking, either in the same encoder or after submitting the bake encoder. Await model.ready first to include textures. Recapture when the subject changes.
  • Creation throws if views is outside 2 to 16, size is not a power of two from 16 to 512, the subject has no meshes, or a mesh or the card belongs to another pool.

To capture animation, give a piece a pose from options.poses, a set of poses. An array of poses creates one atlas phase per pose. A row’s phase selects which to draw. The stampede captures six phases of a horse gallop:

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

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

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

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

@binds ― 1 storage: positionAt, the positions

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

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

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

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

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

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

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

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

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

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

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

@param ― items what the pool will hold

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

roomFor
([
const horse: gltf.ModelData<never>
horse
,
import impostors
impostors
.
const CARD: meshPool.ModelData<{}>

This model is the card every atlas draws on: add it to the pool once, with pool.add(impostors.CARD).meshes[0], give the mesh to each create and instances of it to the cards. Count it in the pool's room, as in meshPool.roomFor([horse, impostors.CARD]).

CARD
])); // room for the card
const
const body: meshPool.Model
body
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

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

add
(
const horse: gltf.ModelData<never>
horse
);
const
const card: meshPool.Mesh
card
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

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

add
(
import impostors
impostors
.
const CARD: meshPool.ModelData<{}>

This model is the card every atlas draws on: add it to the pool once, with pool.add(impostors.CARD).meshes[0], give the mesh to each create and instances of it to the cards. Count it in the pool's room, as in meshPool.roomFor([horse, impostors.CARD]).

CARD
).
Model.meshes: readonly meshPool.Mesh[]
meshes
[0]!;
const
const gallop: poses.ClipData
gallop
=
import poses
poses
.
function clip(file: {
readonly clips: readonly poses.ClipData[];
}, name: string): poses.ClipData
export clip

Returns the clip named name among a file's clips. Throws listing the names the file has.

@param ― file what gltf.read returns, or anything holding clips

@param ― name the clip's name in the file

@returns ― the clip, as the file holds it

clip
(
const horse: gltf.ModelData<never>
horse
, 'Gallop');
const
const figures: poses.Poses<"gallop", never>
figures
=
import poses
poses
.
function create<"gallop", never>(root: TgpuRoot, skin: poses.SkinData, clips: Readonly<Record<"gallop", poses.ClipData | readonly poses.ClipData[]>>, options: poses.Options<never>): poses.Poses<"gallop", never>

Creates poses of skin that play named clips, for example { idle: general.clips[6], walk }. An entry that is an array of clips is a line, its clips sampled at one phase and mixed by mix. It bakes clips at hz keys per second and blends between adjacent keys during playback. Call animate each frame to compute joints in model space before drawing instances. The skin must have 1 to 256 joints in parent-first order, with 16-float inverse binds. Invalid joint data, capacity, bake rate, clips, lines of fewer than two clips, more than three layers, a layer named base or a layer joint the skin lacks throw.

@param ― skin the joints, as gltf.read returns them in a model's skin

@param ― clips the clips and lines its poses play, by the names add and play take

@returns ― poses you animate every frame and destroy

create
(
const root: TgpuRoot
root
,
const horse: gltf.ModelData<never>
horse
.
ModelData<never>.skin?: poses.SkinData | undefined

Provides the skin used by meshes in the default scene. When present, every mesh has joints and weights. An unskinned mesh follows the joint it is attached to.

skin
!, {
gallop: poses.ClipData | readonly poses.ClipData[]
gallop
}, {
Options<L extends string = string>.capacity: number

Limits the pose count to an integer from 1 to 65 535.

capacity
: 6 });
const
const phases: poses.Id[]
phases
=
var Array: ArrayConstructor
Array
.
ArrayConstructor.from<unknown, poses.Id>(iterable: Iterable<unknown> | ArrayLike<unknown>, mapfn: (v: unknown, k: number) => poses.Id, thisArg?: any): poses.Id[] (+3 overloads)

Creates an array from an iterable object.

@param ― iterable An iterable object to convert to an array.

@param ― mapfn A mapping function to call on every element of the array.

@param ― thisArg Value of 'this' used to invoke the mapfn.

from
(
{
ArrayLike<T>.length: number
length
: 6 },
(
_: unknown
_
,
i: number
i
) =>
const figures: poses.Poses<"gallop", never>
figures
.
Poses<"gallop", never>.add(clip: "gallop", time: number, play?: poses.Play<never> | undefined): poses.Id

Adds a pose playing clip on its base from time on the clock passed to animate. Its layers play nothing. Ignores layer, weight and fade. Throws if the set is full or the clip was not supplied at creation.

add
('gallop', 0, {
Play<never>.from?: number | undefined

Sets the starting offset within the clip or cycle in seconds. Default 0.

from
: (
const gallop: poses.ClipData
gallop
.
ClipData.duration: number

Gives the time of the last key in seconds.

duration
*
i: number
i
) / 6,
Play<never>.speed?: number | undefined

Scales playback speed. A value of 1 uses the original speed, 0 pauses and negative values play backwards. Default 1.

speed
: 0 }), // held still
);
const
const atlas: impostors.Atlas
atlas
=
import impostors
impostors
.
function create(root: TgpuRoot, pool: meshPool.MeshPool, subject: meshPool.Model | meshPool.Mesh | readonly impostors.Piece[], options: impostors.Options): impostors.Atlas

Creates an atlas that capture fills with views around subject. Each view stores textured colour, coverage, normal and depth. A model is captured whole; a piece list places each mesh. Back faces receive light from their side on double-sided meshes, and masked meshes apply their alpha cutoff.

Capture after shape baking in the same encoder or an earlier submission. Wait for model.ready to include model textures; otherwise, capture uses untextured factors. Invalid view counts or sizes, empty subjects, meshes or a card from another pool, invalid poses and excess texture layers throw.

@param ― pool the mesh pool that holds the subject and the card

@param ― subject a mesh, a model, or a list of pieces, each a mesh of pool

@returns ― an atlas you capture into an encoder and destroy

create
(
const root: TgpuRoot
root
,
const pool: meshPool.MeshPool<{}>
pool
,
const body: meshPool.Model
body
.
Model.meshes: readonly meshPool.Mesh[]
meshes
.
ReadonlyArray<Mesh>.map<{
mesh: meshPool.Mesh;
pose: poses.Id[];
}>(callbackfn: (value: meshPool.Mesh, index: number, array: readonly meshPool.Mesh[]) => {
mesh: meshPool.Mesh;
pose: poses.Id[];
}, thisArg?: any): {
mesh: meshPool.Mesh;
pose: poses.Id[];
}[]

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
((
mesh: meshPool.Mesh
mesh
) => ({
mesh: meshPool.Mesh
mesh
,
pose: poses.Id[]
pose
:
const phases: poses.Id[]
phases
})),
{
Options.card: meshPool.Mesh

Selects the card mesh the atlas draws on: impostors.CARD added to the same pool. Atlases share one card.

card
,
Options.poses?: poses.Poses<string, string> | undefined

Supplies the poses selected by pieces. Animate them before capture, in the same encoder or one submitted earlier. Default none.

poses
:
const figures: poses.Poses<"gallop", never>
figures
,
Options.views?: number | undefined

Sets the number of views per side of the atlas grid, from 2 to 16. Default 12.

views
: 5,
Options.size?: number | undefined

Sets the width and height of each view in texels. It must be a power of two from 16 to 512. Default 128.

size
: 64 },
);
const
const loading: TgpuCommandEncoder
loading
=
const root: TgpuRoot
root
.
TgpuRoot.createCommandEncoder(descriptor?: GPUCommandEncoderDescriptor): TgpuCommandEncoder

Creates a

TgpuCommandEncoder

for batching multiple render/compute passes (and draws within them) into a single submission.

@example

const encoder = root.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{ view: msaaTexture, resolveTarget: context }],
});
scenePipeline.with(pass).draw(vertexCount);
skyPipeline.with(pass).draw(3);
pass.end();
encoder.submit();

createCommandEncoder
();
const
const posing: TgpuComputePass
posing
=
const loading: TgpuCommandEncoder
loading
.
TgpuCommandEncoder.beginComputePass(descriptor?: TgpuComputePassDescriptor): TgpuComputePass

Begins recording a compute pass

beginComputePass
();
const figures: poses.Poses<"gallop", never>
figures
.
Poses<"gallop", never>.animate(pass: TgpuComputePass, time: number): void

Uploads changes and computes every pose joint at time, with one workgroup per pose. Record this once per frame in a compute pass before drawing, also while paused, so last frame's joints stay one frame old.

animate
(
const posing: TgpuComputePass
posing
, 0); // pose before capture in the same encoder
const posing: TgpuComputePass
posing
.
TgpuComputePass.end(): void

Completes the recording of this compute pass

end
();
const atlas: impostors.Atlas
atlas
.
Atlas.capture(encoder: TgpuCommandEncoder): void

Records the subject bounds, captured views and atlas mipmaps into encoder. The atlas stays blank until submission executes these commands. Capture again after poses or textures change. Throws after destroy.

capture
(
const loading: TgpuCommandEncoder
loading
);
const loading: TgpuCommandEncoder
loading
.
TgpuCommandEncoder.submit(): void

Finishes the recording and submits the resulting command buffer to the device queue

submit
();
  • Capture bounds include posed vertices from every phase.
  • Poses must be live and meshes must have joints. All pose arrays must have the same nonzero length. A single pose or unposed piece repeats across phases.
  • The card faces the camera through the subject centre. Its bounds fit the projected subject box to reduce unused pixels.
  • Cards use instance fields. Row color multiplies atlas colour. Roughness, metallic and emission come from the row.
  • Each pixel shades at the captured subject point, so light, fog and shadows use that position. The shadow pass writes its depth 12 atlas texels farther from the light, so a card doesn’t shadow itself where its views disagree.
  • The kind’s coverage reads the captured coverage. With multisampling, coverage selects a fraction of pixel samples. Without it, pixels below half coverage are omitted.
  • Cards that overlap, such as a forest, draw best with prepass: true on their renderer: depth first, then each visible pixel shades once. With blend: 'translucent', one draw composites row opacity times captured coverage over the background without writing depth.
  • cull: true tests the sphere around the captured subject.

Use impostors.define to add kind fields and choose who writes transforms, as with instances.define. Device fields also include phase, indexed from 0. Values beyond the atlas phase count select the last phase. Kernels can move cards and select phases:

const
const Walker: instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">
Walker
=
import impostors
impostors
.
function define<"Walker", {
member: d.U32;
}, {}, "device">(name: "Walker", options?: impostors.KindOptions<{
member: d.U32;
}, {}, "device">): instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">

Defines an instance kind that draws an atlas with custom host and device fields. Its device fields include a zero-based phase, initially 0. Values beyond the captured range select the last phase. Inject the atlas into the renderer. Instance colour multiplies atlas colour; roughness, metallic and emission control shading as for a mesh.

@param ― name a label for ids and WGSL, as table.define takes it

@returns ― a kind to give instances.create as kind

define
('Walker', {
KindOptions<{ member: U32; }, {}, "device">.transform?: "device" | undefined

Selects who writes position, rotation and scale, as in instances.define. Default 'host'.

transform
: 'device',
KindOptions<{ member: U32; }, {}, "device">.host?: ({
member: d.U32;
} & {
readonly rotation?: "is a field of every instance" | undefined;
readonly position?: "is a field of every instance" | undefined;
readonly scale?: "is a field of every instance" | undefined;
readonly color?: "is a field of every instance" | undefined;
readonly roughness?: "is a field of every instance" | undefined;
readonly emission?: "is a field of every instance" | undefined;
readonly metallic?: "is a field of every instance" | undefined;
readonly mesh?: "is a field of every instance" | undefined;
readonly opacity?: "is a field of every instance" | undefined;
} & {
...;
} & {
...;
}) | undefined

Adds CPU-written fields, as in instances.define. Default none.

host
: {
member: d.U32
member
:
import d
d
.
const u32: d.U32
export u32

A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

@example const value = u32(); // 0

@example const value = u32(7); // 7

@example const value = u32(3.14); // 3

@example const value = u32(-1); // 4294967295

@example const value = u32(-3.1); // 0

u32
},
});
const
const cards: instances.Instances<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">>
cards
=
import instances
instances
.
function create<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">>(root: TgpuRoot, pool: meshPool.MeshPool, options: instances.Options<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">>): instances.Instances<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">> (+1 overload)

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

@param ― pool the mesh pool whose meshes it draws

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

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

create
(
const root: TgpuRoot
root
,
const pool: meshPool.MeshPool<{}>
pool
, {
Options<Kind<"Walker", { member: U32; }, { phase: d.U32; }, "device">>.kind?: instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device"> | undefined

Selects a kind from instances.define. Default a plain kind with a transform and material.

kind
:
const Walker: instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">
Walker
,
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
: 1024 });
const cards: instances.Instances<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">>
cards
.
Instances<Kind<"Walker", { member: U32; }, { phase: d.U32; }, "device">>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: instances.Values<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">> | ((mesh: meshPool.Mesh, index: number) => instances.Values<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">>) | undefined): IdOf<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">>

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

add
(
const card: meshPool.Mesh
card
, {
member: number
member
: 0 });
const cards: instances.Instances<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">>
cards
.
Instances<Kind<"Walker", { member: U32; }, { phase: d.U32; }, "device">>.flush(encoder?: TgpuCommandEncoder): void

Uploads changed rows. When instances are added or removed, it also uploads live rows per mesh and updates draw counts. Call this once per frame before drawing. HistoryInstances take the frame's encoder and throw without it.

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

@module ― typegpu

tgpu
.
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [64],
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly clipDistances: d.BuiltinClipDistances;
readonly position: d.BuiltinPosition;
readonly frontFacing: d.BuiltinFrontFacing;
readonly fragDepth: d.BuiltinFragDepth;
readonly primitiveIndex: d.BuiltinPrimitiveIndex;
readonly sampleIndex: d.BuiltinSampleIndex;
... 11 more ...;
readonly numSubgroups: d.BuiltinNumSubgroups;
}
export builtin
builtin
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
},
})(({
gid: d.v3u
gid
}) => {
'use gpu';
if (
gid: d.v3u
gid
.
v3u.x: number
x
>=
const Walker: instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">
Walker
.
span: TgpuGenericFn
() => number

Returns the highest live slot plus one.

span
() || !
const Walker: instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">
Walker
.
alive: TgpuGenericFn
(i: number) => boolean

Tests whether slot i holds a row. Returns false beyond the span.

alive
(
gid: d.v3u
gid
.
v3u.x: number
x
)) return;
const Walker: instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">
Walker
.
device: TgpuAccessor<Rows<DeviceOf<{
phase: d.U32;
}, "device">>>

Provides GPU-written rows with read-write access in compute pipelines and read-only access in render pipelines.

device
.
TgpuAccessor<Rows<DeviceOf<{ phase: d.U32; }, "device">>>.$: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
phase: number;
}[]
$
[
gid: d.v3u
gid
.
v3u.x: number
x
]!.
position: d.v3f
position
=
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(
const Walker: instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">
Walker
.
member: Getter
(i: number) => number
member
(
gid: d.v3u
gid
.
v3u.x: number
x
), 0, 0);
const Walker: instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">
Walker
.
device: TgpuAccessor<Rows<DeviceOf<{
phase: d.U32;
}, "device">>>

Provides GPU-written rows with read-write access in compute pipelines and read-only access in render pipelines.

device
.
TgpuAccessor<Rows<DeviceOf<{ phase: d.U32; }, "device">>>.$: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
phase: number;
}[]
$
[
gid: d.v3u
gid
.
v3u.x: number
x
]!.
phase: number
phase
= 1;
});
const
const moving: TgpuComputePipeline
moving
=
const root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const cards: instances.Instances<instances.Kind<"Walker", {
member: d.U32;
}, {
phase: d.U32;
}, "device">>
cards
.
function inject(): Inject

Fills the definition's accessors with this table's buffers.

inject
()).
WithBinding.createComputePipeline<{
gid: d.BuiltinGlobalInvocationId;
}>(descriptor: TgpuComputePipeline.Descriptor<{
gid: d.BuiltinGlobalInvocationId;
}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
compute
:
const move: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
move
});

To read a simulation’s own buffer instead, give define a place that returns the card’s transform and a visible that hides a card in every pass, as for instances. The card faces the camera from that transform, and culling follows it.

Atlas sampling determines pixel cost. atlas.inject(sampling) defaults to 'blended':

Renderer is piped Reads Samples a pixel
atlas.inject('nearest') Selects the nearest view, switching as the camera turns. 2
atlas.inject() Blends the three nearest views, weighted by direction. 6
atlas.inject('parallax') Blends three views with depth-based ray correction for close viewing. 18

The quiver trees draw distant trees and boulders as impostors.

Use from and to to draw near cards with 'parallax' and far cards with 'nearest'. Both renderers can share an atlas.

stampede4 096 horses under sun cascades, skinned nearby and cards farther off, easing into a gallop when frightened.
Hover a horse to see its name, click it to follow, or click the ground for a thunderclap. Change the hand-off distance or tint cards to see where impostors replace skinned meshes.

The atlas has six gallop phases and four walk phases, each with 25 views at 64 texels. Each horse selects the phase matching its stride. Hovered and followed horses keep skinned meshes. Renderers cull horses outside the camera and sun views.

What
an atlas 8 bytes per texel per view per phase, plus one third for mipmaps. Defaults use 25 MB; 6 views, 6 phases and size 64 use 9 MB.
capturing Four samples at 12 bytes per texel for one view, retained until destroy for recapture. Size 128 uses 786 KB.

Destroy an unused atlas with atlas.destroy(). The card stays in the pool for other atlases. After destruction, capture and inject throw The atlas was destroyed.

  • Phases do not interpolate. Capture phases for deformation too. A one-phase tree stops swaying when it becomes a card.
  • views² × phases must fit maxTextureArrayLayers, at least 256 on WebGPU.
  • The atlas keeps colour and normals, not the meshes’ roughness, metallic or emission.
  • A hemisphere atlas seen from below shows its lowest views.
  • Each sampling mode needs separate colour, depth and shadow pipelines.
  • Small far cards show their edges as dots under 4× multisampling, where few samples are covered.
  • Use meshes for low-triangle models. Atlas sampling can cost more than their vertex work.