simvx.core.irradiance_volume

IrradianceVolume3D: baked diffuse global-illumination probe grid.

A box-shaped grid of irradiance probes. Each probe stores the low-frequency diffuse lighting reaching its position as spherical-harmonic L1 coefficients (12 floats). At shade time a fragment inside the volume trilinearly blends the eight surrounding probes and evaluates their SH for the surface normal, so a dynamic object moving through the volume picks up soft, direction-dependent indirect light baked from the surrounding (e.g. coloured) walls.

The volume is axis-aligned and centred on the node’s world_position; the node’s rotation is intentionally ignored for the grid (standard for irradiance volumes: the probes sample the world, not the node’s local frame).

Example::

vol = IrradianceVolume3D(extents=(6, 4, 6), spacing=2.0)
room.add_child(vol)

Capture reuses the reflection-probe cubemap machinery (a small radiance cube per probe) reduced to SH by a compute pass; update_budget probes are baked per frame so a large grid amortises over several frames. bake_mode “once” bakes the grid a single time (the common case for static level lighting); “always” re-bakes round-robin for dynamic scenes; “disabled” leaves the volume inert (feature-off, byte-identical to a scene with no volume).

Module Contents

Classes

IrradianceVolume3D

A box of baked irradiance probes that gives dynamic objects indirect light.

Data

API

simvx.core.irradiance_volume.log

‘getLogger(…)’

simvx.core.irradiance_volume.MAX_PROBES

256

simvx.core.irradiance_volume.__all__

[‘IrradianceVolume3D’, ‘MAX_PROBES’]

class simvx.core.irradiance_volume.IrradianceVolume3D(extents=None, spacing=None, **kwargs)[source]

Bases: simvx.core.nodes_3d.node3d.Node3D

A box of baked irradiance probes that gives dynamic objects indirect light.

Each probe stores the low-frequency diffuse lighting reaching its position as spherical-harmonic L1 coefficients (12 floats). At shade time a fragment inside the volume trilinearly blends the eight surrounding probes and evaluates their SH for its surface normal, so a character walking through a red-walled room picks up a soft red bounce that flat or sky ambient cannot produce. Outside the box, shading falls back to the ambient the WorldEnvironment would give it, and the transition cross-fades.

The box is axis-aligned and centred on the node’s world_position. The node’s rotation is deliberately ignored for the grid: probes sample the world, not the node’s local frame. extents are half-extents, so a volume covering a 12 x 8 x 12 room is extents=(6, 4, 6).

Probe count per axis is floor(2 * half / spacing) + 1, capped in total at MAX_PROBES: a grid that would exceed the cap is coarsened (the effective spacing is raised) so the volume still spans its full extents rather than being clipped. Halving spacing costs roughly eight times the probes, so tune it against :meth:probe_count.

Baking is amortised at update_budget probes per frame. bake_mode "once" (default) bakes the grid a single time, which is what static level lighting wants; "always" re-bakes round-robin for scenes whose lighting changes; "disabled" leaves the volume inert. Call

Meth:

request_update after moving the volume or changing the lighting to force a fresh bake.

Example::

vol = IrradianceVolume3D(extents=(6, 4, 6), spacing=2.0)
room.add_child(vol)

Initialization

extents

‘Property(…)’

spacing

‘Property(…)’

intensity

‘Property(…)’

bake_mode

‘Property(…)’

update_budget

‘Property(…)’

request_update() None[source]

Force a full re-bake of the volume on the next frame(s).

grid_dims() tuple[int, int, int][source]

Probe counts per axis, coarsened so the total stays within MAX_PROBES.

Each axis gets floor(2*half / spacing) + 1 probes (>= 1). If the product exceeds MAX_PROBES the spacing is effectively raised (counts scaled down) so the volume keeps spanning its full extents.

probe_count() int[source]
bounds_world() tuple[simvx.core.math.types.Vec3, simvx.core.math.types.Vec3][source]

World AABB of the probe centres (probe (0,0,0) .. probe (nx-1,ny-1,nz-1)).

probe_positions() numpy.ndarray[source]

World positions of every probe, shape (N, 3), index = x + nx*(y + ny*z).

get_gizmo_lines() list[tuple[simvx.core.math.types.Vec3, simvx.core.math.types.Vec3]][source]

Wireframe box for the volume extents (12 edges).

position

‘_SpatialVecProperty(…)’

rotation

‘Property(…)’

scale

‘_SpatialVecProperty(…)’

render_layer

‘Property(…)’

property rotation_degrees: simvx.core.math.types.Vec3
property world_position: simvx.core.math.types.Vec3
property world_rotation: simvx.core.math.types.Quat
property world_scale: simvx.core.math.types.Vec3
property forward: simvx.core.math.types.Vec3
property right: simvx.core.math.types.Vec3
property up: simvx.core.math.types.Vec3
translate(offset: tuple[float, float, float] | numpy.ndarray)
translate_global(offset: tuple[float, float, float] | numpy.ndarray)
rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)
rotate_x(angle: float)
rotate_y(angle: float)
rotate_z(angle: float)
look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)
face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None
set_render_layer(index: int, enabled: bool = True) None
is_on_render_layer(index: int) bool
wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

script_error_raised

‘Signal(…)’

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
reset_error() None
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) None
reparent(new_parent: simvx.core.node.Node)
node_at(path, default=_NO_DEFAULT)
find(target, *, direct: bool = False)
find_all(target, *, direct: bool = False)
expect(target, *, direct: bool = False)
ancestor(target)
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]
property path: str
property is_scene_root: bool
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
on_ready() None
on_enter_tree() None
on_exit_tree() None
on_update(dt: float) None
on_fixed_update(dt: float) None
on_draw(renderer) None
on_picked(event: simvx.core.events.InputEvent) None
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
queue_redraw() None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None
property app
property tree: simvx.core.scene_tree.SceneTree
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()