Screen-space reflections¶
a glossy floor mirroring the scene above it.
▶ Run in browserTags: 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()