simvx.core.clustered_lighting

Forward+ clustered lighting — CPU-side data model and light-to-cluster assignment.

Divides the camera view frustum into a 3D grid of clusters (tiles_x * tiles_y * depth_slices). Each cluster stores the indices of lights that overlap it. The graphics backend dispatches compute shaders using the output buffers; this module provides the data model and assignment logic.

Depth slices use exponential distribution (log-space) matching the non-linear depth buffer, giving more resolution near the camera where it matters most.

Module Contents

Classes

ClusterConfig

Configuration for the cluster grid dimensions.

LightCluster

A single cluster cell in the 3D grid. Holds indices of overlapping lights.

ClusterGrid

3D grid of clusters dividing the view frustum.

Functions

assign_lights

Assign lights to clusters based on camera view and projection.

Data

API

simvx.core.clustered_lighting.log[source]

‘getLogger(…)’

simvx.core.clustered_lighting.__all__

[‘ClusterGrid’, ‘LightCluster’, ‘assign_lights’, ‘ClusterConfig’]

class simvx.core.clustered_lighting.ClusterConfig[source]

Configuration for the cluster grid dimensions.

tiles_x: int

16

tiles_y: int

9

depth_slices: int

24

max_lights_per_cluster: int

256

class simvx.core.clustered_lighting.LightCluster[source]

A single cluster cell in the 3D grid. Holds indices of overlapping lights.

light_indices: list[int]

‘field(…)’

property light_count: int
clear() None[source]
class simvx.core.clustered_lighting.ClusterGrid(config: simvx.core.clustered_lighting.ClusterConfig | None = None)[source]

3D grid of clusters dividing the view frustum.

The grid is addressed as (tile_x, tile_y, slice_z) where:

  • tile_x / tile_y subdivide the screen in pixel-space

  • slice_z subdivides the depth range in log-space

Attributes: config: Grid dimensions and limits. clusters: Flat array of LightCluster objects (x-major order). slice_boundaries: Precomputed view-space Z boundaries per depth slice.

Initialization

__slots__

(‘config’, ‘clusters’, ‘slice_boundaries’, ‘_total’)

index(tx: int, ty: int, sz: int) int[source]

Flat index from 3D grid coordinates (x-major: x + y * tiles_x + z * tiles_x * tiles_y).

cluster_at(tx: int, ty: int, sz: int) simvx.core.clustered_lighting.LightCluster[source]

Return the cluster at grid coordinates.

clear() None[source]

Clear all light assignments from every cluster.

rebuild(near: float, far: float) None[source]

Recompute depth slice boundaries for the given clip planes.

Uses exponential (log-space) distribution::

depth[i] = near * (far / near) ** (i / num_slices)

This concentrates slices near the camera, matching perspective depth precision.

depth_slice(view_z: float) int[source]

Return the depth slice index for a view-space Z value (positive = in front of camera).

Returns -1 if outside the frustum range.

to_light_index_buffer() numpy.ndarray[source]

Pack all cluster light indices into a flat uint32 array for GPU upload.

Returns concatenated light index lists for all clusters.

to_tile_buffer() numpy.ndarray[source]

Pack cluster metadata into an (N, 2) uint32 array: (offset, count) per cluster.

property total_clusters: int
simvx.core.clustered_lighting.assign_lights(lights: list, view_matrix: numpy.ndarray, projection_matrix: numpy.ndarray, viewport_size: tuple[int, int], grid: simvx.core.clustered_lighting.ClusterGrid) simvx.core.clustered_lighting.ClusterGrid[source]

Assign lights to clusters based on camera view and projection.

This is the CPU-side culling pass. For each light, it determines which clusters the light’s bounding volume overlaps and records the light index in those clusters.

Args: lights: List of Light3D nodes (PointLight3D, SpotLight3D, DirectionalLight3D). view_matrix: 4x4 camera view matrix (row-major numpy array). projection_matrix: 4x4 camera projection matrix (row-major numpy array). viewport_size: (width, height) in pixels. grid: ClusterGrid to populate (will be cleared first).

Returns: The same grid instance, populated with light assignments.