| import os | |
| import sys | |
| import numpy as np | |
| import trimesh | |
| from typing import Tuple, Optional | |
| os.environ['PYOPENGL_PLATFORM'] = 'osmesa' | |
| try: | |
| from OpenGL import osmesa | |
| if not hasattr(osmesa, 'OSMesaCreateContextAttribs'): | |
| osmesa.OSMesaCreateContextAttribs = osmesa.OSMesaCreateContext | |
| print("Patched OSMesaCreateContextAttribs to use OSMesaCreateContext") | |
| except Exception as patch_error: | |
| print(f"Could not patch OSMesa: {patch_error}") | |
| try: | |
| import pyrender | |
| except Exception as e: | |
| print(f"Warning: pyrender import failed with osmesa: {e}") | |
| try: | |
| os.environ['PYOPENGL_PLATFORM'] = 'egl' | |
| import pyrender | |
| print("Using EGL platform") | |
| except Exception as e2: | |
| print(f"Warning: pyrender import failed with egl: {e2}") | |
| try: | |
| os.environ['PYOPENGL_PLATFORM'] = 'glx' | |
| import pyrender | |
| print("Using GLX platform") | |
| except Exception as e3: | |
| print(f"Warning: pyrender import failed with glx: {e3}") | |
| os.environ['PYOPENGL_PLATFORM'] = 'osmesa' | |
| import pyrender | |
| class AvatarRenderer: | |
| def __init__( | |
| self, | |
| image_size: int = 512, | |
| camera_type: str = "orthographic", | |
| light_intensity: float = 2.0 | |
| ): | |
| self.image_size = image_size | |
| self.camera_type = camera_type | |
| self.light_intensity = light_intensity | |
| self.renderer = pyrender.OffscreenRenderer( | |
| viewport_width=image_size, | |
| viewport_height=image_size, | |
| point_size=1.0 | |
| ) | |
| def render( | |
| self, | |
| vertices: np.ndarray, | |
| faces: np.ndarray, | |
| camera_pose: Optional[np.ndarray] = None, | |
| light_pose: Optional[np.ndarray] = None | |
| ) -> np.ndarray: | |
| mesh = trimesh.Trimesh(vertices=vertices, faces=faces) | |
| mesh.vertices -= mesh.vertices.mean(axis=0) | |
| scale = 1.0 / (mesh.vertices.max(axis=0) - mesh.vertices.min(axis=0)).max() | |
| mesh.vertices *= scale * 0.8 | |
| pyrender_mesh = pyrender.Mesh.from_trimesh(mesh) | |
| scene = pyrender.Scene(bg_color=[1.0, 1.0, 1.0, 1.0]) | |
| scene.add(pyrender_mesh) | |
| if self.camera_type == "orthographic": | |
| camera = pyrender.OrthographicCamera(xmag=1.0, ymag=1.0) | |
| else: | |
| camera = pyrender.PerspectiveCamera(yfov=np.pi / 3.0, aspectRatio=1.0) | |
| if camera_pose is None: | |
| camera_pose = np.eye(4) | |
| camera_pose[:3, 3] = [0, 0, 2.5] | |
| scene.add(camera, pose=camera_pose) | |
| if light_pose is None: | |
| light_pose = np.eye(4) | |
| light_pose[:3, 3] = [0, 0, 2.5] | |
| light = pyrender.DirectionalLight(color=[1.0, 1.0, 1.0], intensity=self.light_intensity) | |
| scene.add(light, pose=light_pose) | |
| ambient_light = pyrender.SpotLight( | |
| color=[1.0, 1.0, 1.0], | |
| intensity=0.5, | |
| innerConeAngle=np.pi / 16.0, | |
| outerConeAngle=np.pi / 6.0 | |
| ) | |
| scene.add(ambient_light, pose=light_pose) | |
| color, depth = self.renderer.render(scene) | |
| if color.shape[2] == 4: | |
| color = color[:, :, :3] | |
| return color | |
| def __del__(self): | |
| if hasattr(self, 'renderer'): | |
| self.renderer.delete() | |
| _renderer_instance = None | |
| def get_renderer(image_size: int = 512, camera_type: str = "orthographic") -> AvatarRenderer: | |
| global _renderer_instance | |
| if _renderer_instance is None: | |
| _renderer_instance = AvatarRenderer(image_size=image_size, camera_type=camera_type) | |
| return _renderer_instance | |
| def render_avatar( | |
| vertices: np.ndarray, | |
| faces: np.ndarray, | |
| image_size: int = 512 | |
| ) -> np.ndarray: | |
| renderer = get_renderer(image_size=image_size) | |
| return renderer.render(vertices, faces) | |