Screen-space global illumination¶
coloured light bleeding between surfaces.
▶ Run in browserTags: 3d ssgi global-illumination colour-bleed pbr
Screen-space GI casts a half-resolution hemisphere of rays from every lit surface, marches them through the depth buffer + thin G-buffer, and gathers the on-screen scene colour they hit: one bounce of indirect diffuse. The result feeds the pluggable indirect-diffuse ambient hook, so a brightly lit red wall throws a red tint across the white floor beside it and the green wall tints the other side, all on top of the flat ambient (a Cornell-box classic). The gather is Hi-Z traced (sharing the SSR trace utilities) and temporally accumulated to denoise, so the bounce converges over a few frames; fast camera motion trails a little (an accepted tradeoff). A direction that leaves the screen or finds no near surface gathers nothing, so open floor keeps the flat ambient and nothing is crushed.
Usage: uv run python examples/features/3d/ssgi.py
Controls: Space / click / tap - Toggle SSGI on/off Escape - Quit
Source¶
1"""Screen-space global illumination: coloured light bleeding between surfaces.
2
3Screen-space GI casts a half-resolution hemisphere of rays from every
4lit surface, marches them through the depth buffer + thin G-buffer, and gathers
5the on-screen scene colour they hit: one bounce of indirect diffuse. The result
6feeds the pluggable indirect-diffuse ambient hook, so a brightly lit
7red wall throws a red tint across the white floor beside it and the green wall
8tints the other side, all on top of the flat ambient (a Cornell-box classic).
9The gather is Hi-Z traced (sharing the SSR trace utilities) and temporally
10accumulated to denoise, so the bounce converges over a few frames; fast camera
11motion trails a little (an accepted tradeoff). A direction that leaves the screen
12or finds no near surface gathers nothing, so open floor keeps the flat ambient
13and nothing is crushed.
14
15# /// simvx
16# tags = ["3d", "ssgi", "global-illumination", "colour-bleed", "pbr"]
17# screenshot_frame = 45
18# ///
19
20Usage:
21 uv run python examples/features/3d/ssgi.py
22
23Controls:
24 Space / click / tap - Toggle SSGI on/off
25 Escape - Quit
26"""
27
28import math
29
30from simvx.core import (
31 Camera3D,
32 Input,
33 InputMap,
34 Key,
35 Material,
36 Mesh,
37 MeshInstance3D,
38 MouseButton,
39 Node3D,
40 PointLight3D,
41 Quat,
42 Text2D,
43 Vec3,
44 WorldEnvironment,
45)
46from simvx.graphics import App
47
48WIDTH, HEIGHT = 1280, 720
49
50
51class SSGIScene(Node3D):
52 def __init__(self, **kwargs):
53 super().__init__(name="SSGIDemo", **kwargs)
54
55 # Camera looking down the axis into an open-fronted box, so the coloured
56 # side walls and the white floor between them fill the frame.
57 self.camera = self.add_child(Camera3D(name="Camera", fov=55, near=0.1, far=60.0))
58 self.camera.position = Vec3(0.0, 3.0, 9.5)
59 self.camera.look_at(Vec3(0.0, 2.6, -2.0))
60
61 # A single bright point light near the ceiling: it lights the coloured
62 # walls directly, and SSGI carries their colour onto the neutral surfaces.
63 lamp = self.add_child(PointLight3D(name="Lamp", position=Vec3(0.0, 5.4, 1.0)))
64 lamp.colour = (1.0, 0.97, 0.92)
65 lamp.intensity = 20.0
66 lamp.range = 24.0
67 lamp.shadows = True # sole key light: cast shadows (point shadows are opt-in)
68
69 # Cornell-style box: white floor / ceiling / back wall, a saturated red
70 # left wall and green right wall (matte, so they bounce diffusely). Rough,
71 # non-metallic surfaces so the effect is pure indirect diffuse.
72 white = Material(colour=(0.80, 0.80, 0.80), metallic=0.0, roughness=0.9)
73 red = Material(colour=(0.85, 0.08, 0.08), metallic=0.0, roughness=0.9)
74 green = Material(colour=(0.08, 0.75, 0.12), metallic=0.0, roughness=0.9)
75
76 span = 6.0 # interior half-width / height
77 thick = 0.3
78
79 def wall(name, mat, position, scale):
80 self.add_child(
81 MeshInstance3D(
82 name=name,
83 mesh=Mesh.cube(1.0),
84 material=mat,
85 position=position,
86 scale=scale,
87 )
88 )
89
90 wall("Floor", white, Vec3(0, 0, -1), Vec3(span * 2, thick, span * 2))
91 wall("Ceiling", white, Vec3(0, span, -1), Vec3(span * 2, thick, span * 2))
92 wall("BackWall", white, Vec3(0, span * 0.5, -1 - span), Vec3(span * 2, span * 2, thick))
93 wall("LeftWall", red, Vec3(-span, span * 0.5, -1), Vec3(thick, span * 2, span * 2))
94 wall("RightWall", green, Vec3(span, span * 0.5, -1), Vec3(thick, span * 2, span * 2))
95
96 # Two white blocks standing on the floor: they catch the coloured bleed on
97 # their inward faces and cast contact darkening where they meet the floor.
98 self.add_child(
99 MeshInstance3D(
100 name="TallBlock",
101 mesh=Mesh.cube(1.0),
102 material=white,
103 position=Vec3(-2.2, 1.8, -2.6),
104 scale=Vec3(2.0, 3.6, 2.0),
105 rotation=Quat.from_euler(0, math.radians(18), 0),
106 )
107 )
108 self.add_child(
109 MeshInstance3D(
110 name="ShortBlock",
111 mesh=Mesh.cube(1.0),
112 material=white,
113 position=Vec3(2.0, 1.0, -0.4),
114 scale=Vec3(2.0, 2.0, 2.0),
115 rotation=Quat.from_euler(0, math.radians(-20), 0),
116 )
117 )
118
119 self._env = self.add_child(WorldEnvironment(name="Env"))
120 # Flat, dim ambient so the colour bleed reads clearly against it (no IBL:
121 # SSGI is purely additive here, so it can never crush the image).
122 self._env.ambient_light_colour = (0.10, 0.10, 0.12)
123 self._env.ambient_light_energy = 0.5
124 self._env.ssgi_enabled = True
125 self._env.ssgi_intensity = 0.85
126 self._env.ssgi_max_distance = 12.0
127
128 self._title = self.add_child(Text2D(text="SCREEN-SPACE GLOBAL ILLUMINATION", position=(10, 8), font_scale=1.5))
129 self._status = self.add_child(Text2D(text="SSGI: ON", position=(10, 40), font_scale=1.3))
130 self._controls = self.add_child(
131 Text2D(text="SPACE/TAP:Toggle SSGI ESC:Quit", position=(10, HEIGHT - 30), font_scale=1.1)
132 )
133
134 def on_ready(self):
135 InputMap.add_action("toggle_ssgi", [Key.SPACE])
136 InputMap.add_action("quit", [Key.ESCAPE])
137
138 def on_update(self, dt: float):
139 # Space or a click/tap flips the Property directly (touch arrives as
140 # MouseButton.LEFT on web, so the demo stays usable on phones).
141 if Input.is_action_just_pressed("toggle_ssgi") or Input.is_mouse_button_just_pressed(MouseButton.LEFT):
142 self._env.ssgi_enabled = not self._env.ssgi_enabled
143 self._status.text = f"SSGI: {'ON' if self._env.ssgi_enabled else 'OFF'}"
144 if Input.is_action_just_pressed("quit"):
145 self.app.quit()
146 # Pin the controls hint to the live viewport bottom (resize-aware).
147 self._controls.position = (10, self.app.height - 30)
148
149
150def main():
151 scene = SSGIScene()
152 app = App(title="SimVX SSGI Demo", width=WIDTH, height=HEIGHT)
153 app.run(scene)
154
155
156if __name__ == "__main__":
157 main()