Skip to main content

Module light

Module light 

Source
Expand description

The frame’s lights, packed for the shaders and clustered on the CPU.

A forward renderer shades a fragment from the lights that can reach it, and forward clustered rendering answers “which lights reach this fragment” by cutting the view into a grid of cells — tiles in screen space, logarithmic slices in depth — and giving every cell the indices of the lights that touch it. A fragment then reads exactly one cell.

The clustering here is plain CPU work over the packed records, producing the u32 cell array and the LightGpu array a draw binds. It needs no compute shader, which is what lets the same code serve WebGL2: a device with no compute passes emits the very same bytes. The arrays are uploaded like every other frame-wide array — a storage buffer where the device has those, texels where it does not.

§The grid

Screen space is cut into cells of about ClusterConfig::cell_pixels pixels, and depth into ClusterConfig::z_slices slices. Perspective slices are equal in ln(depth) after the first slice’s fixed boundary; orthographic ones are linear. Both mappings are monotonic and shared by the CPU assignment and the fragment lookup; that shared mapping is the one correctness requirement, because it is what keeps a light and the fragments it lights in one cell.

§The cell record

A cell is CELL_WORDS u32s: the count of lights in it, then their indices, then padding to a whole texel. The indices point into the frame’s LightGpu array, so a cell costs no light data of its own.

Lights are ranked by PackedLight::importance before assignment, so a cell that overflows drops its weakest light and the first entries of every cell are its strongest.

§A light at infinity is not in the grid

A directional light reaches every fragment, so assigning it to every cell would spend a slot of each on the same record. Directional lights and the frame’s ambient term live in LightingUniform instead — one frame-wide uniform every lit fragment reads before the cell’s own lights — and the grid holds only bounded ones. The uniform carries at most MAX_DIRECTIONAL_LIGHTS of them; the rest of a frame’s directional lights are dropped, weakest first, the way a full cell drops its weakest light.

Re-exports§

use glam::Mat4; 🔒
use glam::UVec4; 🔒
use glam::Vec2; 🔒
use glam::Vec3; 🔒
use glam::Vec4; 🔒

Structs§

BoundingSphere
The sphere clustering culls a light by.
ClusterConfig
How a frame’s view is divided into cells.
ClusterUniform
The clustering state a frame’s shaders read.
DirectionalGpu
One directional light, as the frame-wide uniform carries it.
LightClusters
One frame’s clustered light assignment, in storage the caller reuses.
LightGpu
One light as the shaders read it.
LightingUniform
The frame-wide lighting every lit fragment reads before its cell’s lights.
PackedLight
A light packed for the shader, with the facts clustering needs.

Enums§

LightKind
Which of the light record’s lanes a light uses.

Constants§

CELL_WORDS
How many u32s one cell occupies: its count, its light indices, and the padding that rounds the record to a whole Rgba32Float texel.
LIGHTS_PER_CELL
How many lights one cell holds: the record minus its count word.
MAX_DIRECTIONAL_LIGHTS
How many directional lights one frame’s LightingUniform carries.
QUAD_LIGHT_CORNERS
How many corners a quad light’s polygon has.

Functions§

depth_slice
The depth slice containing depth, mirrored by cluster_depth_slice in the built-in shader.
insert 🔒
Append index to the cell at cell, dropping it when the cell is full.
screen_cells 🔒
The x and y cells a light’s sphere covers, as inclusive ranges.