Screen-space global illumination

coloured light bleeding between surfaces.

▶ Run in browser

Tags: 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())