Skip to content

Commit 97ad3b4

Browse files
committed
Fix camera render API.
1 parent f948d53 commit 97ad3b4

File tree

6 files changed

+89
-44
lines changed

6 files changed

+89
-44
lines changed

genesis/ext/pyrender/offscreen.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def render(
148148
if shadow and not self._is_software:
149149
flags |= RenderFlags.SHADOWS_ALL
150150

151-
if depth and not rgb and not seg and not normal:
151+
if depth and not (rgb or seg):
152152
flags |= RenderFlags.DEPTH_ONLY
153153

154154
if plane_reflection and not self._is_software:
@@ -166,22 +166,25 @@ def render(
166166
if depth:
167167
flags |= RenderFlags.RET_DEPTH
168168

169-
if self._platform.supports_framebuffers():
170-
flags |= RenderFlags.OFFSCREEN
171-
retval = renderer.render(scene, flags, seg_node_map)
172-
else:
173-
if flags & RenderFlags.ENV_SEPARATE:
174-
gs.raise_exception("'env_separate_rigid=True' not supported on this platform.")
175-
if normal:
176-
gs.raise_exception("'normal=True' not supported on this platform.")
177-
renderer.render(scene, flags, seg_node_map)
178-
if depth:
179-
depth = renderer.read_depth_buf()
180-
if flags & RenderFlags.DEPTH_ONLY:
181-
retval = (depth,)
169+
if rgb or depth or seg:
170+
if self._platform.supports_framebuffers():
171+
flags |= RenderFlags.OFFSCREEN
172+
retval = renderer.render(scene, flags, seg_node_map)
182173
else:
183-
color = renderer.read_color_buf()
184-
retval = (color, depth)
174+
if flags & RenderFlags.ENV_SEPARATE:
175+
gs.raise_exception("'env_separate_rigid=True' not supported on this platform.")
176+
if normal:
177+
gs.raise_exception("'normal=True' not supported on this platform.")
178+
renderer.render(scene, flags, seg_node_map)
179+
if depth:
180+
depth = renderer.read_depth_buf()
181+
if flags & RenderFlags.DEPTH_ONLY:
182+
retval = (depth,)
183+
else:
184+
color = renderer.read_color_buf()
185+
retval = (color, depth)
186+
else:
187+
retval = ()
185188

186189
if normal:
187190
class CustomShaderCache:
@@ -204,7 +207,7 @@ def get_program(self, vertex_shader, fragment_shader, geometry_shader=None, defi
204207
if env_separate_rigid:
205208
flags |= RenderFlags.ENV_SEPARATE
206209
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
207-
normal_arr, _ = renderer.render(scene, flags, is_first_pass=False)
210+
normal_arr, *_ = renderer.render(scene, flags, is_first_pass=False, force_skip_shadows=True)
208211
retval = (*retval, normal_arr)
209212

210213
renderer._program_cache = old_cache

genesis/ext/pyrender/renderer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def point_size(self):
115115
def point_size(self, value):
116116
self._point_size = float(value)
117117

118-
def render(self, scene, flags, seg_node_map=None, is_first_pass=True):
118+
def render(self, scene, flags, seg_node_map=None, *, is_first_pass=True, force_skip_shadows=False):
119119
"""Render a scene with the given set of flags.
120120
121121
Parameters
@@ -159,7 +159,7 @@ def render(self, scene, flags, seg_node_map=None, is_first_pass=True):
159159
env_idx = i if use_env_idx else -1
160160

161161
# Render necessary shadow maps
162-
if not (flags & RenderFlags.SEG or flags & RenderFlags.DEPTH_ONLY):
162+
if not (force_skip_shadows or flags & RenderFlags.SEG or flags & RenderFlags.DEPTH_ONLY):
163163
for ln in scene.light_nodes:
164164
take_pass = False
165165
if isinstance(ln.light, DirectionalLight) and flags & RenderFlags.SHADOWS_DIRECTIONAL:
@@ -873,7 +873,7 @@ def _configure_forward_pass_viewport(self, flags):
873873
# If using offscreen render, bind main framebuffer
874874
if flags & RenderFlags.OFFSCREEN:
875875
self._configure_main_framebuffer()
876-
if (flags & RenderFlags.SEG or flags & RenderFlags.DEPTH_ONLY):
876+
if flags & RenderFlags.SEG or flags & RenderFlags.DEPTH_ONLY:
877877
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb)
878878
else:
879879
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._main_fb_ms)

genesis/ext/pyrender/viewer.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,10 +1138,13 @@ def _render(self, camera_node=None, renderer=None, normal=False):
11381138

11391139
if self.render_flags["depth"]:
11401140
flags |= RenderFlags.RET_DEPTH
1141-
if not (self.render_flags["rgb"] or self.render_flags["seg"] or normal):
1141+
if not (self.render_flags["rgb"] or self.render_flags["seg"]):
11421142
flags |= RenderFlags.DEPTH_ONLY
11431143

1144-
retval = renderer.render(self.scene, flags, seg_node_map=seg_node_map)
1144+
if self.render_flags["rgb"] or self.render_flags["depth"] or self.render_flags["seg"]:
1145+
retval = renderer.render(self.scene, flags, seg_node_map=seg_node_map)
1146+
else:
1147+
retval = ()
11451148

11461149
if normal:
11471150
class CustomShaderCache:
@@ -1164,8 +1167,8 @@ def get_program(self, vertex_shader, fragment_shader, geometry_shader=None, defi
11641167
if self.render_flags["env_separate_rigid"]:
11651168
flags |= RenderFlags.ENV_SEPARATE
11661169
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
1167-
normal_arr, _ = renderer.render(scene, flags, is_first_pass=False)
1168-
retval = retval + (normal_arr,)
1170+
normal_arr, *_ = renderer.render(scene, flags, is_first_pass=False)
1171+
retval = (*retval, normal_arr)
11691172

11701173
renderer._program_cache = old_cache
11711174

genesis/vis/camera.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -371,10 +371,6 @@ def render(
371371
normal_arr : np.ndarray
372372
The rendered surface normal(s).
373373
"""
374-
375-
if (rgb or depth or segmentation or normal) is False:
376-
gs.raise_exception("Nothing to render.")
377-
378374
rgb_arr, depth_arr, seg_arr, seg_color_arr, normal_arr = None, None, None, None, None
379375

380376
if self._batch_renderer is not None:
@@ -485,7 +481,7 @@ def render_pointcloud(self, world_frame=True):
485481
mask_arr : np.ndarray
486482
The valid depth mask.
487483
"""
488-
# Compute the (denormalized) depth map using PyRender systematically
484+
# Compute the (denormalized) depth map using PyRender systematically.
489485
# TODO: Add support of BatchRendered (requires access to projection matrix)
490486
self._rasterizer.update_scene()
491487
rgb_arr, depth_arr, seg_idxc_arr, normal_arr = self._rasterizer.render_camera(

genesis/vis/rasterizer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def render_camera(self, camera, rgb=True, depth=False, segmentation=False, norma
8484
)
8585

8686
if segmentation:
87-
seg_idxc_rgb_arr, _ = self._renderer.render(
87+
seg_idxc_rgb_arr, *_ = self._renderer.render(
8888
self._context._scene,
8989
self._camera_targets[camera.uid],
9090
camera_node=self._camera_nodes[camera.uid],
@@ -112,7 +112,7 @@ def render_camera(self, camera, rgb=True, depth=False, segmentation=False, norma
112112
)
113113

114114
if segmentation:
115-
seg_idxc_rgb_arr, _ = self._viewer.render_offscreen(
115+
seg_idxc_rgb_arr, *_ = self._viewer.render_offscreen(
116116
self._camera_nodes[camera.uid],
117117
self._camera_targets[camera.uid],
118118
rgb=False,
@@ -129,7 +129,7 @@ def render_camera(self, camera, rgb=True, depth=False, segmentation=False, norma
129129
if depth:
130130
depth_arr = retval[int(rgb)]
131131
if normal:
132-
normal_arr = retval[int(rgb + rgb_arr)]
132+
normal_arr = retval[int(rgb + depth)]
133133
return rgb_arr, depth_arr, seg_idxc_arr, normal_arr
134134

135135
def update_scene(self):

tests/test_render.py

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import itertools
12
import queue
23
import sys
34
from io import BytesIO
@@ -246,7 +247,7 @@ def test_batched_offscreen_rendering(show_viewer, tol):
246247
)
247248
scene.build(n_envs=3, env_spacing=(2.0, 2.0))
248249

249-
for _ in range(10):
250+
for _ in range(7):
250251
dofs_lower_bound, dofs_upper_bound = robot.get_dofs_limit()
251252
qpos = dofs_lower_bound + (dofs_upper_bound - dofs_lower_bound) * torch.rand(robot.n_qs)
252253

@@ -271,6 +272,47 @@ def test_batched_offscreen_rendering(show_viewer, tol):
271272
assert_allclose(steps_rgb_arrays[0][i], steps_rgb_arrays[1][i], tol=tol)
272273

273274

275+
@pytest.mark.required
276+
def test_render_api(show_viewer):
277+
scene = gs.Scene(
278+
show_viewer=show_viewer,
279+
show_FPS=False,
280+
)
281+
scene.add_entity(
282+
morph=gs.morphs.Sphere(
283+
pos=(0.0, 0.0, 0.0),
284+
radius=1.0,
285+
fixed=True,
286+
),
287+
)
288+
camera = scene.add_camera(
289+
pos=(0.0, 0.0, 10.0),
290+
lookat=(0.0, 0.0, 0.0),
291+
GUI=show_viewer,
292+
)
293+
scene.build()
294+
295+
rgb_arrs, depth_arrs, seg_arrs, normal_arrs = [], [], [], []
296+
for rgb, depth, seg, normal in itertools.product((True, False), repeat=4):
297+
rgb_arr, depth_arr, seg_arr, normal_arr = camera.render(rgb=rgb, depth=depth, segmentation=seg, normal=normal)
298+
if rgb:
299+
rgb_arrs.append(rgb_arr.astype(np.float32))
300+
if depth:
301+
depth_arrs.append(depth_arr.astype(np.float32))
302+
if seg:
303+
seg_arrs.append(seg_arr.astype(np.float32))
304+
if normal:
305+
normal_arrs.append(normal_arr.astype(np.float32))
306+
307+
assert_allclose(np.diff(rgb_arrs, axis=0), 0.0, tol=gs.EPS)
308+
assert_allclose(np.diff(seg_arrs, axis=0), 0.0, tol=gs.EPS)
309+
assert_allclose(np.diff(normal_arrs, axis=0), 0.0, tol=gs.EPS)
310+
# Depth is not matching at machine-precision because of MSAA being disabled for depth-only
311+
# FIXME: There is one pixel off on MacOS with Apple's Software Rendering, probably due to a bug...
312+
tol = 2e-4 if sys.platform == "darwin" else gs.EPS
313+
assert_allclose(np.diff(depth_arrs, axis=0)[[0, 1, 2, 4, 5, 6]], 0.0, tol=tol)
314+
315+
274316
@pytest.mark.required
275317
def test_point_cloud(show_viewer):
276318
CAMERA_DIST = 8.0
@@ -312,7 +354,8 @@ def test_point_cloud(show_viewer):
312354
GUI=show_viewer,
313355
)
314356
for camera in scene.visualizer.cameras:
315-
camera._near = 1.0
357+
camera._near = 2.0
358+
camera._far = 15.0
316359
scene.build()
317360

318361
if show_viewer:
@@ -329,12 +372,14 @@ def test_point_cloud(show_viewer):
329372
point_cloud = point_cloud[mask]
330373
point_cloud = point_cloud @ gu.z_up_to_R(np.array((1.0, 1.0, 1.0)), np.array((0.0, 0.0, 1.0))).T
331374
point_cloud -= np.array((CAMERA_DIST, CAMERA_DIST, CAMERA_DIST))
332-
assert_allclose(np.linalg.norm(point_cloud, ord=float("inf"), axis=-1), BOX_HALFSIZE, atol=1e-4)
375+
# FIXME: Tolerance must be increased whe using Apple's Software Rendering, probably due to a bug...
376+
tol = 2e-4 if sys.platform == "darwin" else 1e-4
377+
assert_allclose(np.linalg.norm(point_cloud, ord=float("inf"), axis=-1), BOX_HALFSIZE, atol=tol)
333378

334379
point_cloud, mask = camera_box_2.render_pointcloud(world_frame=True)
335380
point_cloud = point_cloud[mask]
336381
point_cloud += np.array((0.0, OBJ_OFFSET, 0.0))
337-
assert_allclose(np.linalg.norm(point_cloud, ord=float("inf"), axis=-1), BOX_HALFSIZE, atol=1e-4)
382+
assert_allclose(np.linalg.norm(point_cloud, ord=float("inf"), axis=-1), BOX_HALFSIZE, atol=tol)
338383

339384
# It is not possible to get higher accuracy because of tesselation
340385
point_cloud, mask = camera_sphere.render_pointcloud(world_frame=False)
@@ -387,21 +432,19 @@ def test_batched_mounted_camera_rendering(show_viewer, tol):
387432
for cam in cams:
388433
cam.attach(robot.get_link("hand"), gu.trans_R_to_T(trans, R))
389434

390-
target_quat = np.tile(np.array([0, 1, 0, 0]), [n_envs, 1]) # pointing downwards
391-
center = np.tile(np.array([-0.25, -0.25, 0.5]), [n_envs, 1])
435+
target_quat = np.tile(np.array([0, 1, 0, 0]), (n_envs, 1)) # pointing downwards
436+
center = np.tile(np.array([-0.25, -0.25, 0.5]), (n_envs, 1))
392437
rng = np.random.default_rng(42)
393438
angular_speed = rng.uniform(-10, 10, n_envs)
394439
r = 0.25
395440

396441
ee_link = robot.get_link("hand")
397442

398443
steps_rgb_queue: queue.Queue[list[np.ndarray]] = queue.Queue(maxsize=2)
399-
400-
for i in range(50):
401-
target_pos = np.zeros([n_envs, 3])
402-
target_pos[:, 0] = center[:, 0] + np.cos(i / 360 * np.pi * angular_speed) * r
403-
target_pos[:, 1] = center[:, 1] + np.sin(i / 360 * np.pi * angular_speed) * r
404-
target_pos[:, 2] = center[:, 2]
444+
for i in range(20):
445+
target_pos = center.copy()
446+
target_pos[:, 0] += np.cos(i / 360 * np.pi * angular_speed) * r
447+
target_pos[:, 1] += np.sin(i / 360 * np.pi * angular_speed) * r
405448

406449
q = robot.inverse_kinematics(
407450
link=ee_link,

0 commit comments

Comments
 (0)