Skip to content

@typegpu/kitshadows

Shadows

The sun's shadow, fixed or in cascades fitted to the view, and shadows of point and spot lights.

kit.shadows

shader
GPU functions
cascadeOfcascadedfilteredshadowed
schemas
CascadeAt
types
CascadesFittedShadowOptionsFixedShadowOptionsShadowShadowMapOptionsShadowOptionsShadowRecordOptionsSunShadowPointShadowPointShadowOptionsPointShadowRecordOptions
reads
  • lighting

    Its inject() fills them in the calling pipeline.

Set 256 texels and compare one, four or nine shadow comparisons. The map spans 9 units around a 0.3-unit-wide pillar; extra comparisons soften its stepped shadow.

Without shadows, a floating block can appear to rest on the floor. A lighting with a shadow group creates light.shadow, which records depth from the sun. Add each caster to it as you add drawings to the scene, and record it each frame before the passes that shade:

const
const light: lighting.LightingOf<{
readonly settings: {
readonly sunDirection: [-0.6, 1, -0.7];
};
readonly shadow: {
readonly extent: 9;
};
}>
light
=
import lighting
lighting
.
function create<{
readonly settings: {
readonly sunDirection: [-0.6, 1, -0.7];
};
readonly shadow: {
readonly extent: 9;
};
}>(root: TgpuRoot, options?: ({
readonly settings: {
readonly sunDirection: [-0.6, 1, -0.7];
};
readonly shadow: {
readonly extent: 9;
};
} & lighting.Options & {
readonly settings: {
readonly sunDirection: [-0.6, 1, -0.7];
};
readonly shadow: {
readonly extent: 9;
};
}) | undefined): lighting.LightingOf<{
readonly settings: {
readonly sunDirection: [-0.6, 1, -0.7];
};
readonly shadow: {
readonly extent: 9;
};
}>

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
settings: {
readonly sunDirection: [-0.6, 1, -0.7];
} & ((Partial<InferInput<WgslStruct<{
sunDirection: Vec3f;
sunIntensity: F32;
sunColor: Vec3f;
skyIntensity: F32;
skyTop: Vec3f;
fogDensity: F32;
skyBottom: Vec3f;
fogFalloff: F32;
fogColor: Vec3f;
skyReflection: F32;
fogStart: F32;
}>>> | undefined) & {
readonly sunDirection: [-0.6, 1, -0.7];
})

Sets initial sun, sky and fog values over defaults. Default defaults, which has no fog.

settings
: {
sunDirection: [-0.6, 1, -0.7]
sunDirection
: [-0.6, 1, -0.7] },
shadow: {
readonly extent: 9;
} & FixedShadowOptions & {
readonly extent: 9;
}

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

shadow
: {
extent: 9

Sets the width of the square covered by the map in world units. The square is centred on the point passed to follow. Change it with setExtent. A shadow with an eye takes none. Default 20.

extent
: 9 },
});
const
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
=
import passes
passes
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}, {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>(root: TgpuRoot, screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}>, writes: {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}, options?: passes.TargetOptions<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}> | 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 depth: {
readonly format: "depth24plus";
};
}>
screen
, {
hdr: typeof passes.color
hdr
:
import passes
passes
.
const color: typeof passes.color

Marks the target that receives each renderer's colour.

color
,
depth: typeof passes.depth
depth
:
import passes
passes
.
const depth: typeof passes.depth

Marks the depth target.

depth
})
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.pipe(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

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

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

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

inject
())
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.beneath(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

Adds fill after the fills given to earlier beneath calls and returns a description of the same passes. A drawing's own fills take precedence over these, and piped fills over both, so beneath supplies defaults such as a lighting. A compound drawing that passes its own from to pipeline gets them by starting from with inject().

beneath
(
const light: lighting.LightingOf<{
readonly settings: {
readonly sunDirection: [-0.6, 1, -0.7];
};
readonly shadow: {
readonly extent: 9;
};
}>
light
.
Lighting.inject(): Inject

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

inject
());
const
const renderer: instances.Renderer
renderer
=
const things: instances.Instances<Plain>
things
.
Instances<Plain>.renderer(options?: instances.RendererOptions): instances.Renderer

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

renderer
();
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 settings: {
readonly sunDirection: [-0.6, 1, -0.7];
};
readonly shadow: {
readonly extent: 9;
};
}>
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
);
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 things: instances.Instances<Plain>
things
.
Instances<Plain>.flush(encoder?: TgpuCommandEncoder): void

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

flush
();
const light: lighting.LightingOf<{
readonly settings: {
readonly sunDirection: [-0.6, 1, -0.7];
};
readonly shadow: {
readonly extent: 9;
};
}>
light
.
shadow: Shadow | (Cascades & Shadow)

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

shadow
.
SunShadow.record(frame: frames.Frame | TgpuCommandEncoder, casters: passes.Drawn | readonly passes.Drawn[], options?: ShadowRecordOptions): void

Clears the map in a depth pass per cascade that redraw picks and draws the casters built by add. Call this while recording each frame, before drawing shaded surfaces. When disabled, it draws nothing and makes every point sunlit. A frame that draws no cascade writes a timer's timestamps from an empty compute pass.

record
(
frame: frames.Frame
frame
,
const cast: passes.Drawn
cast
);
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.record(frame: frames.Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}> | undefined): void

Begins a pass, draws drawn or each drawing in a list in order, then ends the pass. Reuse the list to avoid allocating each frame. Call begin yourself when you need to choose drawings per frame. Records nothing while enabled is false.

record
(
frame: frames.Frame
frame
,
const drawn: passes.Drawn
drawn
);
}),
);
  • light.shadow.add(renderer) builds depth pipelines with bias against self-shadowing. cast.destroy() removes the caster and frees them.
  • await light.shadow.initAsync() while loading compiles every caster added.
  • The map covers a square extent world units wide, centred on center. Outside it, surfaces are lit. Wider coverage spreads each texel over more ground: Blockfall covers its well with 2048 texels across 16 units, about 1/128 of a block per texel.
  • The shadow follows sunDirection. A direction written to light.settings turns it at its next record. Call aim(direction) to cast from elsewhere.
  • Time the pass with a profiler as record(frame, casters, { timestampWrites: shadow.writes() }). A frame that draws no cascade writes the timer from an empty compute pass.
  • A layer of the map takes size² × 2 bytes in depth16unorm and size² × 4 in the other formats.
stampede4 096 horses under sun cascades, skinned nearby and cards farther off, easing into a gallop when frightened.
Click a horse to ride beside it. Its legs cast sharp shadows from the nearest cascade while the trees on the far slopes cast theirs from the last.

One square sharp enough for a horse’s legs covers a few dozen metres. Give the shadow an eye and it splits the view into cascades, each a layer of one map. The nearest covers the first metres in front of the camera, and each later one covers more ground with the same texels. record fits them to the camera each frame:

const
const view: cameras.OrbitCamera
view
=
import cameras
cameras
.
function orbit(root: TgpuRoot, options: cameras.OrbitOptions): cameras.OrbitCamera

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

@returns ― a camera you update every frame and destroy

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

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

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

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

target
: [0, 1, 0],
OrbitOptions.distance: number

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

distance
: 12 });
const
const light: lighting.LightingOf<{
readonly settings: {
readonly sunDirection: [0.6, 0.35, 0.3];
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
};
}>
light
=
import lighting
lighting
.
function create<{
readonly settings: {
readonly sunDirection: [0.6, 0.35, 0.3];
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
};
}>(root: TgpuRoot, options?: ({
readonly settings: {
readonly sunDirection: [0.6, 0.35, 0.3];
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
};
} & lighting.Options & {
readonly settings: {
readonly sunDirection: [0.6, 0.35, 0.3];
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
};
}) | undefined): lighting.LightingOf<...>

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
settings: {
readonly sunDirection: [0.6, 0.35, 0.3];
} & ((Partial<InferInput<WgslStruct<{
sunDirection: Vec3f;
sunIntensity: F32;
sunColor: Vec3f;
skyIntensity: F32;
skyTop: Vec3f;
fogDensity: F32;
skyBottom: Vec3f;
fogFalloff: F32;
fogColor: Vec3f;
skyReflection: F32;
fogStart: F32;
}>>> | undefined) & {
readonly sunDirection: [0.6, 0.35, 0.3];
})

Sets initial sun, sky and fog values over defaults. Default defaults, which has no fog.

settings
: {
sunDirection: [0.6, 0.35, 0.3]
sunDirection
: [0.6, 0.35, 0.3] },
shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
} & FittedShadowOptions & {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
}

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

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

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

eye
:
const view: cameras.OrbitCamera
view
,
cascades: 3

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

cascades
: 3,
far: 120

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

far
: 120 },
});
const
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
=
import passes
passes
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}, {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>(root: TgpuRoot, screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}>, writes: {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}, options?: passes.TargetOptions<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}> | 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 depth: {
readonly format: "depth24plus";
};
}>
screen
, {
hdr: typeof passes.color
hdr
:
import passes
passes
.
const color: typeof passes.color

Marks the target that receives each renderer's colour.

color
,
depth: typeof passes.depth
depth
:
import passes
passes
.
const depth: typeof passes.depth

Marks the depth target.

depth
})
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.pipe(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

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

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

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

inject
())
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.beneath(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

Adds fill after the fills given to earlier beneath calls and returns a description of the same passes. A drawing's own fills take precedence over these, and piped fills over both, so beneath supplies defaults such as a lighting. A compound drawing that passes its own from to pipeline gets them by starting from with inject().

beneath
(
const light: lighting.LightingOf<{
readonly settings: {
readonly sunDirection: [0.6, 0.35, 0.3];
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
};
}>
light
.
Lighting.inject(): Inject

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

inject
());
const
const renderer: instances.Renderer
renderer
=
const things: instances.Instances<Plain>
things
.
Instances<Plain>.renderer(options?: instances.RendererOptions): instances.Renderer

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

renderer
();
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 settings: {
readonly sunDirection: [0.6, 0.35, 0.3];
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
};
}>
light
.
shadow: Cascades | (Shadow & Cascades)

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
);
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 view: cameras.OrbitCamera
view
.
OrbitCamera.update(dt: number): void

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

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

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

dt
);
const things: instances.Instances<Plain>
things
.
Instances<Plain>.flush(encoder?: TgpuCommandEncoder): void

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

flush
();
const light: lighting.LightingOf<{
readonly settings: {
readonly sunDirection: [0.6, 0.35, 0.3];
};
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
readonly far: 120;
};
}>
light
.
shadow: Cascades | (Shadow & Cascades)

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

shadow
.
SunShadow.record(frame: frames.Frame | TgpuCommandEncoder, casters: passes.Drawn | readonly passes.Drawn[], options?: ShadowRecordOptions): void

Clears the map in a depth pass per cascade that redraw picks and draws the casters built by add. Call this while recording each frame, before drawing shaded surfaces. When disabled, it draws nothing and makes every point sunlit. A frame that draws no cascade writes a timer's timestamps from an empty compute pass.

record
(
frame: frames.Frame
frame
,
const cast: passes.Drawn
cast
); // a depth pass per cascade
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.record(frame: frames.Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}> | undefined): void

Begins a pass, draws drawn or each drawing in a list in order, then ends the pass. Reuse the list to avoid allocating each frame. Call begin yourself when you need to choose drawings per frame. Records nothing while enabled is false.

record
(
frame: frames.Frame
frame
,
const drawn: passes.Drawn
drawn
);
}),
);
  • eye takes a camera with a CPU view: cameras.orbit, cameras.flat or cameras.custom. Update it before record.
  • The split after cascade k lies at far × (k / cascades)², 11% and 44% of far for three. splits: [0.08, 0.3] places them as fractions of far instead.
  • record fits a sphere around each slice of the view and snaps its centre to whole texels. The sphere keeps its size as the camera turns, so shadow edges hold still.
  • reach is how far each cascade reaches towards the sun beyond its slice. A caster farther towards the sun casts nothing.
  • cascades, far and splits need an eye, and extent and center take none. Type checking refuses the other combinations, and create throws for them. cascades: 1 fits one map to the view.

Casters draw once per cascade through the pipelines a single map builds. An instances renderer with cull: true culls every cascade in one pair of dispatches. Shading picks the first cascade whose square holds the point and switches cascades without a blend. A finished look for model scenes sizes two cascades for a scene of glTF models.

record clears and draws every cascade each frame. Casters that hold still under a still sun draw the same depth again. Pass redraw to choose the cascades that draw this frame. The others keep the layer they drew last:

// Set when a caster moves, from your own game state
let
let stirred: boolean
stirred
= true;
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 light: lighting.LightingOf<{
readonly shadow: {
readonly extent: 16;
};
}>
light
.
shadow: Shadow | (Cascades & Shadow)

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

shadow
.
SunShadow.record(frame: frames.Frame | TgpuCommandEncoder, casters: Drawn | readonly Drawn[], options?: ShadowRecordOptions): void

Clears the map in a depth pass per cascade that redraw picks and draws the casters built by add. Call this while recording each frame, before drawing shaded surfaces. When disabled, it draws nothing and makes every point sunlit. A frame that draws no cascade writes a timer's timestamps from an empty compute pass.

record
(
frame: frames.Frame
frame
,
const cast: Drawn
cast
, {
ShadowRecordOptions.redraw?: ((cascade: number) => boolean) | undefined

Chooses which cascades draw their layer this frame, by index from the nearest. A cascade it skips keeps the layer it drew last, so still casters under a still sun can skip. A cascade still draws on the first record and when its place changes, as a fitted shadow's does when the eye moves, after aim, follow, setExtent, setFar and resize, and after a frame with enabled false, so () => false draws casters that never move once. Default every cascade.

redraw
: () =>
let stirred: boolean
stirred
});
let stirred: boolean
stirred
= false;
});

A cascade draws anyway the first time record runs and whenever its place changes: when a fitted shadow’s eye moves, when the sun turns, after aim, follow, setExtent, setFar or resize, and after a frame with the shadow off. A skipped cascade shows casters where they stood when it last drew.

Casters that never move, such as terrain or a reef, need no state of their own. A redraw that always answers false draws the shadow once and again only when its place changes:

const
const still: {
redraw: () => boolean;
}
still
= {
redraw: () => boolean
redraw
: () => false };
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 light: lighting.LightingOf<{
readonly shadow: {
readonly extent: 16;
};
}>
light
.
shadow: Shadow | (Cascades & Shadow)

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

shadow
.
SunShadow.record(frame: frames.Frame | TgpuCommandEncoder, casters: Drawn | readonly Drawn[], options?: ShadowRecordOptions): void

Clears the map in a depth pass per cascade that redraw picks and draws the casters built by add. Call this while recording each frame, before drawing shaded surfaces. When disabled, it draws nothing and makes every point sunlit. A frame that draws no cascade writes a timer's timestamps from an empty compute pass.

record
(
frame: frames.Frame
frame
,
const cast: Drawn
cast
,
const still: {
redraw: () => boolean;
}
still
);
});

Add a custom caster as a drawing to light.shadow. Supply a Vertex with a clip-space position, an at function producing it and a draw function. The shadow fills the kit’s cameras.current with the sun’s view, so scene vertex functions cast without changes:

const
const statue: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
statue
=
import meshes
meshes
.
unindexed<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>(g: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>): meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
export unindexed
unindexed
(
import meshes
meshes
.
function box({ width, height, depth, widthSegments, heightSegments, depthSegments, }?: meshes.BoxOptions): meshes.IndexedGeometry
box
());
const
const Corner: d.WgslStruct<{
position: d.Vec4f;
}>
Corner
=
import d
d
.
struct<{
position: d.Vec4f;
}>(props: {
position: d.Vec4f;
}): d.WgslStruct<{
position: d.Vec4f;
}>
export struct

Creates a struct schema that can be used to construct GPU buffers. Ensures proper alignment and padding of properties (as opposed to a d.unstruct schema). The order of members matches the passed in properties object.

@example const CircleStruct = d.struct({ radius: d.f32, pos: d.vec3f });

@param ― props Record with string keys and TgpuData values, each entry describing one struct member.

struct
({
position: d.Vec4f
position
:
import d
d
.
const vec4f: d.Vec4f
vec4f
});
const
const cast: 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<d.WgslStruct<{
position: d.Vec4f;
}>, unknown>(drawing: Drawing<d.WgslStruct<{
position: d.Vec4f;
}>, unknown>): Drawn (+1 overload)

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

add
({
Shaders<WgslStruct<{ position: Vec4f; }>, unknown>.Vertex: d.WgslStruct<{
position: d.Vec4f;
}>

Defines a vertex with its position and data carried to pixels.

Vertex
:
const Corner: d.WgslStruct<{
position: d.Vec4f;
}>
Corner
,
Shaders<WgslStruct<{ position: Vec4f; }>, unknown>.at: (vertexIndex: number, instanceIndex: number) => {
position: d.v4f;
}
at
: (
vertexIndex: number
vertexIndex
: number,
_instanceIndex: number
_instanceIndex
: number) => {
'use gpu';
const
const world: d.v3f
world
=
const statue: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
statue
.
Geometry<WgslStruct<{ position: Vec3f; normal: Vec3f; uv: Vec2f; }>>.vertexAt(this: void, i: number): {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
vertexAt
(
vertexIndex: number
vertexIndex
).
position: d.v3f
position
+
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(0, 1, 0);
return
const Corner: d.WgslStruct
(props?: {
position: d.v4f;
} | undefined) => {
position: d.v4f;
}
Corner
({
position: d.v4f
position
:
import cameras
cameras
.
const current: TgpuAccessor<d.WgslStruct<{
viewProjection: d.Mat4x4f;
position: d.Vec3f;
right: d.Vec3f;
up: d.Vec3f;
}>>

Provides the camera for a pipeline. A camera's inject() fills it with the camera uniform.

current
.
TgpuAccessor<WgslStruct<{ viewProjection: Mat4x4f; position: Vec3f; right: Vec3f; up: Vec3f; }>>.$: {
viewProjection: d.m4x4f;
position: d.v3f;
right: d.v3f;
up: d.v3f;
}
$
.
viewProjection: d.m4x4f

Transforms world coordinates to clip space.

viewProjection
*
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
const world: d.v3f
world
, 1) });
},
Drawing<WgslStruct<{ position: Vec4f; }>, unknown>.draw(pass: TgpuRenderCommands): void

Records a draw into the pass, on which the description has set the pipeline it built. That pipeline binds indices if supplied. drawIndexed throws without an index buffer. A group the drawing sets with pass.setBindGroup stays for later drawings in the pass that read its layout, so give such a layout to one drawing.

draw
: (
pass: TgpuRenderCommands
pass
) =>
pass: TgpuRenderCommands
pass
.
TgpuRenderCommands.draw(vertexCount: number, instanceCount?: number, firstVertex?: number, firstInstance?: number): void
draw
(
const statue: meshes.Geometry<d.WgslStruct<{
position: d.Vec3f;
normal: d.Vec3f;
uv: d.Vec2f;
}>>
statue
.
Geometry<V extends d.AnyWgslData = WgslStruct<{ position: Vec3f; normal: Vec3f; uv: Vec2f; }>>.vertexCount: number
vertexCount
),
});
const light: lighting.LightingOf<{
readonly shadow: {};
}>
light
.
shadow: Shadow | (Cascades & Shadow)

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

shadow
.
SunShadow.record(frame: Frame | TgpuCommandEncoder, casters: Drawn | readonly Drawn[], options?: ShadowRecordOptions): void

Clears the map in a depth pass per cascade that redraw picks and draws the casters built by add. Call this while recording each frame, before drawing shaded surfaces. When disabled, it draws nothing and makes every point sunlit. A frame that draws no cascade writes a timer's timestamps from an empty compute pass.

record
(
const frame: Frame
frame
,
const cast: Drawn
cast
); // each frame
  • Depth-only drawings need neither surfaceOf nor color, and build no fragment stage. With surfaceOf and depthOf, a drawing can supply another point’s depth, as impostors do.
  • A drawing with cutout, such as a sprite whose texture is clear around its figure, casts the figure’s shadow. The shadow pass runs cutout alone, so the same drawing goes into the shadow map even when its surfaceOf reads the lighting.
  • Instances cut out by masked meshes or by their kind’s coverage cast through it the same way, at their own depth.
  • Supply an index buffer as indices for drawIndexed and fill shader dependencies with inject().

shade takes the share of sunlight at a point from the sunShadow hook. The default, shadows.filtered, compares the point with the map: it moves the point lift texels along its normal, makes 1, 4 or 9 comparisons as filter sets, and returns their mean, from 0 in shadow to 1 in light. A hook of your own can change that share. The fishtank multiplies shadows.filtered(point) by its caustics. This one blends across the edge of a cascade:

const
const blended: shading.SunShadowHook
blended
:
import shading
shading
.
type SunShadowHook = (point: shading.Point) => number

Returns the share of sunlight that reaches a point, as shadows.filtered does. A value of 0 means full shadow and 1 means full light. Values above 1 can model focused light.

SunShadowHook
= (
point: {
position: v3f;
normal: v3f;
view: v3f;
uv: v2f;
uv1: v2f;
}
point
) => {
'use gpu';
const
const at: {
index: number;
edge: number;
}
at
=
import shadows
shadows
.
const cascadeOf: (point: shading.Point) => {
index: number;
edge: number;
}

Returns the first cascade of the sun's shadow whose square holds the point, inset by the filter's half width and the shadow's lift, and how near its edge the point lies. Index -1 means no cascade holds it, or the shadow is off.

cascadeOf
(
point: {
position: v3f;
normal: v3f;
view: v3f;
uv: v2f;
uv1: v2f;
}
point
); // index -1 outside every cascade
const
const lit: number
lit
=
import shadows
shadows
.
const cascaded: (point: shading.Point, index: number) => number

Returns the share of sunlight that passes the filter in cascade index of the sun's shadow. A grid of depth comparisons gives 0 in full shadow and 1 in full light. For a point outside the cascade's square or depth, an index without a cascade, or while the shadow is off, this returns 1.

cascaded
(
point: {
position: v3f;
normal: v3f;
view: v3f;
uv: v2f;
uv1: v2f;
}
point
,
const at: {
index: number;
edge: number;
}
at
.
index: number
index
);
if (
const at: {
index: number;
edge: number;
}
at
.
edge: number

Rises from 0 to 1 over the outer 10% of the cascade's square.

edge
<= 0) return
const lit: number
lit
;
return
import std
std
.
function mix(e1: number, e2: number, e3: number): number (+2 overloads)
export mix
mix
(
const lit: number
lit
,
import shadows
shadows
.
const cascaded: (point: shading.Point, index: number) => number

Returns the share of sunlight that passes the filter in cascade index of the sun's shadow. A grid of depth comparisons gives 0 in full shadow and 1 in full light. For a point outside the cascade's square or depth, an index without a cascade, or while the shadow is off, this returns 1.

cascaded
(
point: {
position: v3f;
normal: v3f;
view: v3f;
uv: v2f;
uv1: v2f;
}
point
,
const at: {
index: number;
edge: number;
}
at
.
index: number
index
+ 1),
const at: {
index: number;
edge: number;
}
at
.
edge: number

Rises from 0 to 1 over the outer 10% of the cascade's square.

edge
);
};
const
const light: lighting.LightingOf<{
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
};
readonly shading: {
readonly sunShadow: shading.SunShadowHook;
};
}>
light
=
import lighting
lighting
.
function create<{
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
};
readonly shading: {
readonly sunShadow: shading.SunShadowHook;
};
}>(root: TgpuRoot, options?: ({
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
};
readonly shading: {
readonly sunShadow: shading.SunShadowHook;
};
} & lighting.Options & {
readonly shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
};
readonly shading: {
readonly sunShadow: shading.SunShadowHook;
};
}) | undefined): lighting.LightingOf<...>

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
shadow: {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
} & shadows.FittedShadowOptions & {
readonly eye: cameras.OrbitCamera;
readonly cascades: 3;
}

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

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

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

eye
:
const view: cameras.OrbitCamera
view
,
cascades: 3

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

cascades
: 3 },
shading: {
readonly sunShadow: shading.SunShadowHook;
} & lighting.Shading & {
readonly sunShadow: shading.SunShadowHook;
}

Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog, filtered and reaching.

shading
: {
sunShadow: shading.SunShadowHook

Returns the share of sunlight that reaches a point. A value of 0 means full shadow and 1 means full light. Values above 1 can model focused light, such as light through moving water. Default shadows.filtered, which reads the shadow map.

sunShadow
:
const blended: shading.SunShadowHook
blended
},
});
  • cascadeOf(point) returns the first cascade holding the point, inset by the filter’s half width and lift, and an edge rising from 0 to 1 over the outer 10% of its square.
  • cascaded(point, index) filters cascade index. It returns 1 for a point outside that cascade and for an index outside the cascades, so the last cascade blends into sunlight.
  • shading.sunShare(point) returns what the hook gives, for shaders that don’t call shade. The domino run’s dust brightens in sunlight with it.

The shadow group’s enabled callback turns the shadow on and off each frame, as for other pass descriptions. A quality menu can change its value without a second lighting:

const
const menu: {
shadows: boolean;
}
menu
= {
shadows: boolean
shadows
: true };
const
const light: lighting.LightingOf<{
readonly shadow: {
readonly enabled: () => boolean;
};
}>
light
=
import lighting
lighting
.
function create<{
readonly shadow: {
readonly enabled: () => boolean;
};
}>(root: TgpuRoot, options?: ({
readonly shadow: {
readonly enabled: () => boolean;
};
} & lighting.Options & {
readonly shadow: {
readonly enabled: () => boolean;
};
}) | undefined): lighting.LightingOf<{
readonly shadow: {
readonly enabled: () => boolean;
};
}>

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
shadow: {
readonly enabled: () => boolean;
} & ((ShadowOptions | undefined) & {
readonly enabled: () => boolean;
})

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

shadow
: {
enabled: () => boolean
enabled
: () =>
const menu: {
shadows: boolean;
}
menu
.
shadows: boolean
shadows
} });
  • While it is off, record records no pass, culling lists stay empty and shadows.filtered returns 1 without sampling the map.
  • The map and pipelines stay allocated, so shadows draw on the first frame after it turns on.

resize(size) replaces the map between frames:

  • Put light.shadow.part in a shared layout before creating pipelines, as bindings.share(root, { shadow: light.shadow.part }), and give it to the lit description as shared.
  • The next frame then makes one bind group and no pipelines. The shadow’s own passes don’t read the map, so they need no group.
  • Without a shared layout, shading pipelines bind the map as fixed resources, and resize throws before it changes anything.
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.
Turn shadows off to let the lamps shine through the table onto the cobbles beneath it. The seller's lamp throws the potions' shadows along the table. Raise the wind to swing the lantern under the string of lights; its shadows swing with it.

A pointShadow group creates light.pointShadow, which gives up to count lights of the lighting’s table a shadow each. cast picks the lights:

import {
import lighting
lighting
,
import table
table
} from '@typegpu/kit';
const
const lamps: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>
lamps
=
import table
table
.
function create<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>(root: TgpuRoot, def: table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>, options: table.Options<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>): table.Table<...> (+1 overload)

Allocates GPU buffers and a CPU copy of host rows. Throws unless capacity is an integer in [1, 2^20].

@param ― def the definition its shaders were written against, from table.define

@returns ― a table you destroy

create
(
const root: TgpuRoot
root
,
import lighting
lighting
.
const SpotLight: table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>
export SpotLight
SpotLight
, {
Options<T extends AnyTableDef>.capacity: number

Sets the table capacity for its lifetime. Must be an integer in [1, 2^20], with slots from 0 to capacity - 1. A row id uses enough bits for the slot and the rest for its generation. The generation has 29 bits at 8 rows and 12 bits at 2^20 rows.

capacity
: 16,
Options<TableDef<"SpotLight", { aim: Vec3f; cone: Vec2f; position: Vec3f; range: F32; color: Vec3f; intensity: F32; }, {}>>.defaults?: table.Values<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>> | undefined

Sets values for fields omitted by add. Without defaults, every field starts at zero. Default none.

defaults
: { ...
import lighting
lighting
.
const WHITE_SPOT: {
readonly intensity: number;
readonly color: v3f;
readonly range: number;
} & {
readonly aim: v3f;
readonly cone: v2f;
}

Holds WHITE with an aim straight down and a full cone, for a table of spot lights.

WHITE_SPOT
},
});
const
const light: lighting.LightingOf<{
readonly points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
};
readonly pointShadow: {
readonly count: 2;
};
}>
light
=
import lighting
lighting
.
function create<{
readonly points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
};
readonly pointShadow: {
readonly count: 2;
};
}>(root: TgpuRoot, options?: ({
readonly points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
};
readonly pointShadow: {
readonly count: 2;
};
} & lighting.Options & {
...;
}) | undefined): lighting.LightingOf<...>

Creates lighting with a sun, sky and fog. It can also read point lights from a table or a function and cast shadows. Pipe its injection into each pipeline that uses it. Throws if points names no source or more than one, a light function has neither count nor candidates, a table comes with count, or candidates.ranges is anything other than a positive integer.

@injects ― hook the shading hooks its options name: reflect, ambient, fog, sunShadow and attenuation

@injects ― definition the sun, sky and fog, the shadows and the point lights, which shading reads

@binds ― 1 uniform: shading.shade, the sun, sky and fog

@binds ― 2 storage, 1 uniform: shading.shade with a table of point lights

@binds ― 3 storage, 1 uniform: shading.shade with a table of lights whose positions are on the device, the table's two parts

@binds ― 1 uniform: shading.shade with points.light or points.spot, plus what the function binds

@binds ― 0 storage: shading.shade with points.candidates, plus what their functions bind, 2 storage for a neighbour grid's range and slot

@binds ― 1 uniform: shading.shade with a shadow, the sun's light space and cascades, beside the depth array and its comparison sampler

@binds ― 1 uniform: shading.shade with pointShadow, each shadow's light, layers and views, beside their depth array and the comparison sampler

@owns ― 1 to 3 buffers, a sampler, targets, a cameras.layered, passes: the sun, sky and fog; with a shadow, its light space, the views of its cascades, a comparison sampler, the depth array and the passes that draw it; with pointShadow, the same for the lights' faces

@records ― shadow.record and pointShadow.record with what was added to them, before the passes that shade

@param ― options.shadow the sun's shadow, with a depth pass per cascade

@param ― options.pointShadow shadows that cast gives to lights of points.table, with a depth pass per face

@param ― options.points point lights from a table or a 'use gpu' function. Every lit point visits each slot up to the table's highest live one, or count with a function, unless candidates names the slots that may reach it

@returns ― lighting you destroy

create
(
const root: TgpuRoot
root
, {
points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
} & lighting.TablePoints & {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
}

Enables point lights from a table or a function. Default none.

points
: {
table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>> & lighting.LightTable

Supplies a table of lighting.PointLight, MovingPointLight, SpotLight or MovingSpotLight. Its fields start at zero, so give it defaults such as lighting.WHITE. Flush and destroy it yourself. Default none, lights from light or spot.

table
:
const lamps: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>
lamps
},
pointShadow: {
readonly count: 2;
} & PointShadowOptions & {
readonly count: 2;
}

Gives up to count point or spot lights a shadow each, which light.pointShadow.cast assigns. Needs points.table of lights the CPU places, not a moving one. Default none.

pointShadow
: {
count: 2

Sets how many lights can cast a shadow at once, each with six layers of the map. Shading compares each in-range light with every shadow. Default 1.

count
: 2 } });
const
const fire: table.RowId<"SpotLight">
fire
=
const lamps: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>
lamps
.
Table<TableDef<"SpotLight", { aim: Vec3f; cone: Vec2f; position: Vec3f; range: F32; color: Vec3f; intensity: F32; }, {}>>.add(values?: table.Values<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>> | undefined): table.RowId<"SpotLight">

Adds a row in the lowest free slot. Uses defaults for omitted fields. Throws when the table is full.

add
({
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [0, 0.8, 0],
color?: ArrayLike<number> | InferInput<Vec3f> | undefined

Sets the light colour in linear colour space.

color
: [1, 0.45, 0.15],
intensity?: number | undefined

Multiplies color.

intensity
: 8,
range?: number | undefined

Sets the distance in world units where light intensity reaches zero.

range
: 14 });
const
const sign: table.RowId<"SpotLight">
sign
=
const lamps: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>
lamps
.
Table<TableDef<"SpotLight", { aim: Vec3f; cone: Vec2f; position: Vec3f; range: F32; color: Vec3f; intensity: F32; }, {}>>.add(values?: table.Values<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>> | undefined): table.RowId<"SpotLight">

Adds a row in the lowest free slot. Uses defaults for omitted fields. Throws when the table is full.

add
({
position?: ArrayLike<number> | InferInput<Vec3f> | undefined
position
: [3, 4, 1],
aim?: ArrayLike<number> | InferInput<Vec3f> | undefined

Points the cone's axis away from the light. It can have any length.

aim
: [0, -1, 0.3],
cone?: ArrayLike<number> | InferInput<Vec2f> | undefined

Holds the cone's scale and offset from lighting.cone(inner, outer). The default [0, 1] lights every direction.

cone
:
import lighting
lighting
.
const cone: (inner: number, outer: number) => v2f

Returns a cone's scale and offset for a spot light's cone, from its inner and outer half-angles in radians. Light falls from full at inner to none at outer, as glTF's spot lights do. It runs on the CPU and in shaders.

cone
(0.3, 0.45),
intensity?: number | undefined

Multiplies color.

intensity
: 30,
range?: number | undefined

Sets the distance in world units where light intensity reaches zero.

range
: 10,
});
const light: lighting.LightingOf<{
readonly points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
};
readonly pointShadow: {
readonly count: 2;
};
}>
light
.
pointShadow: PointShadow

Provides the point and spot lights' shadows when enabled in the options. Throws after destroy.

pointShadow
.
PointShadow.cast(lights: readonly table.RowId<string>[]): void

Gives a shadow to each light in lights, ids in the lighting's table, at most count. A light that already casts keeps its shadow, a light left out frees its own, and a new one takes a free shadow, so casting the same lights again writes nothing. A spot whose outer half-angle is at most 60° draws one layer along its aim, and a wider spot or a point light draws six faces. Throws for more lights than count.

cast
([
const fire: table.RowId<"SpotLight">
fire
,
const sign: table.RowId<"SpotLight">
sign
]); // six faces for the fire, one layer for the sign
const
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
=
import passes
passes
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}, {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>(root: TgpuRoot, screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}>, writes: {
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}, options?: passes.TargetOptions<{
readonly hdr: {
readonly format: "rgba16float";
};
readonly depth: {
readonly format: "depth24plus";
};
}> | 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 depth: {
readonly format: "depth24plus";
};
}>
screen
, {
hdr: typeof passes.color
hdr
:
import passes
passes
.
const color: typeof passes.color

Marks the target that receives each renderer's colour.

color
,
depth: typeof passes.depth
depth
:
import passes
passes
.
const depth: typeof passes.depth

Marks the depth target.

depth
})
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.pipe(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

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

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

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

inject
())
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.beneath(fill: Inject): passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>

Adds fill after the fills given to earlier beneath calls and returns a description of the same passes. A drawing's own fills take precedence over these, and piped fills over both, so beneath supplies defaults such as a lighting. A compound drawing that passes its own from to pipeline gets them by starting from with inject().

beneath
(
const light: lighting.LightingOf<{
readonly points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
};
readonly pointShadow: {
readonly count: 2;
};
}>
light
.
Lighting.inject(): Inject

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

inject
());
const
const renderer: instances.Renderer
renderer
=
const things: instances.Instances<Plain>
things
.
Instances<Plain>.renderer(options?: instances.RendererOptions): instances.Renderer

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

renderer
();
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 casters: passes.Drawn
casters
=
const light: lighting.LightingOf<{
readonly points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
};
readonly pointShadow: {
readonly count: 2;
};
}>
light
.
pointShadow: PointShadow

Provides the point and spot lights' shadows when enabled in the options. Throws after destroy.

pointShadow
.
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
);
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 things: instances.Instances<Plain>
things
.
Instances<Plain>.flush(encoder?: TgpuCommandEncoder): void

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

flush
();
const lamps: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>
lamps
.
Table<TableDef<"SpotLight", { aim: Vec3f; cone: Vec2f; position: Vec3f; range: F32; color: Vec3f; intensity: F32; }, {}>>.flush(encoder?: TgpuCommandEncoder): void

Uploads changes since the last flush with queue.writeBuffer. Writes reach the queue before the next submit. Call once per frame before passes that read the table. A HistoryTable takes the frame's encoder and throws without it.

flush
();
const light: lighting.LightingOf<{
readonly points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
};
readonly pointShadow: {
readonly count: 2;
};
}>
light
.
pointShadow: PointShadow

Provides the point and spot lights' shadows when enabled in the options. Throws after destroy.

pointShadow
.
PointShadow.record(frame: frames.Frame | TgpuCommandEncoder, casters: passes.Drawn | readonly passes.Drawn[], options?: PointShadowRecordOptions): void

Places each shadow at its light from the table's CPU rows, then clears and draws the layers of each shadowed light that redraw picks with the casters built by add, a depth pass per layer. Call this while recording each frame, before drawing shaded surfaces. A removed light frees its shadow. A frame that draws no layer writes a timer's timestamps from an empty compute pass.

record
(
frame: frames.Frame
frame
,
const casters: passes.Drawn
casters
); // seven depth passes
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.record(frame: frames.Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}> | undefined): void

Begins a pass, draws drawn or each drawing in a list in order, then ends the pass. Reuse the list to avoid allocating each frame. Call begin yourself when you need to choose drawings per frame. Records nothing while enabled is false.

record
(
frame: frames.Frame
frame
,
const drawn: passes.Drawn
drawn
);
}),
);
  • Each shadow holds six layers of one texture_depth_2d_array.
  • cast(lights) gives a shadow to each light in the list, at most count. A light that already casts keeps its shadow, a light left out frees its own, and a new one takes a free shadow. Casting the same lights again writes nothing, so you can cast from the lamps nearest the eye every frame.
  • A spot whose outer half-angle is at most 60° draws one layer, a perspective view along its aim. A wider spot or a point light draws six faces. Each face covers 90° plus the filter’s half width, so the filter at a face’s edge stays inside that face.
  • record places each shadow at its light’s row in the table, so a light moved on the CPU needs no other call. A removed light frees its shadow.
  • Near is 2% of the light’s range and far is range.
  • filter, format, bias, enabled and resize work as the sun’s do. lift moves the shaded point along its normal by that many texels at its distance from the light. Share light.pointShadow.part as bindings.share(root, { pointShadow: light.pointShadow.part }) to resize without new pipelines.
  • A layer takes size² × 2 bytes in depth16unorm and size² × 4 in the other formats.

The default attenuation, shading.reaching, multiplies in shadows.shadowed(point, toLight, distance, slot), which a hook of your own can call too. For each light in range, shading compares its slot with each of the count shadows. A shadowed light picks a face from the direction to the point, takes one matrix product and makes the filter’s comparisons.

Each layer is a depth pass that draws every caster, so seven layers cost seven passes each frame. A lamp that holds still over casters that hold still draws the same layers again. Pass redraw to choose the lights that draw this frame. The others keep the layers they drew last:

// Lamps with a caster that moved within their range this frame, from your own game state
const
const stirred: Set<unknown>
stirred
= new
var Set: SetConstructor
new <unknown>(iterable?: Iterable<unknown> | null | undefined) => Set<unknown> (+1 overload)
Set
<unknown>();
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 lamps: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>
lamps
.
Table<TableDef<"SpotLight", { aim: Vec3f; cone: Vec2f; position: Vec3f; range: F32; color: Vec3f; intensity: F32; }, {}>>.flush(encoder?: TgpuCommandEncoder): void

Uploads changes since the last flush with queue.writeBuffer. Writes reach the queue before the next submit. Call once per frame before passes that read the table. A HistoryTable takes the frame's encoder and throws without it.

flush
();
const light: lighting.LightingOf<{
readonly points: {
readonly table: table.Table<table.TableDef<"SpotLight", {
aim: Vec3f;
cone: Vec2f;
position: Vec3f;
range: F32;
color: Vec3f;
intensity: F32;
}, {}>>;
};
readonly pointShadow: {
readonly count: 4;
};
}>
light
.
pointShadow: PointShadow

Provides the point and spot lights' shadows when enabled in the options. Throws after destroy.

pointShadow
.
PointShadow.record(frame: frames.Frame | TgpuCommandEncoder, casters: Drawn | readonly Drawn[], options?: PointShadowRecordOptions): void

Places each shadow at its light from the table's CPU rows, then clears and draws the layers of each shadowed light that redraw picks with the casters built by add, a depth pass per layer. Call this while recording each frame, before drawing shaded surfaces. A removed light frees its shadow. A frame that draws no layer writes a timer's timestamps from an empty compute pass.

record
(
frame: frames.Frame
frame
,
const casters: Drawn
casters
, {
PointShadowRecordOptions.redraw?: ((light: table.RowId<string>) => boolean) | undefined

Chooses which cast lights draw their layers this frame. A light it skips keeps the layers it drew last, so a still light over still casters can skip until something it sees moves. A light still draws when it gains a shadow, when its row moves, turns or changes range, after resize and after a frame with enabled false. Default every light.

redraw
: (
lamp: table.RowId<string>
lamp
) =>
const stirred: Set<unknown>
stirred
.
Set<unknown>.has(value: unknown): boolean

@returns ― a boolean indicating whether an element with the specified value exists in the Set or not.

has
(
lamp: table.RowId<string>
lamp
) });
const stirred: Set<unknown>
stirred
.
Set<unknown>.clear(): void

Removes all elements from the Set.

clear
();
});
  • A light draws anyway on the frame it gains a shadow, when its row moves, turns or changes range, after resize, and after a frame with the shadows off. A light whose flicker jitters its position draws every frame; flicker its intensity alone to keep its layers.
  • A frame that draws no layer writes a timer passed in timestampWrites from an empty compute pass, so a profiler timer can stay on record.
  • A fixed map’s depth spans twice reach. At an extent of 20, a depth16unorm step is about 0.001 world units. A cascade’s depth spans its sphere’s width plus reach.
  • Cascades fit the camera’s view, not the casters. A caster beyond reach towards the sun casts nothing, and nothing beyond far is shadowed.
  • The filter is fixed at creation. A menu offering different filters needs a separate lighting for each.
  • Only lights placed on the CPU cast. lighting.create throws for pointShadow without points.table, or with a table of lighting.MovingPointLight or MovingSpotLight.
  • Each layer that draws draws all its casters: six passes for a point light and one for a narrow spot.
  • count is at most 42 at WebGPU’s minimum of 256 layers. Shading compares every in-range light with every shadow, so its cost grows with count.