File size: 3,874 Bytes
5d4d48e
b08eeb5
5d4d48e
6a9bf88
 
 
 
723e0cd
 
b08eeb5
 
 
 
 
 
 
 
723e0cd
 
 
 
 
 
 
 
 
 
b08eeb5
 
 
 
 
 
 
 
723e0cd
6a9bf88
 
 
 
 
 
 
 
 
 
 
5d4d48e
 
 
 
 
6a9bf88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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)