3D / renderer.py
nexusbert's picture
commit in
b08eeb5
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)