simvx.graphics.renderer.point_shadow_pass

Point and spot light shadow map rendering pass.

Point lights use a 6-face cubemap (rendered as 6 views into one atlas row). Spot lights use a single 2D depth image with a perspective projection matching the cone angle.

Both atlases carry one row per shadow caster slot, so several point and several spot lights can cast at once: the point atlas is 6 x N faces, the spot atlas N squares stacked vertically. WorldEnvironment.shadow_caster_count sets N, which also rides the shadow SSBO as the row divide the forward shader applies.

A row keeps whatever was last recorded into it: a render-pass instance covers only the rows it is given casters for, its clear is scoped to that same area, and the colour atlas comes into the instance as SHADER_READ_ONLY_OPTIMAL rather than UNDEFINED, so nothing discards the rows outside it. That is what lets the caller hand this pass the casters whose maps went stale and leave the rest of the atlas alone (see :class:~.shadow_cache.ShadowRowCache).

Shadow depth textures are registered in the bindless texture array so the forward fragment shader can sample them via integer index.

Module Contents

Classes

PointShadowPass

Renders depth from point/spot light POVs into shadow map textures.

Data

API

simvx.graphics.renderer.point_shadow_pass.__all__

[‘PointShadowPass’]

simvx.graphics.renderer.point_shadow_pass.log

‘getLogger(…)’

simvx.graphics.renderer.point_shadow_pass.POINT_SHADOW_SIZE

512

simvx.graphics.renderer.point_shadow_pass.SPOT_SHADOW_SIZE

1024

simvx.graphics.renderer.point_shadow_pass.DEPTH_FORMAT

None

simvx.graphics.renderer.point_shadow_pass.COLOR_FORMAT

None

class simvx.graphics.renderer.point_shadow_pass.PointShadowPass(engine: Any)

Renders depth from point/spot light POVs into shadow map textures.

Point casters: a 6 x N face atlas (6 x POINT_SHADOW_SIZE wide, one POINT_SHADOW_SIZE row per caster slot). Spot casters: N stacked SPOT_SHADOW_SIZE squares, one per caster slot.

Uses a colour attachment (R32_SFLOAT) to store linear distance from light, plus a depth attachment for correct Z-testing during rendering.

Initialization

__slots__

(‘_engine’, ‘_caster_count’, ‘_max_caster_count’, ‘_point_render_pass’, ‘_point_framebuffer’, ‘_poin…

begin_frame() None

Open a new frame: nothing of this pass has been recorded into it yet.

property caster_count: int

Shadow caster slots the atlases currently hold a row for.

property atlas_generation: int

Serial number of the atlas images currently allocated.

A caller that skips re-recording a row is relying on the image behind it surviving; a resize replaces both images, so a change here means every row is empty again whatever else says otherwise. The numbers are drawn from one process-wide counter, so a fresh pass cannot hand back a number an older one used and be believed.

property max_caster_count: int

Caster slots this DEVICE can hold rows for, at most.

MAX_SHADOW_CASTERS is what the shadow SSBO can address; this is that capped by what the device will allocate, measured in :meth:setup from maxImageDimension2D and maxFramebuffer{Width,Height}. The spot atlas is the binding one: it stacks SPOT_SHADOW_SIZE rows, so eight casters need an 8192 px image where Vulkan only guarantees 4096.

setup(ssbo_layout: Any) None

Initialize point and spot shadow map resources.

The atlases start at :data:DEFAULT_SHADOW_CASTER_COUNT rows;

Meth:

set_caster_count resizes them when the scene’s WorldEnvironment.shadow_caster_count says otherwise.

set_caster_count(count: int) None

Resize both atlases to hold count caster rows.

Clamped to 1..:attr:max_caster_count; a no-op when unchanged, so the per-frame quality-dial call costs one comparison. A real change drains the device and reallocates the atlases, which is why this is a quality setting rather than something to drive per frame, and why it must not be called once this pass has recorded: the atlases the recorded commands will read are destroyed here. The renderer applies it from sync_render_state, which runs before anything is recorded into the frame’s command buffer, and the assertion below holds that ordering to its word. The render passes, pipelines and bindless indices survive: only the images behind them change size.

property point_shadow_texture_index: int

Bindless index of the point shadow atlas texture.

property spot_shadow_texture_index: int

Bindless index of the spot shadow depth texture.

render_point_shadows(cmd: Any, casters: list, instances: list, ssbo_set: Any, mesh_registry: Any, multimesh_draws: list | None = None, materials: numpy.ndarray | None = None) None

Render the given point casters’ 6 cubemap faces into the atlas.

casters is a list of (slot, light_pos, light_range): each caster owns one POINT_SHADOW_SIZE row of the atlas and each of its faces a horizontal slice of that row. Linear distance from the light is written to the R32F colour attachment. MultiMesh blocks are drawn via instanced draws (first_instance=base).

Only the rows casters names are touched. Each run of adjacent rows opens one render-pass instance over exactly those rows: the clear is scoped to the same area, and the colour attachment comes in already in SHADER_READ_ONLY_OPTIMAL rather than UNDEFINED, so a row left out keeps the map recorded into it on an earlier frame. Handing over every caster is therefore the one-instance, whole-atlas pass this always was; handing over the stale ones is the static-shadow cache.

Alpha-tested casters (blend="cutoff") are routed through the cutout pipeline so their shadow shows the cutout; with none present the opaque draw set is byte-identical to the plain opaque pass.

render_spot_shadows(cmd: Any, casters: list, instances: list, ssbo_set: Any, mesh_registry: Any, multimesh_draws: list | None = None, materials: numpy.ndarray | None = None) None

Render the given spot casters’ perspective shadow maps into the atlas.

casters is a list of (slot, light_pos, light_dir, fov, range); each caster owns one SPOT_SHADOW_SIZE square of the vertically stacked atlas. fov is the outer cone half-angle in degrees. As with the point atlas, each run of adjacent rows is one render-pass instance scoped to those rows, and a row no caster names keeps the map it holds.

multimesh_draws are drawn as instanced blocks (first_instance=base); alpha-tested casters route through the cutout pipeline.

get_spot_vp_matrix(light_pos: numpy.ndarray, light_dir: numpy.ndarray, fov: float, light_range: float) numpy.ndarray

Compute the VP matrix for a spot light (for fragment shader sampling).

cleanup() None

Release all GPU resources.