Screen-space reflections

a glossy floor mirroring the scene above it.

▶ Run in browser

Tags: 3d ssr reflections screen-space pbr

Screen-space reflections trace the reflected view ray through the depth buffer + thin G-buffer and feed the result into the pluggable indirect-specular ambient hook, so the glossy floor mirrors the coloured pillars and floating shapes standing on it without any cubemap or probe. The reflection is Hi-Z traced at half resolution and roughness-aware: the mirror floor gives a sharp reflection, the frosted panel a blurred one, and reflections fade as they approach the screen edge (the on-screen-only limit of SSR). A ray that leaves the screen falls back to the flat/IBL ambient, so nothing goes black.

Usage: uv run python examples/features/3d/ssr.py

Controls: Space / click / tap - Toggle SSR on/off Escape - Quit

Source

  1"""Screen-space reflections: a glossy floor mirroring the scene above it.
  2
  3Screen-space reflections trace the reflected view ray through the
  4depth buffer + thin G-buffer and feed the result into the pluggable
  5indirect-specular ambient hook, so the glossy floor mirrors the
  6coloured pillars and floating shapes standing on it without any cubemap or probe.
  7The reflection is Hi-Z traced at half resolution and roughness-aware: the mirror
  8floor gives a sharp reflection, the frosted panel a blurred one, and reflections
  9fade as they approach the screen edge (the on-screen-only limit of SSR). A ray
 10that leaves the screen falls back to the flat/IBL ambient, so nothing goes black.
 11
 12# /// simvx
 13# tags = ["3d", "ssr", "reflections", "screen-space", "pbr"]
 14# screenshot_frame = 40
 15# ///
 16
 17Usage:
 18    uv run python examples/features/3d/ssr.py
 19
 20Controls:
 21    Space / click / tap - Toggle SSR on/off
 22    Escape              - Quit
 23"""
 24
 25import math
 26
 27from simvx.core import (
 28    Camera3D,
 29    DirectionalLight3D,
 30    Input,
 31    InputMap,
 32    Key,
 33    Material,
 34    Mesh,
 35    MeshInstance3D,
 36    MouseButton,
 37    Node3D,
 38    PointLight3D,
 39    Quat,
 40    Text2D,
 41    Vec3,
 42    WorldEnvironment,
 43)
 44from simvx.graphics import App
 45
 46WIDTH, HEIGHT = 1280, 720
 47
 48
 49class SSRScene(Node3D):
 50    def __init__(self, **kwargs):
 51        super().__init__(name="SSRDemo", **kwargs)
 52
 53        # A low, near-grazing camera: reflections stretch across the floor toward
 54        # the viewer (the classic wet-street SSR look) and the Fresnel term boosts
 55        # the reflection at the glancing angle.
 56        self.camera = self.add_child(Camera3D(name="Camera", fov=52, near=0.1, far=120.0))
 57        self.camera.position = Vec3(0.0, 1.7, 10.5)
 58        self.camera.look_at(Vec3(0.0, 1.5, -2.0))
 59
 60        sun = self.add_child(DirectionalLight3D(name="Sun"))
 61        sun.colour = (1.0, 0.96, 0.9)
 62        sun.intensity = 2.4
 63        sun.rotation = Quat.from_euler(math.radians(-55), math.radians(35), 0)
 64
 65        fill = self.add_child(PointLight3D(name="Fill", position=Vec3(-5, 5, 5)))
 66        fill.colour = (0.5, 0.6, 1.0)
 67        fill.intensity = 0.8
 68        fill.range = 30.0
 69
 70        # Semi-metallic, near-mirror floor: low roughness so SSR reads sharply,
 71        # a touch of metalness so the reflection is visible across viewing angles.
 72        floor_mat = Material(colour=(0.5, 0.52, 0.58), metallic=0.9, roughness=0.06)
 73        self.add_child(
 74            MeshInstance3D(
 75                name="Floor",
 76                mesh=Mesh.cube(1.0),
 77                material=floor_mat,
 78                position=Vec3(0, -0.1, 0),
 79                scale=Vec3(24, 0.2, 24),
 80            )
 81        )
 82
 83        # Colourful pillars + shapes standing on the floor, so their reflection is
 84        # unmistakable in the mirror below.
 85        palette = [
 86            (0.90, 0.25, 0.22),
 87            (0.20, 0.70, 0.35),
 88            (0.25, 0.45, 0.95),
 89            (0.95, 0.78, 0.18),
 90            (0.80, 0.30, 0.80),
 91        ]
 92        for i, x in enumerate([-6, -3, 0, 3, 6]):
 93            mat = Material(colour=palette[i], metallic=0.1, roughness=0.4)
 94            self.add_child(
 95                MeshInstance3D(
 96                    name=f"Pillar{i}",
 97                    mesh=Mesh.cube(1.0),
 98                    material=mat,
 99                    position=Vec3(x, 1.6, -3.0),
100                    scale=Vec3(1.1, 3.2, 1.1),
101                )
102            )
103
104        # Floating shapes above the floor centre.
105        self.add_child(
106            MeshInstance3D(
107                name="Sphere",
108                mesh=Mesh.sphere(1.1, rings=24, segments=32),
109                material=Material(colour=(0.95, 0.55, 0.15), metallic=0.2, roughness=0.25),
110                position=Vec3(-2.2, 1.3, 1.5),
111            )
112        )
113        self.add_child(
114            MeshInstance3D(
115                name="Cube",
116                mesh=Mesh.cube(1.6),
117                material=Material(colour=(0.15, 0.85, 0.85), metallic=0.3, roughness=0.2),
118                position=Vec3(2.4, 1.1, 2.0),
119                rotation=Quat.from_euler(0, math.radians(30), 0),
120            )
121        )
122        # A frosted (rougher) panel to show roughness-blurred reflections.
123        self.add_child(
124            MeshInstance3D(
125                name="Panel",
126                mesh=Mesh.cube(1.0),
127                material=Material(colour=(0.85, 0.85, 0.9), metallic=0.1, roughness=0.35),
128                position=Vec3(0.0, 2.2, -5.4),
129                scale=Vec3(6.0, 3.2, 0.3),
130            )
131        )
132
133        self._env = self.add_child(WorldEnvironment(name="Env"))
134        self._env.ambient_light_colour = (0.12, 0.13, 0.16)
135        self._env.ambient_light_energy = 0.5
136        self._env.ssr_enabled = True
137        self._env.ssr_intensity = 1.0
138        self._env.ssr_max_distance = 40.0
139
140        self._title = self.add_child(Text2D(text="SCREEN-SPACE REFLECTIONS", position=(10, 8), font_scale=1.5))
141        self._status = self.add_child(Text2D(text="SSR: ON", position=(10, 40), font_scale=1.3))
142        self._controls = self.add_child(
143            Text2D(text="SPACE/TAP:Toggle SSR   ESC:Quit", position=(10, HEIGHT - 30), font_scale=1.1)
144        )
145
146    def on_ready(self):
147        InputMap.add_action("toggle_ssr", [Key.SPACE])
148        InputMap.add_action("quit", [Key.ESCAPE])
149
150    def on_update(self, dt: float):
151        # Space or a click/tap flips the Property directly (touch arrives as
152        # MouseButton.LEFT on web, so the demo stays usable on phones).
153        if Input.is_action_just_pressed("toggle_ssr") or Input.is_mouse_button_just_pressed(MouseButton.LEFT):
154            self._env.ssr_enabled = not self._env.ssr_enabled
155            self._status.text = f"SSR: {'ON' if self._env.ssr_enabled else 'OFF'}"
156        if Input.is_action_just_pressed("quit"):
157            self.app.quit()
158        # Pin the controls hint to the live viewport bottom (resize-aware).
159        self._controls.position = (10, self.app.height - 30)
160
161
162def main():
163    scene = SSRScene()
164    app = App(title="SimVX SSR Demo", width=WIDTH, height=HEIGHT)
165    app.run(scene)
166
167
168if __name__ == "__main__":
169    main()