Screen-space reflections

a glossy floor mirroring the scene above it.

▶ Run in browser

Tags: 3d ssr reflections screen-space pbr

Screen-space reflections (SSR) make glossy surfaces mirror whatever else is on screen, with no cubemap or probe to place. They are a WorldEnvironment setting:

WorldEnvironment(ssr_enabled=True, ssr_intensity=1.0, ssr_max_distance=40.0)

For every glossy pixel the renderer reflects the view ray off the surface and marches it through the depth buffer until it hits something drawn this frame, then uses that pixel’s colour as the surface’s specular reflection. The trace runs at half resolution and samples a lower-resolution copy of the frame the rougher the surface is, so a satin finish reflects a softer image than a mirror. ssr_max_distance is how far a ray travels in world units, and surfaces rougher than ssr_roughness_cutoff (0.6 by default) are not traced at all.

SSR can only reflect what is on screen. Reflections fade out as they near the screen edge, and a ray that leaves the screen falls back to the ordinary ambient reflection, so nothing turns black. Turned off, the pass costs nothing.

Here a low camera looks across a floor in two finishes, a mirror on the left and satin on the right, under a row of coloured pillars and two floating shapes. Toggle SSR to compare with the ambient reflection alone.

Controls: Space / button - Toggle SSR

Source

  1# /// simvx
  2# tags = ["3d", "ssr", "reflections", "screen-space", "pbr"]
  3# screenshot_frame = 40
  4# ///
  5"""Screen-space reflections: a glossy floor mirroring the scene above it.
  6
  7Screen-space reflections (SSR) make glossy surfaces mirror whatever else is
  8on screen, with no cubemap or probe to place. They are a
  9``WorldEnvironment`` setting:
 10
 11    WorldEnvironment(ssr_enabled=True, ssr_intensity=1.0, ssr_max_distance=40.0)
 12
 13For every glossy pixel the renderer reflects the view ray off the surface and
 14marches it through the depth buffer until it hits something drawn this frame,
 15then uses that pixel's colour as the surface's specular reflection. The trace
 16runs at half resolution and samples a lower-resolution copy of the frame the
 17rougher the surface is, so a satin finish reflects a softer image than a mirror.
 18``ssr_max_distance`` is how far a ray travels in world units, and surfaces
 19rougher than ``ssr_roughness_cutoff`` (0.6 by default) are not traced at all.
 20
 21SSR can only reflect what is on screen. Reflections fade out as they near the
 22screen edge, and a ray that leaves the screen falls back to the ordinary
 23ambient reflection, so nothing turns black. Turned off, the pass costs nothing.
 24
 25Here a low camera looks across a floor in two finishes, a mirror on the left
 26and satin on the right, under a row of coloured pillars and two floating
 27shapes. Toggle SSR to compare with the ambient reflection alone.
 28
 29Controls:
 30    Space / button - Toggle SSR
 31"""
 32
 33import math
 34
 35from simvx.core import (
 36    Anchor,
 37    Button,
 38    Camera3D,
 39    CanvasLayer,
 40    DirectionalLight3D,
 41    Key,
 42    Label,
 43    Material,
 44    Mesh,
 45    MeshInstance3D,
 46    Node3D,
 47    PointLight3D,
 48    Quat,
 49    WorldEnvironment,
 50    on_input,
 51)
 52from simvx.graphics import App
 53
 54PILLAR_COLOURS = [
 55    (0.90, 0.25, 0.22),
 56    (0.20, 0.70, 0.35),
 57    (0.25, 0.45, 0.95),
 58    (0.95, 0.78, 0.18),
 59    (0.80, 0.30, 0.80),
 60]
 61
 62
 63class SSRScene(Node3D):
 64    input_context = {"ssr": [Key.SPACE]}
 65
 66    def on_ready(self):
 67        # A low, near-grazing camera stretches the reflections toward the viewer,
 68        # and Fresnel strengthens them at that glancing angle.
 69        camera = self.add_child(Camera3D(fov=52, near=0.1, far=120.0, position=(0.0, 1.7, 10.5)))
 70        camera.look_at((0.0, 1.5, -2.0))
 71        self.add_child(DirectionalLight3D(colour=(1.0, 0.96, 0.9), intensity=2.4, direction=(-0.33, -0.82, -0.47)))
 72        self.add_child(PointLight3D(colour=(0.5, 0.6, 1.0), intensity=0.8, range=30.0, position=(-5, 5, 5)))
 73        self.env = self.add_child(
 74            WorldEnvironment(
 75                ambient_light_colour=(0.12, 0.13, 0.16),
 76                ambient_light_energy=0.5,
 77                ssr_enabled=True,
 78                ssr_max_distance=40.0,
 79            )
 80        )
 81
 82        # Two floor halves, a mirror and a satin finish, both under the roughness cutoff.
 83        floor = Mesh.box((12.0, 0.2, 24.0))
 84        for x, roughness in ((-6.0, 0.05), (6.0, 0.5)):
 85            material = Material(colour=(0.5, 0.52, 0.58), metallic=0.9, roughness=roughness)
 86            self.add_child(MeshInstance3D(mesh=floor, material=material, position=(x, -0.1, 0)))
 87
 88        pillar = Mesh.box((1.1, 3.2, 1.1))
 89        for x, colour in zip((-6, -3, 0, 3, 6), PILLAR_COLOURS, strict=True):
 90            material = Material(colour=colour, metallic=0.1, roughness=0.4)
 91            self.add_child(MeshInstance3D(mesh=pillar, material=material, position=(x, 1.6, -3.0)))
 92
 93        self.add_child(
 94            MeshInstance3D(
 95                mesh=Mesh.sphere(1.1, rings=24, segments=32),
 96                material=Material(colour=(0.95, 0.55, 0.15), metallic=0.2, roughness=0.25),
 97                position=(-2.2, 1.3, 1.5),
 98            )
 99        )
100        self.add_child(
101            MeshInstance3D(
102                mesh=Mesh.box(1.6),
103                material=Material(colour=(0.15, 0.85, 0.85), metallic=0.3, roughness=0.2),
104                position=(2.4, 1.1, 2.0),
105                rotation=Quat.from_euler(0, math.radians(30), 0),
106            )
107        )
108
109        hud = self.add_child(CanvasLayer())
110        self.button = hud.add_child(Button("SSR", action="ssr", checked=True, anchor=Anchor.TOP_LEFT, inset=12))
111
112        # Example help overlay: not part of the technique shown.
113        hud.add_child(Label("Space: toggle SSR", anchor=Anchor.BOTTOM_LEFT, inset=12, font_size=14))
114
115    @on_input("ssr")
116    def toggle_ssr(self, _event):
117        self.env.ssr_enabled = not self.env.ssr_enabled
118        self.button.checked = self.env.ssr_enabled
119
120
121if __name__ == "__main__":
122    App(title="Screen-Space Reflections", width=1280, height=720).run(SSRScene())