|
|
import os |
|
|
import tempfile |
|
|
|
|
|
import cv2 |
|
|
import kiui.mesh |
|
|
import numpy as np |
|
|
|
|
|
|
|
|
os.environ['PYOPENGL_PLATFORM'] = 'egl' |
|
|
import pyrender |
|
|
import trimesh |
|
|
|
|
|
|
|
|
|
|
|
class MeshRenderer: |
|
|
def __init__(self, size, fov=16 / 180 * np.pi, camera_pose=None, light_pose=None, black_bg=False): |
|
|
|
|
|
self.frustum = {'near': 0.01, 'far': 3.0} |
|
|
self.camera = pyrender.PerspectiveCamera(yfov=fov, znear=self.frustum['near'], |
|
|
zfar=self.frustum['far'], aspectRatio=1.0) |
|
|
|
|
|
|
|
|
self.primitive_material = pyrender.material.MetallicRoughnessMaterial( |
|
|
alphaMode='BLEND', |
|
|
baseColorFactor=[0.3, 0.3, 0.3, 1.0], |
|
|
metallicFactor=0.8, |
|
|
roughnessFactor=0.8 |
|
|
) |
|
|
|
|
|
|
|
|
light_color = np.array([1., 1., 1.]) |
|
|
self.light = pyrender.DirectionalLight(color=light_color, intensity=2) |
|
|
self.light_angle = np.pi / 6.0 |
|
|
|
|
|
|
|
|
self.scene = None |
|
|
self._init_scene(black_bg) |
|
|
|
|
|
|
|
|
self._init_camera(camera_pose) |
|
|
self._init_lighting(light_pose) |
|
|
|
|
|
|
|
|
self.renderer = pyrender.OffscreenRenderer(*size, point_size=1.0) |
|
|
|
|
|
def _init_scene(self, black_bg=False): |
|
|
if black_bg: |
|
|
self.scene = pyrender.Scene(ambient_light=[.2, .2, .2], bg_color=[0, 0, 0]) |
|
|
else: |
|
|
self.scene = pyrender.Scene(ambient_light=[.2, .2, .2], bg_color=[255, 255, 255]) |
|
|
|
|
|
def _init_camera(self, camera_pose=None): |
|
|
if camera_pose is None: |
|
|
camera_pose = np.eye(4) |
|
|
camera_pose[:3, 3] = np.array([0, 0, 1]) |
|
|
self.camera_pose = camera_pose.copy() |
|
|
self.camera_node = self.scene.add(self.camera, pose=camera_pose) |
|
|
|
|
|
def _init_lighting(self, light_pose=None): |
|
|
if light_pose is None: |
|
|
light_pose = np.eye(4) |
|
|
light_pose[:3, 3] = np.array([0, 0, 1]) |
|
|
self.light_pose = light_pose.copy() |
|
|
|
|
|
light_poses = self._get_light_poses(self.light_angle, light_pose) |
|
|
self.light_nodes = [self.scene.add(self.light, pose=light_pose) for light_pose in light_poses] |
|
|
|
|
|
def set_camera_pose(self, camera_pose): |
|
|
self.camera_pose = camera_pose.copy() |
|
|
self.scene.set_pose(self.camera_node, pose=camera_pose) |
|
|
|
|
|
def set_lighting_pose(self, light_pose): |
|
|
self.light_pose = light_pose.copy() |
|
|
|
|
|
light_poses = self._get_light_poses(self.light_angle, light_pose) |
|
|
for light_node, light_pose in zip(self.light_nodes, light_poses): |
|
|
self.scene.set_pose(light_node, pose=light_pose) |
|
|
|
|
|
def render_mesh(self, v, f, t_center, rot=np.zeros(3), tex_img=None, tex_uv=None, |
|
|
camera_pose=None, light_pose=None): |
|
|
|
|
|
v[:] = cv2.Rodrigues(rot)[0].dot((v - t_center).T).T + t_center |
|
|
if tex_img is not None: |
|
|
tex = pyrender.Texture(source=tex_img, source_channels='RGB') |
|
|
tex_material = pyrender.material.MetallicRoughnessMaterial(baseColorTexture=tex) |
|
|
from kiui.mesh import Mesh |
|
|
import torch |
|
|
mesh = Mesh( |
|
|
v=torch.from_numpy(v), |
|
|
f=torch.from_numpy(f), |
|
|
vt=tex_uv['vt'], |
|
|
ft=tex_uv['ft'] |
|
|
) |
|
|
with tempfile.NamedTemporaryFile(suffix='.obj') as f: |
|
|
mesh.write_obj(f.name) |
|
|
tri_mesh = trimesh.load(f.name, process=False) |
|
|
return tri_mesh |
|
|
|
|
|
render_mesh = pyrender.Mesh.from_trimesh(tri_mesh, material=tex_material) |
|
|
else: |
|
|
tri_mesh = trimesh.Trimesh(vertices=v, faces=f) |
|
|
render_mesh = pyrender.Mesh.from_trimesh(tri_mesh, material=self.primitive_material, smooth=True) |
|
|
mesh_node = self.scene.add(render_mesh, pose=np.eye(4)) |
|
|
|
|
|
|
|
|
if camera_pose is not None: |
|
|
self.set_camera_pose(camera_pose) |
|
|
if light_pose is not None: |
|
|
self.set_lighting_pose(light_pose) |
|
|
|
|
|
|
|
|
flags = pyrender.RenderFlags.SKIP_CULL_FACES |
|
|
color, depth = self.renderer.render(self.scene, flags=flags) |
|
|
|
|
|
|
|
|
self.scene.remove_node(mesh_node) |
|
|
|
|
|
return color, depth |
|
|
|
|
|
@staticmethod |
|
|
def _get_light_poses(light_angle, light_pose): |
|
|
light_poses = [] |
|
|
init_pos = light_pose[:3, 3].copy() |
|
|
|
|
|
light_poses.append(light_pose.copy()) |
|
|
|
|
|
light_pose[:3, 3] = cv2.Rodrigues(np.array([light_angle, 0, 0]))[0].dot(init_pos) |
|
|
light_poses.append(light_pose.copy()) |
|
|
|
|
|
light_pose[:3, 3] = cv2.Rodrigues(np.array([-light_angle, 0, 0]))[0].dot(init_pos) |
|
|
light_poses.append(light_pose.copy()) |
|
|
|
|
|
light_pose[:3, 3] = cv2.Rodrigues(np.array([0, -light_angle, 0]))[0].dot(init_pos) |
|
|
light_poses.append(light_pose.copy()) |
|
|
|
|
|
light_pose[:3, 3] = cv2.Rodrigues(np.array([0, light_angle, 0]))[0].dot(init_pos) |
|
|
light_poses.append(light_pose.copy()) |
|
|
|
|
|
return light_poses |
|
|
|
|
|
@staticmethod |
|
|
def _pyrender_mesh_workaround(mesh): |
|
|
|
|
|
with tempfile.NamedTemporaryFile(suffix='.obj') as f: |
|
|
mesh.write_obj(f.name) |
|
|
tri_mesh = trimesh.load(f.name, process=False) |
|
|
return tri_mesh |
|
|
|