Irradiance Probe Volume¶
baked diffuse light that bleeds wall colour onto a moving object.
▶ Run in browserTags: 3d gi irradiance-volume probe colour-bleed pbr
An IrradianceVolume3D is a box-shaped grid of light probes. Each probe
records the diffuse light arriving at its position from every direction, as
spherical-harmonic coefficients. A surface inside the box blends the eight
probes around it and reads the light coming from the way it faces, so an
object moving through the room picks up soft bounce light from the walls:
IrradianceVolume3D(extents=(4.4, 3.2, 4.4), spacing=1.8, position=(0, 3, -1))
extents are half-extents of the box, centred on the node, and spacing
is the distance between probes. intensity scales the indirect light, and
update_budget is how many probes are baked per frame, so a large grid
spreads its bake over the first few frames. bake_mode chooses when it
bakes: "once" (the default, for static lighting), "always" (re-bakes
continuously for lighting that changes) or "disabled" (the volume does
nothing). Call request_update() after moving the volume or its lights.
Here a plain grey sphere glides between a red wall and a green wall. It has no colour of its own to show, so the red tint it takes on near the red wall and the green tint near the green wall come from the baked probes. The ambient light is kept very low so the bounce is the main fill. Unlike screen-space GI, the volume keeps working when the walls are off screen, and unlike a reflection probe it gives a soft diffuse term that suits matte surfaces.
Controls: Space / Volume on/off - Toggle the volume
Source¶
1# /// simvx
2# tags = ["3d", "gi", "irradiance-volume", "probe", "colour-bleed", "pbr"]
3# screenshot_frame = 45
4# ///
5"""Irradiance Probe Volume: baked diffuse light that bleeds wall colour onto a moving object.
6
7An ``IrradianceVolume3D`` is a box-shaped grid of light probes. Each probe
8records the diffuse light arriving at its position from every direction, as
9spherical-harmonic coefficients. A surface inside the box blends the eight
10probes around it and reads the light coming from the way it faces, so an
11object moving through the room picks up soft bounce light from the walls:
12
13 IrradianceVolume3D(extents=(4.4, 3.2, 4.4), spacing=1.8, position=(0, 3, -1))
14
15``extents`` are half-extents of the box, centred on the node, and ``spacing``
16is the distance between probes. ``intensity`` scales the indirect light, and
17``update_budget`` is how many probes are baked per frame, so a large grid
18spreads its bake over the first few frames. ``bake_mode`` chooses when it
19bakes: ``"once"`` (the default, for static lighting), ``"always"`` (re-bakes
20continuously for lighting that changes) or ``"disabled"`` (the volume does
21nothing). Call ``request_update()`` after moving the volume or its lights.
22
23Here a plain grey sphere glides between a red wall and a green wall. It has
24no colour of its own to show, so the red tint it takes on near the red wall
25and the green tint near the green wall come from the baked probes. The
26ambient light is kept very low so the bounce is the main fill. Unlike
27screen-space GI, the volume keeps working when the walls are off screen, and
28unlike a reflection probe it gives a soft diffuse term that suits matte
29surfaces.
30
31Controls:
32 Space / Volume on/off - Toggle the volume
33"""
34
35import math
36
37from simvx.core import (
38 Anchor,
39 Button,
40 Camera3D,
41 CanvasLayer,
42 DirectionalLight3D,
43 IrradianceVolume3D,
44 Key,
45 Label,
46 Material,
47 Mesh,
48 MeshInstance3D,
49 Node3D,
50 WorldEnvironment,
51 on_input,
52)
53from simvx.graphics import App
54
55
56class ProbeVolumeDemo(Node3D):
57 input_context = {"toggle_volume": [Key.SPACE]}
58
59 def on_ready(self):
60 camera = self.add_child(Camera3D(fov=55, near=0.1, far=60.0, position=(0, 2.8, 9)))
61 camera.look_at((0, 2.2, -2))
62 self.add_child(DirectionalLight3D(colour=(1.0, 0.97, 0.92), intensity=1.7, direction=(-0.1, -0.94, -0.32)))
63 self.add_child(WorldEnvironment(ambient_light_colour=(0.04, 0.04, 0.05), ambient_light_energy=0.5))
64
65 # An open-fronted box: white floor, ceiling and back, a red left wall and a green right wall.
66 white = Material(colour=(0.8, 0.8, 0.8), roughness=0.9)
67 red = Material(colour=(0.9, 0.06, 0.06), roughness=1.0)
68 green = Material(colour=(0.06, 0.85, 0.1), roughness=1.0)
69 for size, position, material in [
70 ((10, 0.3, 10), (0, 0, -1), white),
71 ((10, 0.3, 10), (0, 5, -1), white),
72 ((10, 10, 0.3), (0, 2.5, -6), white),
73 ((0.3, 10, 10), (-5, 2.5, -1), red),
74 ((0.3, 10, 10), (5, 2.5, -1), green),
75 ]:
76 self.add_child(MeshInstance3D(mesh=Mesh.box(size), material=material, position=position))
77
78 grey = Material(colour=(0.85, 0.85, 0.85), roughness=0.85)
79 self.sphere = self.add_child(MeshInstance3D(mesh=Mesh.sphere(1.3, segments=48), material=grey))
80
81 self.volume = self.add_child(
82 IrradianceVolume3D(
83 extents=(4.4, 3.2, 4.4), spacing=1.8, position=(0, 3, -1), intensity=2.4, update_budget=24
84 )
85 )
86
87 hud = self.add_child(CanvasLayer())
88 self.status = hud.add_child(Label(anchor=Anchor.TOP_LEFT, inset=10, font_size=20))
89
90 # Example help overlay: not part of the technique shown.
91 hud.add_child(Button("Volume on/off", action="toggle_volume", anchor=Anchor.BOTTOM_RIGHT, inset=10))
92 hud.add_child(Label("Space: toggle the volume", anchor=Anchor.BOTTOM_LEFT, inset=10, font_size=14))
93
94 @on_input("toggle_volume")
95 def toggle_volume(self, _event):
96 self.volume.bake_mode = "disabled" if self.volume.bake_mode == "once" else "once"
97
98 def on_update(self, dt):
99 # Sweep from the red wall through the middle to the green wall and back.
100 self.sphere.position = (-2.8 * math.sin(self.tree.now * 0.9), 2.2, -1.0)
101 on = self.volume.bake_mode != "disabled"
102 self.status.text = f"Irradiance volume: {'ON' if on else 'OFF'}"
103
104
105if __name__ == "__main__":
106 App(title="Irradiance Probe Volume", width=1280, height=720).run(ProbeVolumeDemo())