| """Pyrender-based SMPL mesh rendering with orthographic camera. |
| |
| Renders mesh frames that visually align with the skeleton renderer's |
| camera projection, so skeleton can be overlaid on top. |
| """ |
|
|
| import math |
| import os |
|
|
| import numpy as np |
|
|
| |
| if not hasattr(np, "infty"): |
| np.infty = np.inf |
|
|
| os.environ.setdefault("PYOPENGL_PLATFORM", "egl") |
|
|
| import pyrender |
| import trimesh |
|
|
|
|
| def render_mesh_frames(verts, faces, cam_params): |
| """Render SMPL mesh to a list of RGB image frames. |
| |
| Args: |
| verts: (T, V, 3) mesh vertices in world coordinates. |
| faces: (F, 3) triangle face indices. |
| cam_params: dict from compute_camera_params() containing camera |
| geometry that matches the skeleton renderer. |
| |
| Returns: |
| list of np.ndarray images (H, W, 3), uint8. |
| """ |
| T = verts.shape[0] |
| width = cam_params["width"] |
| height = cam_params["height"] |
| look_at = cam_params["look_at"] |
| distance = cam_params["distance"] |
| elevation = cam_params["elevation"] |
| azimuth = cam_params["azimuth"] |
| ortho_scale = cam_params["ortho_scale"] |
|
|
| |
| |
| |
| |
| xmag = ymag = ortho_scale * 1.25 |
|
|
| |
| front = np.array([ |
| math.cos(elevation) * math.cos(azimuth), |
| math.sin(elevation), |
| math.cos(elevation) * math.sin(azimuth), |
| ]) |
| front /= np.linalg.norm(front) |
| up = np.array([0.0, 1.0, 0.0]) |
| right = np.cross(front, up) |
| right /= np.linalg.norm(right) |
| up = np.cross(right, front) |
|
|
| |
| |
| |
| |
| |
| cam_pos = look_at - front * distance |
| cam_z = -front |
|
|
| R = np.stack([right, up, cam_z], axis=1) |
| cam_pose = np.eye(4) |
| cam_pose[:3, :3] = R |
| cam_pose[:3, 3] = cam_pos |
|
|
| |
| body_material = pyrender.MetallicRoughnessMaterial( |
| baseColorFactor=[0.7, 0.55, 0.45, 1.0], |
| metallicFactor=0.0, |
| roughnessFactor=0.7, |
| ) |
|
|
| |
| ground_material = pyrender.MetallicRoughnessMaterial( |
| baseColorFactor=[1.0, 1.0, 1.0, 1.0], |
| metallicFactor=0.0, |
| roughnessFactor=0.8, |
| ) |
|
|
| |
| x_min = cam_params["x_min"] |
| x_max = cam_params["x_max"] |
| z_min = cam_params["z_min"] |
| z_max = cam_params["z_max"] |
| padding = 1.0 |
| gv = np.array([ |
| [x_min - padding, 0, z_min - padding], |
| [x_max + padding, 0, z_min - padding], |
| [x_max + padding, 0, z_max + padding], |
| [x_min - padding, 0, z_max + padding], |
| ], dtype=np.float32) |
| gf = np.array([[0, 2, 1], [0, 3, 2]], dtype=np.int32) |
| ground_tri = trimesh.Trimesh(vertices=gv, faces=gf, process=False) |
| ground_mesh = pyrender.Mesh.from_trimesh(ground_tri, material=ground_material, smooth=True) |
|
|
| |
| scene = pyrender.Scene( |
| bg_color=[1.0, 1.0, 1.0, 1.0], |
| ambient_light=[0.4, 0.4, 0.4], |
| ) |
| scene.add(ground_mesh) |
|
|
| |
| main_light = pyrender.DirectionalLight(color=[1.0, 1.0, 1.0], intensity=3.0) |
| main_light_pose = np.array([ |
| [1, 0, 0, 0], |
| [0, 0.8, -0.6, 0], |
| [0, 0.6, 0.8, 0], |
| [0, 0, 0, 1], |
| ], dtype=np.float64) |
| scene.add(main_light, pose=main_light_pose) |
|
|
| fill_light = pyrender.DirectionalLight(color=[0.8, 0.8, 0.8], intensity=2.0) |
| fill_light_pose = np.array([ |
| [1, 0, 0, 0], |
| [0, 0.6, 0.8, 0], |
| [0, -0.8, 0.6, 0], |
| [0, 0, 0, 1], |
| ], dtype=np.float64) |
| scene.add(fill_light, pose=fill_light_pose) |
|
|
| |
| camera = pyrender.OrthographicCamera(xmag=xmag, ymag=ymag) |
| scene.add(camera, pose=cam_pose, name="ortho_cam") |
|
|
| |
| body_tri = trimesh.Trimesh(vertices=verts[0], faces=faces, process=False) |
| body_mesh = pyrender.Mesh.from_trimesh(body_tri, material=body_material, smooth=True) |
| body_node = scene.add(body_mesh) |
|
|
| |
| renderer = pyrender.OffscreenRenderer(width, height) |
| render_flags = pyrender.RenderFlags.SHADOWS_DIRECTIONAL |
|
|
| images = [] |
| try: |
| for t in range(T): |
| |
| scene.remove_node(body_node) |
| body_tri = trimesh.Trimesh(vertices=verts[t], faces=faces, process=False) |
| body_mesh = pyrender.Mesh.from_trimesh(body_tri, material=body_material, smooth=True) |
| body_node = scene.add(body_mesh) |
|
|
| color, _ = renderer.render(scene, flags=render_flags) |
| images.append(color) |
| finally: |
| renderer.delete() |
|
|
| return images |
|
|