Render Layers Demo¶
3D object visibility via render layer bitmasks.
▶ Run in browserTags: 3d
Demonstrates:
Assigning objects to different render layers (0, 1, 2)
Camera cull_mask controls which layers are visible
Toggle layers with keyboard (1, 2, 3)
Run: uv run python examples/features/3d/render_layers.py
Controls: 1 - Toggle layer 0 (red cubes) 2 - Toggle layer 1 (green spheres) 3 - Toggle layer 2 (blue cubes) A / D - Orbit camera Click/tap a layer’s status entry to toggle it
Source¶
1"""
2Render Layers Demo: 3D object visibility via render layer bitmasks.
3
4Demonstrates:
5 - Assigning objects to different render layers (0, 1, 2)
6 - Camera cull_mask controls which layers are visible
7 - Toggle layers with keyboard (1, 2, 3)
8
9Run:
10 uv run python examples/features/3d/render_layers.py
11
12Controls:
13 1 - Toggle layer 0 (red cubes)
14 2 - Toggle layer 1 (green spheres)
15 3 - Toggle layer 2 (blue cubes)
16 A / D - Orbit camera
17 Click/tap a layer's status entry to toggle it
18"""
19
20
21import math
22
23from simvx.core import (
24 Camera3D,
25 DirectionalLight3D,
26 Input,
27 InputMap,
28 Key,
29 Material,
30 Mesh,
31 MeshInstance3D,
32 MouseButton,
33 Node3D,
34 Quat,
35 Text2D,
36 Vec3,
37)
38from simvx.graphics import App
39
40WIDTH, HEIGHT = 1280, 720
41
42
43class RenderLayerScene(Node3D):
44 def __init__(self, **kwargs):
45 super().__init__(name="RenderLayerDemo", **kwargs)
46
47 # Camera
48 self._cam_angle = 30.0
49 self.camera = self.add_child(Camera3D(name="Camera", fov=60, near=0.1, far=100.0))
50 self._update_camera()
51
52 # Lighting
53 sun = self.add_child(DirectionalLight3D(name="Sun"))
54 sun.colour = (1.0, 0.95, 0.9)
55 sun.intensity = 1.2
56 sun.rotation = Quat.from_euler(math.radians(-45), math.radians(-30), 0)
57
58 # Ground (on all layers: always visible)
59 ground = self.add_child(
60 MeshInstance3D(
61 name="Ground",
62 mesh=Mesh.cube(1.0),
63 material=Material(colour=(0.3, 0.3, 0.3)),
64 position=Vec3(0, -0.05, 0),
65 scale=Vec3(20, 0.1, 20),
66 )
67 )
68 ground.render_layer = 0b111 # layers 0, 1, 2
69
70 # Layer 0: Red cubes
71 red_mat = Material(colour=(0.9, 0.15, 0.1))
72 for i in range(3):
73 cube = self.add_child(
74 MeshInstance3D(
75 name=f"RedCube{i}",
76 mesh=Mesh.cube(1.0),
77 material=red_mat,
78 position=Vec3(-4 + i * 2, 1.0, -2),
79 scale=Vec3(0.8, 0.8, 0.8),
80 )
81 )
82 cube.render_layer = 1 << 0 # layer 0
83
84 # Layer 1: Green spheres
85 green_mat = Material(colour=(0.1, 0.85, 0.2))
86 for i in range(3):
87 sphere = self.add_child(
88 MeshInstance3D(
89 name=f"GreenSphere{i}",
90 mesh=Mesh.sphere(0.6, rings=12, segments=12),
91 material=green_mat,
92 position=Vec3(-4 + i * 2, 1.0, 0),
93 )
94 )
95 sphere.render_layer = 1 << 1 # layer 1
96
97 # Layer 2: Blue cubes
98 blue_mat = Material(colour=(0.1, 0.2, 0.9))
99 for i in range(3):
100 cube = self.add_child(
101 MeshInstance3D(
102 name=f"BlueCube{i}",
103 mesh=Mesh.cube(1.0),
104 material=blue_mat,
105 position=Vec3(-4 + i * 2, 1.0, 2),
106 scale=Vec3(0.8, 0.8, 0.8),
107 )
108 )
109 cube.render_layer = 1 << 2 # layer 2
110
111 # HUD: one clickable status entry per layer
112 self._title = self.add_child(Text2D(text="RENDER LAYERS DEMO", position=(10, 8), font_scale=1.6))
113 self._layer_entries = []
114 for layer_idx, (label, x, colour) in enumerate(
115 [
116 ("Red", 10, (0.95, 0.35, 0.3)),
117 ("Green", 160, (0.3, 0.9, 0.4)),
118 ("Blue", 310, (0.4, 0.5, 1.0)),
119 ]
120 ):
121 entry = self.add_child(Text2D(text="", position=(x, 40), font_scale=1.2))
122 self._layer_entries.append((layer_idx, label, x, colour, entry))
123 self._controls = self.add_child(
124 Text2D(text="1:Red 2:Green 3:Blue A/D:Orbit Click a layer to toggle", font_scale=1.1)
125 )
126
127 def on_ready(self):
128 InputMap.add_action("cam_left", [Key.A, Key.LEFT])
129 InputMap.add_action("cam_right", [Key.D, Key.RIGHT])
130 InputMap.add_action("toggle_0", [Key.KEY_1])
131 InputMap.add_action("toggle_1", [Key.KEY_2])
132 InputMap.add_action("toggle_2", [Key.KEY_3])
133
134 def _update_camera(self):
135
136 r = 12.0
137 y = 8.0
138 rad = math.radians(self._cam_angle)
139 self.camera.position = Vec3(math.sin(rad) * r, y, math.cos(rad) * r)
140 self.camera.look_at(Vec3(0, 0.5, 0))
141
142 def _toggle_layer(self, layer_idx: int):
143 enabled = self.camera.is_cull_mask_layer_enabled(layer_idx)
144 self.camera.set_cull_mask_layer(layer_idx, not enabled)
145
146 def on_update(self, dt: float):
147 # Camera orbit
148 if Input.is_action_pressed("cam_left"):
149 self._cam_angle -= 60 * dt
150 self._update_camera()
151 if Input.is_action_pressed("cam_right"):
152 self._cam_angle += 60 * dt
153 self._update_camera()
154
155 # Toggle render layers: keys 1/2/3, edge-triggered
156 for key_action, layer_idx in [("toggle_0", 0), ("toggle_1", 1), ("toggle_2", 2)]:
157 if Input.is_action_just_pressed(key_action):
158 self._toggle_layer(layer_idx)
159
160 # Click/tap a status entry to toggle that layer
161 if Input.is_mouse_button_just_pressed(MouseButton.LEFT):
162 mx, my = Input.mouse_position
163 for layer_idx, _label, x, _colour, _entry in self._layer_entries:
164 if x - 4 <= mx <= x + 130 and 34 <= my <= 66:
165 self._toggle_layer(layer_idx)
166
167 # Update status entries and keep the controls hint pinned to the window bottom
168 for layer_idx, label, _x, colour, entry in self._layer_entries:
169 on = self.camera.is_cull_mask_layer_enabled(layer_idx)
170 entry.text = f"{label}:{'ON' if on else 'OFF'}"
171 entry.colour = colour if on else (0.5, 0.5, 0.5)
172 self._controls.position = (10, self.app.height - 30)
173
174
175def main():
176 scene = RenderLayerScene()
177 app = App(title="SimVX Render Layers Demo", width=WIDTH, height=HEIGHT, physics_fps=60)
178 app.run(scene)
179
180
181if __name__ == "__main__":
182 main()