SSAO Demo¶
Screen-Space Ambient Occlusion visualization.
▶ Run in browserTags: 3d
Demonstrates:
SSAO effect on a scene with many objects (columns, walls, corners)
Toggle SSAO on/off with Space key to see the difference
PBR materials with ambient occlusion darkening in crevices
Post-processing (HDR + bloom + SSAO)
Run: uv run python examples/features/3d/ssao.py
Controls: Space - Toggle SSAO on/off A / D - Orbit camera left / right W / S - Zoom in / out Q / E - Raise / lower camera
Source¶
1"""
2SSAO Demo: Screen-Space Ambient Occlusion visualization.
3
4Demonstrates:
5 - SSAO effect on a scene with many objects (columns, walls, corners)
6 - Toggle SSAO on/off with Space key to see the difference
7 - PBR materials with ambient occlusion darkening in crevices
8 - Post-processing (HDR + bloom + SSAO)
9
10Run: uv run python examples/features/3d/ssao.py
11
12Controls:
13 Space - Toggle SSAO on/off
14 A / D - Orbit camera left / right
15 W / S - Zoom in / out
16 Q / E - Raise / lower camera
17"""
18
19
20import math
21
22from simvx.core import (
23 Camera3D,
24 DirectionalLight3D,
25 Input,
26 InputMap,
27 Key,
28 Material,
29 Mesh,
30 MeshInstance3D,
31 Node3D,
32 PointLight3D,
33 Quat,
34 Text2D,
35 Vec3,
36 WorldEnvironment,
37)
38from simvx.graphics import App
39
40WIDTH, HEIGHT = 1280, 720
41
42
43class SSAOScene(Node3D):
44 def __init__(self, **kwargs):
45 super().__init__(name="SSAODemo", **kwargs)
46
47 # Camera
48 self._cam_angle = 30.0
49 self._cam_height = 8.0
50 self._cam_dist = 18.0
51 self.camera = self.add_child(
52 Camera3D(
53 name="Camera",
54 fov=55,
55 near=0.1,
56 far=100.0,
57 )
58 )
59 self._update_camera()
60
61 # Lighting
62 sun = self.add_child(DirectionalLight3D(name="Sun"))
63 sun.colour = (1.0, 0.95, 0.85)
64 sun.intensity = 1.2
65 sun.rotation = Quat.from_euler(math.radians(-50), math.radians(-30), 0)
66
67 fill = self.add_child(
68 PointLight3D(
69 name="Fill",
70 position=Vec3(6, 6, 6),
71 )
72 )
73 fill.colour = (0.4, 0.5, 0.8)
74 fill.intensity = 0.6
75 fill.range = 25.0
76
77 # Ground
78 ground_mat = Material(colour=(0.3, 0.3, 0.32), metallic=0.0, roughness=0.9)
79 self.add_child(
80 MeshInstance3D(
81 name="Ground",
82 mesh=Mesh.cube(1.0),
83 material=ground_mat,
84 position=Vec3(0, -0.05, 0),
85 scale=Vec3(20, 0.1, 20),
86 )
87 )
88
89 # Build a scene with many objects close together to show SSAO
90 self._build_scene()
91
92 # SSAO state via WorldEnvironment
93 self._ssao_on = True
94 self._toggle_cooldown = 0.0
95 self._env = self.add_child(WorldEnvironment(name="Env"))
96 self._env.ssao_enabled = True
97 self._env.bloom_enabled = True
98
99 # HUD
100 self._title = self.add_child(
101 Text2D(
102 text="SSAO DEMO",
103 x=10,
104 y=8,
105 font_scale=1.6,
106 )
107 )
108 self._status = self.add_child(
109 Text2D(
110 text="SSAO: ON",
111 x=10,
112 y=40,
113 font_scale=1.3,
114 )
115 )
116 self._controls = self.add_child(
117 Text2D(
118 text="SPACE:Toggle SSAO WASD/QE:Camera",
119 x=10,
120 y=690,
121 font_scale=1.1,
122 )
123 )
124
125 def on_ready(self):
126 InputMap.add_action("cam_left", [Key.A, Key.LEFT])
127 InputMap.add_action("cam_right", [Key.D, Key.RIGHT])
128 InputMap.add_action("cam_fwd", [Key.W, Key.UP])
129 InputMap.add_action("cam_back", [Key.S, Key.DOWN])
130 InputMap.add_action("cam_up", [Key.Q])
131 InputMap.add_action("cam_down", [Key.E])
132 InputMap.add_action("toggle_ssao", [Key.SPACE])
133
134 def _build_scene(self):
135 """Create columns, walls, and objects to demonstrate AO in crevices."""
136 wall_mat = Material(colour=(0.7, 0.68, 0.65), metallic=0.0, roughness=0.85)
137 column_mat = Material(colour=(0.6, 0.58, 0.55), metallic=0.1, roughness=0.7)
138 dark_mat = Material(colour=(0.35, 0.33, 0.30), metallic=0.0, roughness=0.95)
139 bright_mat = Material(colour=(0.85, 0.4, 0.15), metallic=0.3, roughness=0.4)
140 metal_mat = Material(colour=(0.8, 0.82, 0.85), metallic=0.9, roughness=0.1)
141
142 # Back wall
143 self.add_child(
144 MeshInstance3D(
145 name="BackWall",
146 mesh=Mesh.cube(1.0),
147 material=wall_mat,
148 position=Vec3(0, 3, -6),
149 scale=Vec3(12, 6, 0.3),
150 )
151 )
152
153 # Side walls
154 for i, x in enumerate([-6, 6]):
155 self.add_child(
156 MeshInstance3D(
157 name=f"SideWall{i}",
158 mesh=Mesh.cube(1.0),
159 material=wall_mat,
160 position=Vec3(x, 3, -3),
161 scale=Vec3(0.3, 6, 6),
162 )
163 )
164
165 # Columns along back wall
166 for i, x in enumerate([-4, -2, 0, 2, 4]):
167 self.add_child(
168 MeshInstance3D(
169 name=f"Column{i}",
170 mesh=Mesh.cylinder(0.25, 5.5, segments=16),
171 material=column_mat,
172 position=Vec3(x, 2.75, -5.5),
173 )
174 )
175 # Column base
176 self.add_child(
177 MeshInstance3D(
178 name=f"ColumnBase{i}",
179 mesh=Mesh.cube(1.0),
180 material=dark_mat,
181 position=Vec3(x, 0.15, -5.5),
182 scale=Vec3(0.7, 0.3, 0.7),
183 )
184 )
185 # Column capital
186 self.add_child(
187 MeshInstance3D(
188 name=f"ColumnCap{i}",
189 mesh=Mesh.cube(1.0),
190 material=dark_mat,
191 position=Vec3(x, 5.35, -5.5),
192 scale=Vec3(0.7, 0.3, 0.7),
193 )
194 )
195
196 # Stacked boxes in corner (shows AO between adjacent surfaces)
197 box_positions = [
198 Vec3(-4.5, 0.5, -4.5),
199 Vec3(-4.5, 1.5, -4.5),
200 Vec3(-3.5, 0.5, -4.5),
201 Vec3(-4.5, 0.5, -3.5),
202 Vec3(-4.0, 0.5, -4.0),
203 Vec3(-4.0, 1.5, -4.0),
204 ]
205 for i, pos in enumerate(box_positions):
206 mat = bright_mat if i % 3 == 0 else dark_mat
207 self.add_child(
208 MeshInstance3D(
209 name=f"StackedBox{i}",
210 mesh=Mesh.cube(1.0),
211 material=mat,
212 position=pos,
213 scale=Vec3(0.9, 0.9, 0.9),
214 )
215 )
216
217 # Spheres on the ground (shows AO at ground contact)
218 for i in range(5):
219 x = -2.0 + i * 1.5
220 r = 0.4 + (i % 3) * 0.15
221 mat = metal_mat if i % 2 == 0 else bright_mat
222 self.add_child(
223 MeshInstance3D(
224 name=f"GroundSphere{i}",
225 mesh=Mesh.sphere(r, rings=16, segments=24),
226 material=mat,
227 position=Vec3(x, r, -2.0),
228 )
229 )
230
231 # Archway (two columns + lintel)
232 for i, x in enumerate([-1.5, 1.5]):
233 self.add_child(
234 MeshInstance3D(
235 name=f"ArchColumn{i}",
236 mesh=Mesh.cylinder(0.3, 4.0, segments=16),
237 material=column_mat,
238 position=Vec3(x, 2.0, 0),
239 )
240 )
241 self.add_child(
242 MeshInstance3D(
243 name="ArchLintel",
244 mesh=Mesh.cube(1.0),
245 material=wall_mat,
246 position=Vec3(0, 4.2, 0),
247 scale=Vec3(3.6, 0.4, 0.6),
248 )
249 )
250
251 # Stepped platform (shows AO on step edges)
252 for i in range(4):
253 self.add_child(
254 MeshInstance3D(
255 name=f"Step{i}",
256 mesh=Mesh.cube(1.0),
257 material=dark_mat,
258 position=Vec3(4.0, 0.15 + i * 0.3, -3.0 + i * 0.8),
259 scale=Vec3(2.0, 0.3, 0.8),
260 )
261 )
262
263 def _update_camera(self):
264 rad = math.radians(self._cam_angle)
265 x = math.cos(rad) * self._cam_dist
266 z = math.sin(rad) * self._cam_dist
267 self.camera.position = Vec3(x, self._cam_height, z)
268 self.camera.look_at(Vec3(0, 2.5, -2))
269
270 def on_physics_process(self, dt: float):
271 speed = 40.0
272 if Input.is_action_pressed("cam_left"):
273 self._cam_angle += speed * dt
274 if Input.is_action_pressed("cam_right"):
275 self._cam_angle -= speed * dt
276 if Input.is_action_pressed("cam_fwd"):
277 self._cam_dist = max(8, self._cam_dist - 10 * dt)
278 if Input.is_action_pressed("cam_back"):
279 self._cam_dist = min(35, self._cam_dist + 10 * dt)
280 if Input.is_action_pressed("cam_up"):
281 self._cam_height = min(20, self._cam_height + 6 * dt)
282 if Input.is_action_pressed("cam_down"):
283 self._cam_height = max(2, self._cam_height - 6 * dt)
284 self._update_camera()
285
286 def on_process(self, dt: float):
287 # Toggle SSAO with cooldown
288 self._toggle_cooldown = max(0, self._toggle_cooldown - dt)
289 if Input.is_action_just_pressed("toggle_ssao") and self._toggle_cooldown <= 0:
290 self._ssao_on = not self._ssao_on
291 self._toggle_cooldown = 0.3
292 self._env.ssao_enabled = self._ssao_on
293
294 self._status.text = f"SSAO: {'ON' if self._ssao_on else 'OFF'}"
295
296
297def main():
298 scene = SSAOScene()
299 app = App(title="SimVX SSAO Demo", width=WIDTH, height=HEIGHT, physics_fps=60)
300 app.run(scene)
301
302
303if __name__ == "__main__":
304 main()