Skip to content

Commit def2632

Browse files
duburcqaKashu7100
authored andcommitted
[BUG FIX] Fix render (Genesis-Embodied-AI#1537)
* Fix support of RayTracer on MacOS. * Fix Rasterizer video recording. * Fix RayTracer camera initialization. * Fix support of OSMesa backend for Rasterizer. * Improve exception handling for 'Rasterizer.render_camera'. * Enable antialiasing by default for BatchRender.
1 parent 1801831 commit def2632

File tree

7 files changed

+82
-70
lines changed

7 files changed

+82
-70
lines changed

genesis/engine/scene.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import pickle
3+
import sys
34
import time
45

56
import numpy as np
@@ -526,7 +527,7 @@ def add_camera(
526527
focus_dist=None,
527528
GUI=False,
528529
spp=256,
529-
denoise=True,
530+
denoise=None,
530531
env_idx=None,
531532
):
532533
"""
@@ -564,14 +565,16 @@ def add_camera(
564565
Samples per pixel. Only available when using RayTracer renderer. Defaults to 256.
565566
denoise : bool
566567
Whether to denoise the camera's rendered image. Only available when using the RayTracer renderer. Defaults
567-
to True. If OptiX denoiser is not available in your platform, consider enabling the OIDN denoiser option
568-
when building the RayTracer.
568+
to True on Linux, otherwise False. If OptiX denoiser is not available in your platform, consider enabling
569+
the OIDN denoiser option when building the RayTracer.
569570
570571
Returns
571572
-------
572573
camera : genesis.Camera
573574
The created camera object.
574575
"""
576+
if denoise is None:
577+
denoise = sys.platform != "darwin"
575578
return self._visualizer.add_camera(
576579
res, pos, lookat, up, model, fov, aperture, focus_dist, GUI, spp, denoise, env_idx
577580
)

genesis/ext/pyrender/offscreen.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,22 @@ def render(
173173
else:
174174
if flags & RenderFlags.ENV_SEPARATE:
175175
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.")
178176
renderer.render(scene, flags, seg_node_map)
177+
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0)
178+
glReadBuffer(GL_FRONT)
179179
if depth:
180-
depth = renderer.read_depth_buf()
180+
z_near = scene.main_camera_node.camera.znear
181+
z_far = scene.main_camera_node.camera.zfar
182+
if z_far is None:
183+
z_far = -1.0
184+
depth_arr = renderer.jit.read_depth_buf(self.viewport_height, self.viewport_width, z_near, z_far)
185+
depth_arr = renderer._resize_image(depth_arr, antialias=not seg)
181186
if flags & RenderFlags.DEPTH_ONLY:
182-
retval = (depth,)
187+
retval = (depth_arr,)
183188
else:
184-
color = renderer.read_color_buf()
185-
retval = (color, depth)
189+
color_arr = renderer.jit.read_color_buf(self.viewport_height, self.viewport_width, rgba=False)
190+
color_arr = renderer._resize_image(color_arr, antialias=not seg)
191+
retval = (color_arr, depth_arr) if depth else (color_arr,)
186192
else:
187193
retval = ()
188194

@@ -207,7 +213,16 @@ def get_program(self, vertex_shader, fragment_shader, geometry_shader=None, defi
207213
if env_separate_rigid:
208214
flags |= RenderFlags.ENV_SEPARATE
209215
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
210-
normal_arr, *_ = renderer.render(scene, flags, is_first_pass=False, force_skip_shadows=True)
216+
217+
if self._platform.supports_framebuffers():
218+
normal_arr, *_ = renderer.render(scene, flags, is_first_pass=False, force_skip_shadows=True)
219+
else:
220+
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0)
221+
glReadBuffer(GL_FRONT)
222+
renderer.render(scene, flags, is_first_pass=False, force_skip_shadows=True)
223+
normal_arr = renderer.jit.read_color_buf(self.viewport_height, self.viewport_width, rgba=False)
224+
normal_arr = renderer._resize_image(normal_arr, antialias=not seg)
225+
211226
retval = (*retval, normal_arr)
212227

213228
renderer._program_cache = old_cache

genesis/ext/pyrender/viewer.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,11 +1063,12 @@ def _save_image(self):
10631063
filename = self._get_save_filename(["png", "jpg", "gif", "all"])
10641064
if filename is not None:
10651065
self.viewer_flags["save_directory"] = os.path.dirname(filename)
1066-
imageio.imwrite(filename, self._renderer.read_color_buf())
1066+
data = self._renderer.jit.read_color_buf(*self._viewport_size, rgba=False)
1067+
imageio.imwrite(filename, img_arr)
10671068

10681069
def _record(self):
10691070
"""Save another frame for the GIF."""
1070-
data = self._renderer.read_color_buf()
1071+
data = self._renderer.jit.read_color_buf(*self._viewport_size, rgba=False)
10711072
if not np.all(data == 0.0):
10721073
self.video_recorder.write_frame(data)
10731074

genesis/vis/camera.py

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ def __init__(
130130
self._focus_dist = np.linalg.norm(np.asarray(lookat) - np.asarray(pos))
131131

132132
def build(self):
133+
n_envs = max(self._visualizer.scene.n_envs, 1)
134+
self._multi_env_pos_tensor = torch.empty((n_envs, 3), dtype=gs.tc_float, device=gs.device)
135+
self._multi_env_lookat_tensor = torch.empty((n_envs, 3), dtype=gs.tc_float, device=gs.device)
136+
self._multi_env_up_tensor = torch.empty((n_envs, 3), dtype=gs.tc_float, device=gs.device)
137+
self._multi_env_transform_tensor = torch.empty((n_envs, 4, 4), dtype=gs.tc_float, device=gs.device)
138+
self._multi_env_quat_tensor = torch.empty((n_envs, 4), dtype=gs.tc_float, device=gs.device)
139+
133140
self._envs_offset = torch.as_tensor(self._visualizer._scene.envs_offset, dtype=gs.tc_float, device=gs.device)
134141

135142
self._batch_renderer = self._visualizer.batch_renderer
@@ -154,7 +161,12 @@ def build(self):
154161
self._other_stacked = self._visualizer._context.env_separate_rigid
155162

156163
self._is_built = True
157-
self.setup_initial_env_poses()
164+
self.set_pose(
165+
transform=self._initial_transform, pos=self._initial_pos, lookat=self._initial_lookat, up=self._initial_up
166+
)
167+
# FIXME: For some reason, it is necessary to update the camera twice...
168+
if self._raytracer is not None:
169+
self._raytracer.update_camera(self)
158170

159171
def attach(self, rigid_link, offset_T):
160172
"""
@@ -296,8 +308,8 @@ def _batch_render(
296308
depth=False,
297309
segmentation=False,
298310
normal=False,
311+
antialiasing=True,
299312
force_render=False,
300-
antialiasing=False,
301313
):
302314
"""
303315
Render the camera view with batch renderer.
@@ -371,7 +383,7 @@ def render(
371383
normal_arr : np.ndarray
372384
The rendered surface normal(s).
373385
"""
374-
rgb_arr, depth_arr, seg_arr, seg_color_arr, normal_arr = None, None, None, None, None
386+
rgb_arr, depth_arr, seg_arr, seg_color_arr, seg_idxc_arr, normal_arr = None, None, None, None, None, None
375387

376388
if self._batch_renderer is not None:
377389
rgb_arr, depth_arr, seg_idxc_arr, normal_arr = self._batch_render(
@@ -521,28 +533,6 @@ def render_pointcloud(self, world_frame=True):
521533
point_cloud = point_cloud[:, :3].reshape((*depth_arr.shape, 3))
522534
return point_cloud, mask
523535

524-
@gs.assert_built
525-
def setup_initial_env_poses(self):
526-
"""
527-
Setup the camera poses for multiple environments.
528-
"""
529-
if self._initial_transform is not None:
530-
assert self._initial_transform.shape == (4, 4)
531-
self._initial_pos, self._initial_lookat, self._initial_up = gu.T_to_pos_lookat_up(self._initial_transform)
532-
else:
533-
self._initial_transform = gu.pos_lookat_up_to_T(self._initial_pos, self._initial_lookat, self._initial_up)
534-
535-
n_envs = max(self._visualizer.scene.n_envs, 1)
536-
self._multi_env_pos_tensor = self._initial_pos.repeat((n_envs, 1))
537-
self._multi_env_lookat_tensor = self._initial_lookat.repeat((n_envs, 1))
538-
self._multi_env_up_tensor = self._initial_up.repeat((n_envs, 1))
539-
self._multi_env_transform_tensor = self._initial_transform.repeat((n_envs, 1, 1))
540-
self._multi_env_quat_tensor = _T_to_quat_for_madrona(self._multi_env_transform_tensor)
541-
542-
self._rasterizer.update_camera(self)
543-
if self._raytracer is not None:
544-
self._raytracer.update_camera(self)
545-
546536
@gs.assert_built
547537
def set_pose(self, transform=None, pos=None, lookat=None, up=None, env_idx=None):
548538
"""

genesis/vis/rasterizer.py

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -69,36 +69,37 @@ def render_camera(self, camera, rgb=True, depth=False, segmentation=False, norma
6969
self._context.buffer.clear()
7070

7171
# Render
72-
if rgb or depth or normal:
73-
retval = self._renderer.render(
74-
self._context._scene,
75-
self._camera_targets[camera.uid],
76-
camera_node=self._camera_nodes[camera.uid],
77-
env_separate_rigid=self._context.env_separate_rigid,
78-
rgb=rgb,
79-
normal=normal,
80-
seg=False,
81-
depth=depth,
82-
plane_reflection=rgb and self._context.plane_reflection,
83-
shadow=rgb and self._context.shadow,
84-
)
85-
86-
if segmentation:
87-
seg_idxc_rgb_arr, *_ = self._renderer.render(
88-
self._context._scene,
89-
self._camera_targets[camera.uid],
90-
camera_node=self._camera_nodes[camera.uid],
91-
env_separate_rigid=self._context.env_separate_rigid,
92-
rgb=False,
93-
normal=False,
94-
seg=True,
95-
depth=False,
96-
plane_reflection=False,
97-
shadow=False,
98-
)
99-
100-
# Unset the context
101-
self._renderer.make_uncurrent()
72+
try:
73+
if rgb or depth or normal:
74+
retval = self._renderer.render(
75+
self._context._scene,
76+
self._camera_targets[camera.uid],
77+
camera_node=self._camera_nodes[camera.uid],
78+
env_separate_rigid=self._context.env_separate_rigid,
79+
rgb=rgb,
80+
normal=normal,
81+
seg=False,
82+
depth=depth,
83+
plane_reflection=rgb and self._context.plane_reflection,
84+
shadow=rgb and self._context.shadow,
85+
)
86+
87+
if segmentation:
88+
seg_idxc_rgb_arr, *_ = self._renderer.render(
89+
self._context._scene,
90+
self._camera_targets[camera.uid],
91+
camera_node=self._camera_nodes[camera.uid],
92+
env_separate_rigid=self._context.env_separate_rigid,
93+
rgb=False,
94+
normal=False,
95+
seg=True,
96+
depth=False,
97+
plane_reflection=False,
98+
shadow=False,
99+
)
100+
finally:
101+
# Unset the context
102+
self._renderer.make_uncurrent()
102103
else:
103104
# Render
104105
if rgb or depth or normal:

genesis/vis/raytracer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ def add_camera(self, camera):
604604
if camera_model == "pinhole":
605605
self._cameras[camera_name] = LuisaRenderPy.PinholeCamera(
606606
name=camera_name,
607-
pose=self.get_transform(camera.transform),
607+
pose=self.get_transform(np.eye(4)),
608608
film=LuisaRenderPy.Film(resolution=camera.res),
609609
filter=LuisaRenderPy.Filter(),
610610
spp=camera.spp,
@@ -613,7 +613,7 @@ def add_camera(self, camera):
613613
elif camera_model == "thinlens":
614614
self._cameras[camera_name] = LuisaRenderPy.ThinLensCamera(
615615
name=camera_name,
616-
pose=self.get_transform(camera.transform),
616+
pose=self.get_transform(np.eye(4)),
617617
film=LuisaRenderPy.Film(resolution=camera.res),
618618
filter=LuisaRenderPy.Filter(),
619619
spp=camera.spp,

tests/test_render.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def test_segmentation(segmentation_level, particle_mode):
119119

120120
@pytest.mark.required
121121
@pytest.mark.flaky(reruns=3, condition=(sys.platform == "darwin"))
122-
def test_batched_offscreen_rendering(show_viewer, tol):
122+
def test_batched_offscreen_rendering(tmp_path, show_viewer, tol):
123123
scene = gs.Scene(
124124
vis_options=gs.options.VisOptions(
125125
# rendered_envs_idx=(0, 1, 2),
@@ -247,6 +247,7 @@ def test_batched_offscreen_rendering(show_viewer, tol):
247247
)
248248
scene.build(n_envs=3, env_spacing=(2.0, 2.0))
249249

250+
cam.start_recording()
250251
for _ in range(7):
251252
dofs_lower_bound, dofs_upper_bound = robot.get_dofs_limit()
252253
qpos = dofs_lower_bound + (dofs_upper_bound - dofs_lower_bound) * torch.rand(robot.n_qs)
@@ -270,6 +271,7 @@ def test_batched_offscreen_rendering(show_viewer, tol):
270271

271272
for i in range(3):
272273
assert_allclose(steps_rgb_arrays[0][i], steps_rgb_arrays[1][i], tol=tol)
274+
cam.stop_recording(save_to_filename=(tmp_path / "video.mp4"))
273275

274276

275277
@pytest.mark.required

0 commit comments

Comments
 (0)