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
constlight:lighting.LightingOf<{
readonly settings:{
readonly sunDirection:[-0.6,1, -0.7];
};
readonly shadow:{
readonly extent:9;
};
}>
light=
import lighting
lighting.
functioncreate<{
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(
constroot: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
constscene:passes.Passes<{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
}>
scene=
import passes
passes
.
functioncreate<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
},{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
}>(root:TgpuRoot,screen:targets.Targets<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>,writes:{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
},options?:passes.TargetOptions<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>|undefined):passes.Passes<...>(+1overload)
Describes the targets and outputs for scene passes. With a surface such as the canvas, writes
only each renderer's colour. With a target set, writes colour into the target marked color,
depth into the target marked depth, and other outputs into their named targets. Multisampled
targets resolve according to resolve. Targets with layers draw one pass per layer, and every
target the description draws into needs the same layers. add builds a renderer's drawing.
begin starts a pass. pipe supplies fills such as a camera.
@param ― screen the surface, or the targets, which give the formats; each one the description
names is drawn into
@param ― writes what fills each target, by its key in screen
@returns ― a description you destroy, which holds the pipelines it builds
create(
constroot:TgpuRoot
root,
constscreen:targets.Targets<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>
screen,{
hdr:typeofpasses.color
hdr:
import passes
passes.
constcolor:typeofpasses.color
Marks the target that receives each renderer's colour.
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(
constview:cameras.OrbitCamera
view.
CameraBase.inject(): Inject
Fills cameras.current for the pipelines created from the configuration it is piped into.
Adds fill after the fills given to earlier beneath calls and returns a description of the
same passes. A drawing's own fills take precedence over these, and piped fills over both, so
beneath supplies defaults such as a lighting. A compound drawing that passes its own from
to pipeline gets them by starting from with inject().
beneath(
constlight: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.
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.
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(
constrenderer:instances.Renderer
renderer);
const
constcast:passes.Drawn
cast=
constlight: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(+1overload)
Builds a caster's depth pipeline with the shadow's bias and returns its draw methods.
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
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();
constlight: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.
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.
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,
constdrawn: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.
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:
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
constlight: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.
functioncreate<{
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(
constroot: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:
constview: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
constscene:passes.Passes<{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
}>
scene=
import passes
passes
.
functioncreate<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
},{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
}>(root:TgpuRoot,screen:targets.Targets<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>,writes:{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
},options?:passes.TargetOptions<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>|undefined):passes.Passes<...>(+1overload)
Describes the targets and outputs for scene passes. With a surface such as the canvas, writes
only each renderer's colour. With a target set, writes colour into the target marked color,
depth into the target marked depth, and other outputs into their named targets. Multisampled
targets resolve according to resolve. Targets with layers draw one pass per layer, and every
target the description draws into needs the same layers. add builds a renderer's drawing.
begin starts a pass. pipe supplies fills such as a camera.
@param ― screen the surface, or the targets, which give the formats; each one the description
names is drawn into
@param ― writes what fills each target, by its key in screen
@returns ― a description you destroy, which holds the pipelines it builds
create(
constroot:TgpuRoot
root,
constscreen:targets.Targets<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>
screen,{
hdr:typeofpasses.color
hdr:
import passes
passes.
constcolor:typeofpasses.color
Marks the target that receives each renderer's colour.
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(
constview:cameras.OrbitCamera
view.
CameraBase.inject(): Inject
Fills cameras.current for the pipelines created from the configuration it is piped into.
Adds fill after the fills given to earlier beneath calls and returns a description of the
same passes. A drawing's own fills take precedence over these, and piped fills over both, so
beneath supplies defaults such as a lighting. A compound drawing that passes its own from
to pipeline gets them by starting from with inject().
beneath(
constlight: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.
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.
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(
constrenderer:instances.Renderer
renderer);
const
constcast:passes.Drawn
cast=
constlight: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(+1overload)
Builds a caster's depth pipeline with the shadow's bias and returns its draw methods.
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
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();
constlight: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.
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.
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,
constdrawn: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
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.
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:
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.
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:
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.
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.
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(
constframe:Frame
frame,
constcast: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
constblended:shading.SunShadowHook
blended:
import shading
shading.
typeSunShadowHook=(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
constat:{
index:number;
edge:number;
}
at=
import shadows
shadows.
constcascadeOf:(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.
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,
constat:{
index:number;
edge:number;
}
at.
index: number
index);
if(
constat:{
index:number;
edge:number;
}
at.
edge: number
Rises from 0 to 1 over the outer 10% of the cascade's square.
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,
constat:{
index:number;
edge:number;
}
at.
index: number
index+1),
constat:{
index:number;
edge:number;
}
at.
edge: number
Rises from 0 to 1 over the outer 10% of the cascade's square.
edge);
};
const
constlight:lighting.LightingOf<{
readonly shadow:{
readonly eye:cameras.OrbitCamera;
readonly cascades:3;
};
readonly shading:{
readonly sunShadow:shading.SunShadowHook;
};
}>
light=
import lighting
lighting.
functioncreate<{
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(
constroot: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:
constview: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:
constblended: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
constmenu:{
shadows:boolean;
}
menu={
shadows: boolean
shadows:true};
const
constlight:lighting.LightingOf<{
readonly shadow:{
readonlyenabled:()=>boolean;
};
}>
light=
import lighting
lighting.
functioncreate<{
readonly shadow:{
readonlyenabled:()=>boolean;
};
}>(root:TgpuRoot,options?:({
readonly shadow:{
readonlyenabled:()=>boolean;
};
}&lighting.Options&{
readonly shadow:{
readonlyenabled:()=>boolean;
};
})|undefined):lighting.LightingOf<{
readonly shadow:{
readonlyenabled:()=>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(
constroot: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:()=>
constmenu:{
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.
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.
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:
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(
constroot:TgpuRoot
root,
import lighting
lighting.
constSpotLight: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.
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
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.
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.
Holds the cone's scale and offset from lighting.cone(inner, outer). The default [0, 1]
lights every direction.
cone:
import lighting
lighting.
constcone:(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.
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([
constfire:table.RowId<"SpotLight">
fire,
constsign:table.RowId<"SpotLight">
sign]);// six faces for the fire, one layer for the sign
const
constscene:passes.Passes<{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
}>
scene=
import passes
passes
.
functioncreate<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
},{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
}>(root:TgpuRoot,screen:targets.Targets<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>,writes:{
readonly hdr:typeofpasses.color;
readonly depth:typeofpasses.depth;
},options?:passes.TargetOptions<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>|undefined):passes.Passes<...>(+1overload)
Describes the targets and outputs for scene passes. With a surface such as the canvas, writes
only each renderer's colour. With a target set, writes colour into the target marked color,
depth into the target marked depth, and other outputs into their named targets. Multisampled
targets resolve according to resolve. Targets with layers draw one pass per layer, and every
target the description draws into needs the same layers. add builds a renderer's drawing.
begin starts a pass. pipe supplies fills such as a camera.
@param ― screen the surface, or the targets, which give the formats; each one the description
names is drawn into
@param ― writes what fills each target, by its key in screen
@returns ― a description you destroy, which holds the pipelines it builds
create(
constroot:TgpuRoot
root,
constscreen:targets.Targets<{
readonly hdr:{
readonly format:"rgba16float";
};
readonly depth:{
readonly format:"depth24plus";
};
}>
screen,{
hdr:typeofpasses.color
hdr:
import passes
passes.
constcolor:typeofpasses.color
Marks the target that receives each renderer's colour.
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(
constview:cameras.OrbitCamera
view.
CameraBase.inject(): Inject
Fills cameras.current for the pipelines created from the configuration it is piped into.
Adds fill after the fills given to earlier beneath calls and returns a description of the
same passes. A drawing's own fills take precedence over these, and piped fills over both, so
beneath supplies defaults such as a lighting. A compound drawing that passes its own from
to pipeline gets them by starting from with inject().
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.
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.
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
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.
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.
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.
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,
constdrawn: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
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.
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.
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)=>
conststirred: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)});
conststirred: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.