Screen-space global illumination¶
coloured light bleeding between surfaces.
▶ Run in browserTags: 3d ssgi global-illumination colour-bleed pbr
Global illumination is light that arrives after bouncing off another surface.
Screen-space GI (SSGI) approximates one such bounce from what is already on
screen, and one switch on the WorldEnvironment turns it on:
WorldEnvironment(ssgi_enabled=True, ssgi_intensity=0.85, ssgi_max_distance=12.0)
At half resolution, every visible surface casts a hemisphere of rays through
the depth buffer and gathers the scene colour where they land. That gathered
light is added on top of the ambient light, so a brightly lit red wall throws a
red tint across the white floor beside it. ssgi_intensity scales the bounce
and ssgi_max_distance is how far, in world units, a ray may travel to find
a surface. The result is accumulated over several frames to remove noise, so
it settles a moment after the view changes and fast camera motion leaves a
slight trail. A ray that leaves the screen or finds nothing gathers nothing:
open floor keeps the plain ambient, so SSGI only ever adds light. With SSGI off
the pass does not run at all.
The scene is a Cornell box: a white room with a red left wall and a green right wall, lit by one point light near the ceiling, with a dim flat ambient so the bleed onto the floor and the two white blocks reads clearly. All surfaces are rough and non-metallic, so the effect is purely diffuse.
Controls: Space / button - Toggle SSGI
Source¶
1# /// simvx
2# tags = ["3d", "ssgi", "global-illumination", "colour-bleed", "pbr"]
3# screenshot_frame = 45
4# ///
5"""Screen-space global illumination: coloured light bleeding between surfaces.
6
7Global illumination is light that arrives after bouncing off another surface.
8Screen-space GI (SSGI) approximates one such bounce from what is already on
9screen, and one switch on the ``WorldEnvironment`` turns it on:
10
11 WorldEnvironment(ssgi_enabled=True, ssgi_intensity=0.85, ssgi_max_distance=12.0)
12
13At half resolution, every visible surface casts a hemisphere of rays through
14the depth buffer and gathers the scene colour where they land. That gathered
15light is added on top of the ambient light, so a brightly lit red wall throws a
16red tint across the white floor beside it. ``ssgi_intensity`` scales the bounce
17and ``ssgi_max_distance`` is how far, in world units, a ray may travel to find
18a surface. The result is accumulated over several frames to remove noise, so
19it settles a moment after the view changes and fast camera motion leaves a
20slight trail. A ray that leaves the screen or finds nothing gathers nothing:
21open floor keeps the plain ambient, so SSGI only ever adds light. With SSGI off
22the pass does not run at all.
23
24The scene is a Cornell box: a white room with a red left wall and a green right
25wall, lit by one point light near the ceiling, with a dim flat ambient so the
26bleed onto the floor and the two white blocks reads clearly. All surfaces are
27rough and non-metallic, so the effect is purely diffuse.
28
29Controls:
30 Space / button - Toggle SSGI
31"""
32
33import math
34
35from simvx.core import (
36 Anchor,
37 Button,
38 Camera3D,
39 CanvasLayer,
40 Key,
41 Material,
42 Mesh,
43 MeshInstance3D,
44 Node3D,
45 PointLight3D,
46 Quat,
47 WorldEnvironment,
48 on_input,
49)
50from simvx.graphics import App
51
52SPAN = 6.0 # interior half-width and full height of the room
53THICK = 0.3
54
55
56class SSGIScene(Node3D):
57 input_context = {"ssgi": [Key.SPACE]}
58
59 def on_ready(self):
60 # Looking into the open front of the box, so both coloured walls and the floor fill the frame.
61 camera = self.add_child(Camera3D(fov=55, near=0.1, far=60.0, position=(0.0, 3.0, 9.5)))
62 camera.look_at((0.0, 2.6, -2.0))
63
64 # The one light: it lights the coloured walls directly and SSGI carries their colour onward.
65 self.add_child(
66 PointLight3D(colour=(1.0, 0.97, 0.92), intensity=20.0, range=24.0, shadows=True, position=(0, 5.4, 1))
67 )
68
69 white = Material(colour=(0.80, 0.80, 0.80), roughness=0.9)
70 red = Material(colour=(0.85, 0.08, 0.08), roughness=0.9)
71 green = Material(colour=(0.08, 0.75, 0.12), roughness=0.9)
72 walls = [
73 (white, (0, 0, -1), (SPAN * 2, THICK, SPAN * 2)), # floor
74 (white, (0, SPAN, -1), (SPAN * 2, THICK, SPAN * 2)), # ceiling
75 (white, (0, SPAN / 2, -1 - SPAN), (SPAN * 2, SPAN * 2, THICK)), # back
76 (red, (-SPAN, SPAN / 2, -1), (THICK, SPAN * 2, SPAN * 2)), # left
77 (green, (SPAN, SPAN / 2, -1), (THICK, SPAN * 2, SPAN * 2)), # right
78 ]
79 for material, position, size in walls:
80 self.add_child(MeshInstance3D(mesh=Mesh.box(size), material=material, position=position))
81
82 # Two white blocks catch the coloured bleed on their inward faces.
83 for size, position, yaw in [((2.0, 3.6, 2.0), (-2.2, 1.8, -2.6), 18), ((2.0, 2.0, 2.0), (2.0, 1.0, -0.4), -20)]:
84 rotation = Quat.from_euler(0, math.radians(yaw), 0)
85 self.add_child(MeshInstance3D(mesh=Mesh.box(size), material=white, position=position, rotation=rotation))
86
87 # A dim flat ambient colour, so the bounce is what colours the white surfaces.
88 self.env = self.add_child(
89 WorldEnvironment(
90 ambient_light_colour=(0.10, 0.10, 0.12),
91 ambient_light_energy=0.5,
92 ssgi_enabled=True,
93 ssgi_intensity=0.85,
94 ssgi_max_distance=12.0,
95 )
96 )
97
98 hud = self.add_child(CanvasLayer())
99 self.button = hud.add_child(Button("SSGI", action="ssgi", checked=True, anchor=Anchor.TOP_LEFT, inset=12))
100
101 @on_input("ssgi")
102 def toggle_ssgi(self, _event):
103 self.env.ssgi_enabled = not self.env.ssgi_enabled
104 self.button.checked = self.env.ssgi_enabled
105
106
107if __name__ == "__main__":
108 App(title="Screen-Space Global Illumination", width=1280, height=720).run(SSGIScene())