Store baked or changing light in textures and sample it through the lighting ambient hook.
texels
Reduce the field to 64 texels per side to see blocky emitters and softer shadows. Glows are opaque emitting discs, and pillars are opaque non-emitting squares.
Every shaded pixel loops over point-light slots. Dozens of lamps or short-lived shots and blasts can make that expensive. Compute light into a texture at load time or each frame, then sample it at the shaded point through ambient. TypeGPU binds the texture in each pipeline using that lighting.
Specifies the sampling behavior when the sample footprint is larger than one texel.
minFilter:'linear'});
const
constyard:lighting.Lighting
yard=
import lighting
lighting.
functioncreate<{
readonly shading:{
readonlyambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
};
}>(root:TgpuRoot,options?:({
readonly shading:{
readonlyambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
};
}&lighting.Options&{
...;
})|undefined):lighting.Lighting
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,{
shading:{
readonly ambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
}&lighting.Shading&{
readonly ambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
}
Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog,
filtered and reaching.
Computes ambient light by blending sky and ground colours according to the surface normal.
Reflections sample the sky in the reflection direction. Roughness blends them towards diffuse
light, and skyReflection scales them. The material's occlusion darkens both.
hemisphere(
material:{
color:d.v3f;
roughness: number;
emission:d.v3f;
metallic: number;
occlusion: number;
}
material,
point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
}
point)+
material:{
color:d.v3f;
roughness: number;
emission:d.v3f;
metallic: number;
occlusion: number;
}
material.
color:d.v3f
Sets the diffuse colour of a dielectric or the reflection colour of a metal, in linear
colour space.
color*
constlamps:d.v3f
lamps;
},
},
});
The volume uses 8 bytes per texel, 0.5 MB in this example, and one sample per shaded pixel.
Fill it with a kernel that sums lamp light at each texel or a radiance field computed over a yard map.
Creates a sampled texture from one image. 8-bit images become rgba8unorm-srgb, or
rgba8unorm with color: 'linear'. Half-float images, and 8-bit ones with packed, become
format. Throws if the pixel count doesn't match the size, the image is empty or larger than
the device allows, packed comes with color, or format with 8-bit colour or data.
@param ― picture pixels from decode, decodeHdr or your own code, or a bitmap
@returns ― a texture you destroy, with every mip level unless mips is false
create(
constroot:TgpuRoot
root,
constpicture:images.Picture
picture,{
Options.packed?:images.Unpack|undefined
Converts light stored in an 8-bit image, such as RGBM, to linear light once, at upload, so the
texture filters and mips like any HDR texture. A 'use gpu' function that takes each texel as
stored, unfiltered, and returns linear RGBA. The texture takes format. It reads raw values,
so it takes no color. Default none.
packed:
import images
images.
constrgbm:(range:number)=>images.Unpack
Unpacks RGBM, light stored as rgb × a × range in linear values, for packed.
@param ― range the brightest light the encoding holds
@returns ― a function for packed
rgbm(8),
Options.mips?: boolean |undefined
Generates every mip level down to 1 × 1. Default true.
Creates a filtering sampler for textures from this module.
@returns ― a sampler, which needs no destroy
sampler(
constroot:TgpuRoot
root,{
SamplerOptions.wrap?: GPUAddressMode |undefined
Says what happens past the edge: WebGPU's address mode on every axis. Default 'repeat'.
wrap:'clamp-to-edge'});
const
constroom:lighting.Lighting
room=
import lighting
lighting.
functioncreate<{
readonly shading:{
readonlyambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
};
}>(root:TgpuRoot,options?:({
readonly shading:{
readonlyambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
};
}&lighting.Options&{
...;
})|undefined):lighting.Lighting
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,{
shading:{
readonly ambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
}&lighting.Shading&{
readonly ambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
}
Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog,
filtered and reaching.
Shades a point by the light around it, for an ambient hook.
irradiance is the light a white matte surface at the point reflects, as environment.irradiance
and a diffuse lightmap give it. It is used as given, so multiply it by material.occlusion if the
light doesn't already include occlusion.
reflection is the light arriving along reflectionOf, as environment.reflection gives it.
It is weighted by reflectance and darkened by specularOcclusion of the material's
occlusion.
surrounded(
material:{
color:d.v3f;
roughness: number;
emission:d.v3f;
metallic: number;
occlusion: number;
}
material,
point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
}
point,
constlight:d.v3f
light,
constlight:d.v3f
light);
},
},
});
If the bake already includes occlusion, pass its light as is. Otherwise multiply the diffuse light by material.occlusion.
To reflect an environment instead, sample it along shading.reflectionOf(point, material.roughness).
gltf.read keeps the second set with uvs1: true. Load 8-bit packed maps with packed. On React Native, use an encoding that leaves alpha at 255, as its decoder needs.
classroomBlender's Classroom, its daylight and lamps baked on a second uv set, sunbeams through its dust.
Drag to paint, or tap the cave first on a touch screen; turn bounce off to remove reflected light. Lava and crystal emit light, rock and sand block it, and walls and steam reflect it.
Shots, muzzle flashes and blasts can create dozens of lights lasting a fraction of a second. Draw their emission into a texture over the ground. @typegpu/radiance-cascades computes how much reaches each texel around obstacles such as towers and trees. The kit doesn’t depend on it, so add it yourself, in a version whose typegpu peer range covers your project’s TypeGPU, such as:
Create a medium, a texture holding emission and extinction per texel. This kernel fills it from a table of glows:
import{
import table
table}from'@typegpu/kit';
import*as
import hrc
hrcfrom'@typegpu/radiance-cascades/holographic';
const
constGlow:table.TableDef<"Glow",{
position:d.Vec2f;
radius:d.F32;
color:d.Vec3f;
},{}>
Glow=
import table
table.
functiondefine<"Glow",{
position:d.Vec2f;
radius:d.F32;
color:d.Vec3f;
},{}>(name:"Glow",parts:{
host?:{
position:d.Vec2f;
radius:d.F32;
color:d.Vec3f;
}|undefined;
device?:{}|undefined;
}):table.TableDef<"Glow",{
position:d.Vec2f;
radius:d.F32;
color:d.Vec3f;
},{}>
Declares CPU-written host fields and GPU-written device fields using the records accepted by
d.struct. Define it at module scope beside shaders. Throws if no fields are declared, a field
appears in both parts, the CPU writer cannot write a field type such as f16, or a field uses a
reserved member name such as alive.
name brands row ids as RowId<'Enemy'> and labels structs, buffers, WGSL functions and
errors. It is not a lookup key.
@param ― name a label for ids and WGSL
@param ― parts the host fields the CPU writes and the device fields the GPU writes
@returns ― a definition whose getters shaders read and that table.create allocates
define('Glow',{
host?:{
position:d.Vec2f;
radius: d.F32;
color: d.Vec3f;
}|undefined
host:{
position:d.Vec2f
position:
import d
d.
constvec2f:d.Vec2f
vec2f,
radius:d.F32
radius:
import d
d.
constf32:d.F32
export f32
A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)
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,
constGlow:table.TableDef<"Glow",{
position:d.Vec2f;
radius:d.F32;
color:d.Vec3f;
},{}>
Glow,{
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:256});
const
constSIDE:32
SIDE=32;// world units across the square of ground the field covers
Specifies the sampling behavior when the sample footprint is larger than one texel.
minFilter:'linear'});
const
constlight:lighting.Lighting
light=
import lighting
lighting.
functioncreate<{
readonly shading:{
readonlyambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
};
}>(root:TgpuRoot,options?:({
readonly shading:{
readonlyambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
};
}&lighting.Options&{
...;
})|undefined):lighting.Lighting
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,{
shading:{
readonly ambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
}&lighting.Shading&{
readonly ambient:(material:{
color:d.v3f;
roughness:number;
emission:d.v3f;
metallic:number;
occlusion:number;
},point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
})=>d.v3f;
}
Sets the functions that shading.shade calls. Default pbr, hemisphere, heightFog,
filtered and reaching.
Computes ambient light by blending sky and ground colours according to the surface normal.
Reflections sample the sky in the reflection direction. Roughness blends them towards diffuse
light, and skyReflection scales them. The material's occlusion darkens both.
hemisphere(
material:{
color:d.v3f;
roughness: number;
emission:d.v3f;
metallic: number;
occlusion: number;
}
material,
point:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
}
point)+
material:{
color:d.v3f;
roughness: number;
emission:d.v3f;
metallic: number;
occlusion: number;
}
material.
color:d.v3f
Sets the diffuse colour of a dielectric or the reflection colour of a metal, in linear
colour space.
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
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(
constroot:TgpuRoot
root,(
frame:frames.Frame
frame)=>{
constglows:{
flush():void;
}
glows.
functionflush():void
flush();
const
constcompute:TgpuComputePass
compute=
frame:frames.Frame
frame.
Frame.encoder: TgpuCommandEncoder
Records commands for submission after the recorder returns. submit() replaces this encoder.
Read it at each call instead of keeping it, and let the frame submit it.
Records the solve into one compute pass. A supplied encoder is not submitted and a
supplied pass is not ended; with neither, the work is submitted on its own.
// Record passes using light after this compute pass
}),
);
run submits no commands. field.output retains the light until the next run. Use a cadence, such as 30 Hz, and rerun whenever the board changes.
An opaque disc emits its colour. In clear air, a glow of radius r contributes about asin(r / d) / π of that colour at distance d: about one tenth at three radii and one fortieth at twelve. Light in a plane falls as one over distance rather than distance squared.
Walls use opaque texels without emission and cast soft shadows. A point on a wall face lies in those dark texels. Offset its sample two texels along the normal.
Extinction is per texel, so finer fields make haze dim light faster. Multiply by SIDE / TEXELS to express it per world unit.
The field is flat. Elevated points receive the light at ground level, and points outside the square sample its nearest edge. Use ambient to fade light with height and reject samples outside the square.
A medium can read any simulation buffer, including towers and shots, to find emitters.
The kernel above tests every glow at every texel, which suits a few dozen glows. For thousands, run one thread per glow to atomicAdd fixed-point light into a u32 grid. Then run one thread per texel to convert the sums into the medium.
field.byteSize gives the field’s memory. The medium adds 8 bytes a texel.
field.sectors stores light from four directions. Use it to shade by normal and compute reflections from walls:
Read the previous run’s sectors just outside each wall face. Each sector contains one quarter of the surrounding light. Multiply by four, weight by the normal and multiply by wall albedo.
Add the reflected light to the medium before the next run. Each run adds one bounce.
Falling sand computes one bounce from walls and steam at each 30 Hz update.