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