Lighting and shadows¶
Add directional lights and ambient lighting to illuminate 3D meshes with realistic shadows.
Features:
viser.SceneApi.set_ambient_light()for global illuminationviser.SceneApi.add_directional_light()for sun-like directional lightingviser.SceneApi.configure_default_lights()for quick setupviser.SceneNodeHandle.cast_shadowto enable shadow castingviser.SceneApi.add_light_spot()for focused cone lighting with direction controlDynamic light control with GUI sliders
Note
This example requires external assets. To download them, run:
git clone -b v1.0.24 https://github.com/viser-project/viser.git
cd viser/examples
./assets/download_assets.sh
python 01_scene/06_lighting.py # With viser installed.
Source: examples/01_scene/06_lighting.py
Code¶
1import time
2from pathlib import Path
3
4import numpy as np
5import trimesh
6
7import viser
8import viser.transforms as tf
9
10
11def main() -> None:
12 # Load mesh.
13 mesh = trimesh.load_mesh(str(Path(__file__).parent / "../assets/dragon.obj"))
14 assert isinstance(mesh, trimesh.Trimesh)
15 mesh.apply_scale(0.05)
16 vertices = mesh.vertices
17 faces = mesh.faces
18 print(f"Loaded mesh with {vertices.shape} vertices, {faces.shape} faces")
19 print(mesh)
20
21 # Start Viser server with mesh.
22 server = viser.ViserServer()
23
24 server.scene.add_mesh_simple(
25 name="/simple",
26 vertices=vertices,
27 faces=faces,
28 wxyz=tf.SO3.from_x_radians(np.pi / 2).wxyz,
29 position=(0.0, 2.0, 0.0),
30 )
31 server.scene.add_mesh_trimesh(
32 name="/trimesh",
33 mesh=mesh,
34 wxyz=tf.SO3.from_x_radians(np.pi / 2).wxyz,
35 position=(0.0, -2.0, 0.0),
36 )
37 grid = server.scene.add_grid(
38 "grid",
39 width=20.0,
40 height=20.0,
41 position=np.array([0.0, 0.0, -2.0]),
42 )
43
44 # adding controls to custom lights in the scene
45 server.scene.add_transform_controls(
46 "/control0", position=(0.0, 10.0, 5.0), scale=2.0
47 )
48 server.scene.add_label("/control0/label", "Directional")
49 server.scene.add_transform_controls(
50 "/control1", position=(0.0, -5.0, 5.0), scale=2.0
51 )
52 server.scene.add_label("/control1/label", "Point")
53 server.scene.add_transform_controls(
54 "/control2", position=(5.0, 0.0, 5.0), scale=2.0
55 )
56 server.scene.add_label("/control2/label", "Spot")
57
58 directional_light = server.scene.add_light_directional(
59 name="/control0/directional_light",
60 color=(186, 219, 173),
61 cast_shadow=True,
62 )
63 point_light = server.scene.add_light_point(
64 name="/control1/point_light",
65 color=(192, 255, 238),
66 intensity=30.0,
67 cast_shadow=True,
68 )
69 spot_light = server.scene.add_light_spot(
70 name="/control2/spot_light",
71 color=(255, 200, 150),
72 intensity=80.0,
73 distance=15.0,
74 angle=np.pi / 6,
75 penumbra=0.4,
76 cast_shadow=True,
77 # direction is in the light's local frame; rotating the
78 # transform control will rotate the cone accordingly.
79 direction=(0.0, 0.0, -1.0),
80 )
81
82 with server.gui.add_folder("Grid Shadows"):
83 # Create grid shadows toggle
84 grid_shadows = server.gui.add_slider(
85 "Intensity",
86 min=0.0,
87 max=1.0,
88 step=0.01,
89 initial_value=grid.shadow_opacity,
90 )
91
92 @grid_shadows.on_update
93 def _(_) -> None:
94 grid.shadow_opacity = grid_shadows.value
95
96 # Create default light toggle.
97 gui_default_lights = server.gui.add_checkbox("Default lights", initial_value=True)
98 gui_default_shadows = server.gui.add_checkbox(
99 "Default shadows", initial_value=False
100 )
101
102 gui_default_lights.on_update(
103 lambda _: server.scene.configure_default_lights(
104 gui_default_lights.value, gui_default_shadows.value
105 )
106 )
107 gui_default_shadows.on_update(
108 lambda _: server.scene.configure_default_lights(
109 gui_default_lights.value, gui_default_shadows.value
110 )
111 )
112
113 # Create light control inputs.
114 with server.gui.add_folder("Directional light"):
115 gui_directional_color = server.gui.add_rgb(
116 "Color", initial_value=directional_light.color
117 )
118 gui_directional_intensity = server.gui.add_slider(
119 "Intensity",
120 min=0.0,
121 max=20.0,
122 step=0.01,
123 initial_value=directional_light.intensity,
124 )
125 gui_directional_shadows = server.gui.add_checkbox("Shadows", True)
126
127 @gui_directional_color.on_update
128 def _(_) -> None:
129 directional_light.color = gui_directional_color.value
130
131 @gui_directional_intensity.on_update
132 def _(_) -> None:
133 directional_light.intensity = gui_directional_intensity.value
134
135 @gui_directional_shadows.on_update
136 def _(_) -> None:
137 directional_light.cast_shadow = gui_directional_shadows.value
138
139 with server.gui.add_folder("Point light"):
140 gui_point_color = server.gui.add_rgb("Color", initial_value=point_light.color)
141 gui_point_intensity = server.gui.add_slider(
142 "Intensity",
143 min=0.0,
144 max=200.0,
145 step=0.01,
146 initial_value=point_light.intensity,
147 )
148 gui_point_shadows = server.gui.add_checkbox("Shadows", True)
149
150 @gui_point_color.on_update
151 def _(_) -> None:
152 point_light.color = gui_point_color.value
153
154 @gui_point_intensity.on_update
155 def _(_) -> None:
156 point_light.intensity = gui_point_intensity.value
157
158 @gui_point_shadows.on_update
159 def _(_) -> None:
160 point_light.cast_shadow = gui_point_shadows.value
161
162 with server.gui.add_folder("Spot light"):
163 gui_spot_color = server.gui.add_rgb("Color", initial_value=spot_light.color)
164 gui_spot_intensity = server.gui.add_slider(
165 "Intensity",
166 min=0.0,
167 max=200.0,
168 step=1.0,
169 initial_value=spot_light.intensity,
170 )
171 gui_spot_angle = server.gui.add_slider(
172 "Cone angle (deg)",
173 min=5.0,
174 max=89.0,
175 step=1.0,
176 initial_value=np.rad2deg(spot_light.angle),
177 )
178 gui_spot_penumbra = server.gui.add_slider(
179 "Penumbra",
180 min=0.0,
181 max=1.0,
182 step=0.01,
183 initial_value=spot_light.penumbra,
184 )
185 gui_spot_shadows = server.gui.add_checkbox("Shadows", True)
186
187 @gui_spot_color.on_update
188 def _(_) -> None:
189 spot_light.color = gui_spot_color.value
190
191 @gui_spot_intensity.on_update
192 def _(_) -> None:
193 spot_light.intensity = gui_spot_intensity.value
194
195 @gui_spot_angle.on_update
196 def _(_) -> None:
197 spot_light.angle = np.deg2rad(gui_spot_angle.value)
198
199 @gui_spot_penumbra.on_update
200 def _(_) -> None:
201 spot_light.penumbra = gui_spot_penumbra.value
202
203 @gui_spot_shadows.on_update
204 def _(_) -> None:
205 spot_light.cast_shadow = gui_spot_shadows.value
206
207 # Create GUI elements for controlling environment map.
208 with server.gui.add_folder("Environment map"):
209 gui_env_preset = server.gui.add_dropdown(
210 "Preset",
211 (
212 "None",
213 "apartment",
214 "city",
215 "dawn",
216 "forest",
217 "lobby",
218 "night",
219 "park",
220 "studio",
221 "sunset",
222 "warehouse",
223 ),
224 initial_value="city",
225 )
226 gui_background = server.gui.add_checkbox("Background", False)
227 gui_bg_blurriness = server.gui.add_slider(
228 "Bg Blurriness",
229 min=0.0,
230 max=1.0,
231 step=0.01,
232 initial_value=0.0,
233 )
234 gui_bg_intensity = server.gui.add_slider(
235 "Bg Intensity",
236 min=0.0,
237 max=1.0,
238 step=0.01,
239 initial_value=1.0,
240 )
241 gui_env_intensity = server.gui.add_slider(
242 "Env Intensity",
243 min=0.0,
244 max=1.0,
245 step=0.01,
246 initial_value=0.3,
247 )
248
249 def update_environment_map(_) -> None:
250 server.scene.configure_environment_map(
251 gui_env_preset.value if gui_env_preset.value != "None" else None,
252 background=gui_background.value,
253 background_blurriness=gui_bg_blurriness.value,
254 background_intensity=gui_bg_intensity.value,
255 environment_intensity=gui_env_intensity.value,
256 )
257
258 update_environment_map(None)
259 gui_env_preset.on_update(update_environment_map)
260 gui_background.on_update(update_environment_map)
261 gui_bg_blurriness.on_update(update_environment_map)
262 gui_bg_intensity.on_update(update_environment_map)
263 gui_env_intensity.on_update(update_environment_map)
264
265 while True:
266 time.sleep(10.0)
267
268
269if __name__ == "__main__":
270 main()