Screen-space global illumination¶
coloured light bleeding between surfaces.
▶ Run in browserTags: 3d ssgi global-illumination colour-bleed pbr
Screen-space GI (design D8) 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 (design D8), 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 - Toggle SSGI on/off Escape - Quit
Source¶
1"""Screen-space global illumination: coloured light bleeding between surfaces.
2
3Screen-space GI (design D8) 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 (design D8), 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 - 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 Node3D,
39 PointLight3D,
40 Quat,
41 Text2D,
42 Vec3,
43 WorldEnvironment,
44)
45from simvx.graphics import App
46
47WIDTH, HEIGHT = 1280, 720
48
49
50class SSGIScene(Node3D):
51 def __init__(self, **kwargs):
52 super().__init__(name="SSGIDemo", **kwargs)
53
54 # Camera looking down the axis into an open-fronted box, so the coloured
55 # side walls and the white floor between them fill the frame.
56 self.camera = self.add_child(Camera3D(name="Camera", fov=55, near=0.1, far=60.0))
57 self.camera.position = Vec3(0.0, 3.0, 9.5)
58 self.camera.look_at(Vec3(0.0, 2.6, -2.0))
59
60 # A single bright point light near the ceiling: it lights the coloured
61 # walls directly, and SSGI carries their colour onto the neutral surfaces.
62 lamp = self.add_child(PointLight3D(name="Lamp", position=Vec3(0.0, 5.4, 1.0)))
63 lamp.colour = (1.0, 0.97, 0.92)
64 lamp.intensity = 20.0
65 lamp.range = 24.0
66 lamp.shadows = True # sole key light: cast shadows (point shadows are opt-in)
67
68 # Cornell-style box: white floor / ceiling / back wall, a saturated red
69 # left wall and green right wall (matte, so they bounce diffusely). Rough,
70 # non-metallic surfaces so the effect is pure indirect diffuse.
71 white = Material(colour=(0.80, 0.80, 0.80), metallic=0.0, roughness=0.9)
72 red = Material(colour=(0.85, 0.08, 0.08), metallic=0.0, roughness=0.9)
73 green = Material(colour=(0.08, 0.75, 0.12), metallic=0.0, roughness=0.9)
74
75 span = 6.0 # interior half-width / height
76 thick = 0.3
77
78 def wall(name, mat, position, scale):
79 self.add_child(
80 MeshInstance3D(
81 name=name,
82 mesh=Mesh.cube(1.0),
83 material=mat,
84 position=position,
85 scale=scale,
86 )
87 )
88
89 wall("Floor", white, Vec3(0, 0, -1), Vec3(span * 2, thick, span * 2))
90 wall("Ceiling", white, Vec3(0, span, -1), Vec3(span * 2, thick, span * 2))
91 wall("BackWall", white, Vec3(0, span * 0.5, -1 - span), Vec3(span * 2, span * 2, thick))
92 wall("LeftWall", red, Vec3(-span, span * 0.5, -1), Vec3(thick, span * 2, span * 2))
93 wall("RightWall", green, Vec3(span, span * 0.5, -1), Vec3(thick, span * 2, span * 2))
94
95 # Two white blocks standing on the floor: they catch the coloured bleed on
96 # their inward faces and cast contact darkening where they meet the floor.
97 self.add_child(
98 MeshInstance3D(
99 name="TallBlock",
100 mesh=Mesh.cube(1.0),
101 material=white,
102 position=Vec3(-2.2, 1.8, -2.6),
103 scale=Vec3(2.0, 3.6, 2.0),
104 rotation=Quat.from_euler(0, math.radians(18), 0),
105 )
106 )
107 self.add_child(
108 MeshInstance3D(
109 name="ShortBlock",
110 mesh=Mesh.cube(1.0),
111 material=white,
112 position=Vec3(2.0, 1.0, -0.4),
113 scale=Vec3(2.0, 2.0, 2.0),
114 rotation=Quat.from_euler(0, math.radians(-20), 0),
115 )
116 )
117
118 self._ssgi_on = True
119 self._cooldown = 0.0
120 self._env = self.add_child(WorldEnvironment(name="Env"))
121 # Flat, dim ambient so the colour bleed reads clearly against it (no IBL:
122 # SSGI is purely additive here, so it can never crush the image).
123 self._env.ambient_light_colour = (0.10, 0.10, 0.12)
124 self._env.ambient_light_energy = 0.5
125 self._env.ssgi_enabled = True
126 self._env.ssgi_intensity = 0.85
127 self._env.ssgi_max_distance = 12.0
128
129 self._title = self.add_child(Text2D(text="SCREEN-SPACE GLOBAL ILLUMINATION", position=(10, 8), font_scale=1.5))
130 self._status = self.add_child(Text2D(text="SSGI: ON", position=(10, 40), font_scale=1.3))
131 self.add_child(Text2D(text="SPACE:Toggle SSGI ESC:Quit", position=(10, 690), font_scale=1.1))
132
133 def on_ready(self):
134 InputMap.add_action("toggle_ssgi", [Key.SPACE])
135 InputMap.add_action("quit", [Key.ESCAPE])
136
137 def on_update(self, dt: float):
138 self._cooldown = max(0.0, self._cooldown - dt)
139 if Input.is_action_just_pressed("toggle_ssgi") and self._cooldown <= 0.0:
140 self._ssgi_on = not self._ssgi_on
141 self._cooldown = 0.3
142 self._env.ssgi_enabled = self._ssgi_on
143 self._status.text = f"SSGI: {'ON' if self._ssgi_on else 'OFF'}"
144 if Input.is_action_just_pressed("quit"):
145 self.app.quit()
146
147
148def main():
149 scene = SSGIScene()
150 app = App(title="SimVX SSGI Demo", width=WIDTH, height=HEIGHT, physics_fps=60)
151 app.run(scene)
152
153
154if __name__ == "__main__":
155 main()