Spaces:
Build error
Build error
Upload 5 files
Browse files- lib/renderer/camera.py +226 -0
- lib/renderer/glm.py +143 -0
- lib/renderer/mesh.py +526 -0
- lib/renderer/opengl_util.py +369 -0
- lib/renderer/prt_util.py +199 -0
lib/renderer/camera.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
| 5 |
+
# holder of all proprietary rights on this computer program.
|
| 6 |
+
# You can only use this computer program if you have closed
|
| 7 |
+
# a license agreement with MPG or you get the right to use the computer
|
| 8 |
+
# program from someone who is authorized to grant you that right.
|
| 9 |
+
# Any use of the computer program without a valid license is prohibited and
|
| 10 |
+
# liable to prosecution.
|
| 11 |
+
#
|
| 12 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
| 13 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
| 14 |
+
# for Intelligent Systems. All rights reserved.
|
| 15 |
+
#
|
| 16 |
+
# Contact: [email protected]
|
| 17 |
+
|
| 18 |
+
import cv2
|
| 19 |
+
import numpy as np
|
| 20 |
+
|
| 21 |
+
from .glm import ortho
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class Camera:
|
| 25 |
+
def __init__(self, width=1600, height=1200):
|
| 26 |
+
# Focal Length
|
| 27 |
+
# equivalent 50mm
|
| 28 |
+
focal = np.sqrt(width * width + height * height)
|
| 29 |
+
self.focal_x = focal
|
| 30 |
+
self.focal_y = focal
|
| 31 |
+
# Principal Point Offset
|
| 32 |
+
self.principal_x = width / 2
|
| 33 |
+
self.principal_y = height / 2
|
| 34 |
+
# Axis Skew
|
| 35 |
+
self.skew = 0
|
| 36 |
+
# Image Size
|
| 37 |
+
self.width = width
|
| 38 |
+
self.height = height
|
| 39 |
+
|
| 40 |
+
self.near = 1
|
| 41 |
+
self.far = 10
|
| 42 |
+
|
| 43 |
+
# Camera Center
|
| 44 |
+
self.center = np.array([0, 0, 1.6])
|
| 45 |
+
self.direction = np.array([0, 0, -1])
|
| 46 |
+
self.right = np.array([1, 0, 0])
|
| 47 |
+
self.up = np.array([0, 1, 0])
|
| 48 |
+
|
| 49 |
+
self.ortho_ratio = None
|
| 50 |
+
|
| 51 |
+
def sanity_check(self):
|
| 52 |
+
self.center = self.center.reshape([-1])
|
| 53 |
+
self.direction = self.direction.reshape([-1])
|
| 54 |
+
self.right = self.right.reshape([-1])
|
| 55 |
+
self.up = self.up.reshape([-1])
|
| 56 |
+
|
| 57 |
+
assert len(self.center) == 3
|
| 58 |
+
assert len(self.direction) == 3
|
| 59 |
+
assert len(self.right) == 3
|
| 60 |
+
assert len(self.up) == 3
|
| 61 |
+
|
| 62 |
+
@staticmethod
|
| 63 |
+
def normalize_vector(v):
|
| 64 |
+
v_norm = np.linalg.norm(v)
|
| 65 |
+
return v if v_norm == 0 else v / v_norm
|
| 66 |
+
|
| 67 |
+
def get_real_z_value(self, z):
|
| 68 |
+
z_near = self.near
|
| 69 |
+
z_far = self.far
|
| 70 |
+
z_n = 2.0 * z - 1.0
|
| 71 |
+
z_e = 2.0 * z_near * z_far / (z_far + z_near - z_n * (z_far - z_near))
|
| 72 |
+
return z_e
|
| 73 |
+
|
| 74 |
+
def get_rotation_matrix(self):
|
| 75 |
+
rot_mat = np.eye(3)
|
| 76 |
+
s = self.right
|
| 77 |
+
s = self.normalize_vector(s)
|
| 78 |
+
rot_mat[0, :] = s
|
| 79 |
+
u = self.up
|
| 80 |
+
u = self.normalize_vector(u)
|
| 81 |
+
rot_mat[1, :] = -u
|
| 82 |
+
rot_mat[2, :] = self.normalize_vector(self.direction)
|
| 83 |
+
|
| 84 |
+
return rot_mat
|
| 85 |
+
|
| 86 |
+
def get_translation_vector(self):
|
| 87 |
+
rot_mat = self.get_rotation_matrix()
|
| 88 |
+
trans = -np.dot(rot_mat, self.center)
|
| 89 |
+
return trans
|
| 90 |
+
|
| 91 |
+
def get_intrinsic_matrix(self):
|
| 92 |
+
int_mat = np.eye(3)
|
| 93 |
+
|
| 94 |
+
int_mat[0, 0] = self.focal_x
|
| 95 |
+
int_mat[1, 1] = self.focal_y
|
| 96 |
+
int_mat[0, 1] = self.skew
|
| 97 |
+
int_mat[0, 2] = self.principal_x
|
| 98 |
+
int_mat[1, 2] = self.principal_y
|
| 99 |
+
|
| 100 |
+
return int_mat
|
| 101 |
+
|
| 102 |
+
def get_projection_matrix(self):
|
| 103 |
+
ext_mat = self.get_extrinsic_matrix()
|
| 104 |
+
int_mat = self.get_intrinsic_matrix()
|
| 105 |
+
|
| 106 |
+
return np.matmul(int_mat, ext_mat)
|
| 107 |
+
|
| 108 |
+
def get_extrinsic_matrix(self):
|
| 109 |
+
rot_mat = self.get_rotation_matrix()
|
| 110 |
+
int_mat = self.get_intrinsic_matrix()
|
| 111 |
+
trans = self.get_translation_vector()
|
| 112 |
+
|
| 113 |
+
extrinsic = np.eye(4)
|
| 114 |
+
extrinsic[:3, :3] = rot_mat
|
| 115 |
+
extrinsic[:3, 3] = trans
|
| 116 |
+
|
| 117 |
+
return extrinsic[:3, :]
|
| 118 |
+
|
| 119 |
+
def set_rotation_matrix(self, rot_mat):
|
| 120 |
+
self.direction = rot_mat[2, :]
|
| 121 |
+
self.up = -rot_mat[1, :]
|
| 122 |
+
self.right = rot_mat[0, :]
|
| 123 |
+
|
| 124 |
+
def set_intrinsic_matrix(self, int_mat):
|
| 125 |
+
self.focal_x = int_mat[0, 0]
|
| 126 |
+
self.focal_y = int_mat[1, 1]
|
| 127 |
+
self.skew = int_mat[0, 1]
|
| 128 |
+
self.principal_x = int_mat[0, 2]
|
| 129 |
+
self.principal_y = int_mat[1, 2]
|
| 130 |
+
|
| 131 |
+
def set_projection_matrix(self, proj_mat):
|
| 132 |
+
res = cv2.decomposeProjectionMatrix(proj_mat)
|
| 133 |
+
int_mat, rot_mat, camera_center_homo = res[0], res[1], res[2]
|
| 134 |
+
camera_center = camera_center_homo[0:3] / camera_center_homo[3]
|
| 135 |
+
camera_center = camera_center.reshape(-1)
|
| 136 |
+
int_mat = int_mat / int_mat[2][2]
|
| 137 |
+
|
| 138 |
+
self.set_intrinsic_matrix(int_mat)
|
| 139 |
+
self.set_rotation_matrix(rot_mat)
|
| 140 |
+
self.center = camera_center
|
| 141 |
+
|
| 142 |
+
self.sanity_check()
|
| 143 |
+
|
| 144 |
+
def get_gl_matrix(self):
|
| 145 |
+
z_near = self.near
|
| 146 |
+
z_far = self.far
|
| 147 |
+
rot_mat = self.get_rotation_matrix()
|
| 148 |
+
int_mat = self.get_intrinsic_matrix()
|
| 149 |
+
trans = self.get_translation_vector()
|
| 150 |
+
|
| 151 |
+
extrinsic = np.eye(4)
|
| 152 |
+
extrinsic[:3, :3] = rot_mat
|
| 153 |
+
extrinsic[:3, 3] = trans
|
| 154 |
+
axis_adj = np.eye(4)
|
| 155 |
+
axis_adj[2, 2] = -1
|
| 156 |
+
axis_adj[1, 1] = -1
|
| 157 |
+
model_view = np.matmul(axis_adj, extrinsic)
|
| 158 |
+
|
| 159 |
+
projective = np.zeros([4, 4])
|
| 160 |
+
projective[:2, :2] = int_mat[:2, :2]
|
| 161 |
+
projective[:2, 2:3] = -int_mat[:2, 2:3]
|
| 162 |
+
projective[3, 2] = -1
|
| 163 |
+
projective[2, 2] = (z_near + z_far)
|
| 164 |
+
projective[2, 3] = (z_near * z_far)
|
| 165 |
+
|
| 166 |
+
if self.ortho_ratio is None:
|
| 167 |
+
ndc = ortho(0, self.width, 0, self.height, z_near, z_far)
|
| 168 |
+
perspective = np.matmul(ndc, projective)
|
| 169 |
+
else:
|
| 170 |
+
perspective = ortho(-self.width * self.ortho_ratio / 2,
|
| 171 |
+
self.width * self.ortho_ratio / 2,
|
| 172 |
+
-self.height * self.ortho_ratio / 2,
|
| 173 |
+
self.height * self.ortho_ratio / 2, z_near,
|
| 174 |
+
z_far)
|
| 175 |
+
|
| 176 |
+
return perspective, model_view
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def KRT_from_P(proj_mat, normalize_K=True):
|
| 180 |
+
res = cv2.decomposeProjectionMatrix(proj_mat)
|
| 181 |
+
K, Rot, camera_center_homog = res[0], res[1], res[2]
|
| 182 |
+
camera_center = camera_center_homog[0:3] / camera_center_homog[3]
|
| 183 |
+
trans = -Rot.dot(camera_center)
|
| 184 |
+
if normalize_K:
|
| 185 |
+
K = K / K[2][2]
|
| 186 |
+
return K, Rot, trans
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def MVP_from_P(proj_mat, width, height, near=0.1, far=10000):
|
| 190 |
+
'''
|
| 191 |
+
Convert OpenCV camera calibration matrix to OpenGL projection and model view matrix
|
| 192 |
+
:param proj_mat: OpenCV camera projeciton matrix
|
| 193 |
+
:param width: Image width
|
| 194 |
+
:param height: Image height
|
| 195 |
+
:param near: Z near value
|
| 196 |
+
:param far: Z far value
|
| 197 |
+
:return: OpenGL projection matrix and model view matrix
|
| 198 |
+
'''
|
| 199 |
+
res = cv2.decomposeProjectionMatrix(proj_mat)
|
| 200 |
+
K, Rot, camera_center_homog = res[0], res[1], res[2]
|
| 201 |
+
camera_center = camera_center_homog[0:3] / camera_center_homog[3]
|
| 202 |
+
trans = -Rot.dot(camera_center)
|
| 203 |
+
K = K / K[2][2]
|
| 204 |
+
|
| 205 |
+
extrinsic = np.eye(4)
|
| 206 |
+
extrinsic[:3, :3] = Rot
|
| 207 |
+
extrinsic[:3, 3:4] = trans
|
| 208 |
+
axis_adj = np.eye(4)
|
| 209 |
+
axis_adj[2, 2] = -1
|
| 210 |
+
axis_adj[1, 1] = -1
|
| 211 |
+
model_view = np.matmul(axis_adj, extrinsic)
|
| 212 |
+
|
| 213 |
+
zFar = far
|
| 214 |
+
zNear = near
|
| 215 |
+
projective = np.zeros([4, 4])
|
| 216 |
+
projective[:2, :2] = K[:2, :2]
|
| 217 |
+
projective[:2, 2:3] = -K[:2, 2:3]
|
| 218 |
+
projective[3, 2] = -1
|
| 219 |
+
projective[2, 2] = (zNear + zFar)
|
| 220 |
+
projective[2, 3] = (zNear * zFar)
|
| 221 |
+
|
| 222 |
+
ndc = ortho(0, width, 0, height, zNear, zFar)
|
| 223 |
+
|
| 224 |
+
perspective = np.matmul(ndc, projective)
|
| 225 |
+
|
| 226 |
+
return perspective, model_view
|
lib/renderer/glm.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
| 5 |
+
# holder of all proprietary rights on this computer program.
|
| 6 |
+
# You can only use this computer program if you have closed
|
| 7 |
+
# a license agreement with MPG or you get the right to use the computer
|
| 8 |
+
# program from someone who is authorized to grant you that right.
|
| 9 |
+
# Any use of the computer program without a valid license is prohibited and
|
| 10 |
+
# liable to prosecution.
|
| 11 |
+
#
|
| 12 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
| 13 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
| 14 |
+
# for Intelligent Systems. All rights reserved.
|
| 15 |
+
#
|
| 16 |
+
# Contact: [email protected]
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def vec3(x, y, z):
|
| 22 |
+
return np.array([x, y, z], dtype=np.float32)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def radians(v):
|
| 26 |
+
return np.radians(v)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def identity():
|
| 30 |
+
return np.identity(4, dtype=np.float32)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def empty():
|
| 34 |
+
return np.zeros([4, 4], dtype=np.float32)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def magnitude(v):
|
| 38 |
+
return np.linalg.norm(v)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def normalize(v):
|
| 42 |
+
m = magnitude(v)
|
| 43 |
+
return v if m == 0 else v / m
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def dot(u, v):
|
| 47 |
+
return np.sum(u * v)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def cross(u, v):
|
| 51 |
+
res = vec3(0, 0, 0)
|
| 52 |
+
res[0] = u[1] * v[2] - u[2] * v[1]
|
| 53 |
+
res[1] = u[2] * v[0] - u[0] * v[2]
|
| 54 |
+
res[2] = u[0] * v[1] - u[1] * v[0]
|
| 55 |
+
return res
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# below functions can be optimized
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def translate(m, v):
|
| 62 |
+
res = np.copy(m)
|
| 63 |
+
res[:, 3] = m[:, 0] * v[0] + m[:, 1] * v[1] + m[:, 2] * v[2] + m[:, 3]
|
| 64 |
+
return res
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def rotate(m, angle, v):
|
| 68 |
+
a = angle
|
| 69 |
+
c = np.cos(a)
|
| 70 |
+
s = np.sin(a)
|
| 71 |
+
|
| 72 |
+
axis = normalize(v)
|
| 73 |
+
temp = (1 - c) * axis
|
| 74 |
+
|
| 75 |
+
rot = empty()
|
| 76 |
+
rot[0][0] = c + temp[0] * axis[0]
|
| 77 |
+
rot[0][1] = temp[0] * axis[1] + s * axis[2]
|
| 78 |
+
rot[0][2] = temp[0] * axis[2] - s * axis[1]
|
| 79 |
+
|
| 80 |
+
rot[1][0] = temp[1] * axis[0] - s * axis[2]
|
| 81 |
+
rot[1][1] = c + temp[1] * axis[1]
|
| 82 |
+
rot[1][2] = temp[1] * axis[2] + s * axis[0]
|
| 83 |
+
|
| 84 |
+
rot[2][0] = temp[2] * axis[0] + s * axis[1]
|
| 85 |
+
rot[2][1] = temp[2] * axis[1] - s * axis[0]
|
| 86 |
+
rot[2][2] = c + temp[2] * axis[2]
|
| 87 |
+
|
| 88 |
+
res = empty()
|
| 89 |
+
res[:, 0] = m[:, 0] * rot[0][0] + m[:, 1] * rot[0][1] + m[:, 2] * rot[0][2]
|
| 90 |
+
res[:, 1] = m[:, 0] * rot[1][0] + m[:, 1] * rot[1][1] + m[:, 2] * rot[1][2]
|
| 91 |
+
res[:, 2] = m[:, 0] * rot[2][0] + m[:, 1] * rot[2][1] + m[:, 2] * rot[2][2]
|
| 92 |
+
res[:, 3] = m[:, 3]
|
| 93 |
+
return res
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def perspective(fovy, aspect, zNear, zFar):
|
| 97 |
+
tanHalfFovy = np.tan(fovy / 2)
|
| 98 |
+
|
| 99 |
+
res = empty()
|
| 100 |
+
res[0][0] = 1 / (aspect * tanHalfFovy)
|
| 101 |
+
res[1][1] = 1 / (tanHalfFovy)
|
| 102 |
+
res[2][3] = -1
|
| 103 |
+
res[2][2] = -(zFar + zNear) / (zFar - zNear)
|
| 104 |
+
res[3][2] = -(2 * zFar * zNear) / (zFar - zNear)
|
| 105 |
+
|
| 106 |
+
return res.T
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def ortho(left, right, bottom, top, zNear, zFar):
|
| 110 |
+
# res = np.ones([4, 4], dtype=np.float32)
|
| 111 |
+
res = identity()
|
| 112 |
+
res[0][0] = 2 / (right - left)
|
| 113 |
+
res[1][1] = 2 / (top - bottom)
|
| 114 |
+
res[2][2] = -2 / (zFar - zNear)
|
| 115 |
+
res[3][0] = -(right + left) / (right - left)
|
| 116 |
+
res[3][1] = -(top + bottom) / (top - bottom)
|
| 117 |
+
res[3][2] = -(zFar + zNear) / (zFar - zNear)
|
| 118 |
+
return res.T
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def lookat(eye, center, up):
|
| 122 |
+
f = normalize(center - eye)
|
| 123 |
+
s = normalize(cross(f, up))
|
| 124 |
+
u = cross(s, f)
|
| 125 |
+
|
| 126 |
+
res = identity()
|
| 127 |
+
res[0][0] = s[0]
|
| 128 |
+
res[1][0] = s[1]
|
| 129 |
+
res[2][0] = s[2]
|
| 130 |
+
res[0][1] = u[0]
|
| 131 |
+
res[1][1] = u[1]
|
| 132 |
+
res[2][1] = u[2]
|
| 133 |
+
res[0][2] = -f[0]
|
| 134 |
+
res[1][2] = -f[1]
|
| 135 |
+
res[2][2] = -f[2]
|
| 136 |
+
res[3][0] = -dot(s, eye)
|
| 137 |
+
res[3][1] = -dot(u, eye)
|
| 138 |
+
res[3][2] = -dot(f, eye)
|
| 139 |
+
return res.T
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def transform(d, m):
|
| 143 |
+
return np.dot(m, d.T).T
|
lib/renderer/mesh.py
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
| 5 |
+
# holder of all proprietary rights on this computer program.
|
| 6 |
+
# You can only use this computer program if you have closed
|
| 7 |
+
# a license agreement with MPG or you get the right to use the computer
|
| 8 |
+
# program from someone who is authorized to grant you that right.
|
| 9 |
+
# Any use of the computer program without a valid license is prohibited and
|
| 10 |
+
# liable to prosecution.
|
| 11 |
+
#
|
| 12 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
| 13 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
| 14 |
+
# for Intelligent Systems. All rights reserved.
|
| 15 |
+
#
|
| 16 |
+
# Contact: [email protected]
|
| 17 |
+
|
| 18 |
+
from lib.dataset.mesh_util import SMPLX
|
| 19 |
+
from lib.common.render_utils import face_vertices
|
| 20 |
+
import numpy as np
|
| 21 |
+
import lib.smplx as smplx
|
| 22 |
+
import trimesh
|
| 23 |
+
import torch
|
| 24 |
+
import torch.nn.functional as F
|
| 25 |
+
|
| 26 |
+
model_init_params = dict(
|
| 27 |
+
gender='male',
|
| 28 |
+
model_type='smplx',
|
| 29 |
+
model_path=SMPLX().model_dir,
|
| 30 |
+
create_global_orient=False,
|
| 31 |
+
create_body_pose=False,
|
| 32 |
+
create_betas=False,
|
| 33 |
+
create_left_hand_pose=False,
|
| 34 |
+
create_right_hand_pose=False,
|
| 35 |
+
create_expression=False,
|
| 36 |
+
create_jaw_pose=False,
|
| 37 |
+
create_leye_pose=False,
|
| 38 |
+
create_reye_pose=False,
|
| 39 |
+
create_transl=False,
|
| 40 |
+
num_pca_comps=12)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def get_smpl_model(model_type, gender): return smplx.create(
|
| 44 |
+
**model_init_params)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def normalization(data):
|
| 48 |
+
_range = np.max(data) - np.min(data)
|
| 49 |
+
return ((data - np.min(data)) / _range)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def sigmoid(x):
|
| 53 |
+
z = 1 / (1 + np.exp(-x))
|
| 54 |
+
return z
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def load_fit_body(fitted_path, scale, smpl_type='smplx', smpl_gender='neutral', noise_dict=None):
|
| 58 |
+
|
| 59 |
+
param = np.load(fitted_path, allow_pickle=True)
|
| 60 |
+
for key in param.keys():
|
| 61 |
+
param[key] = torch.as_tensor(param[key])
|
| 62 |
+
|
| 63 |
+
smpl_model = get_smpl_model(smpl_type, smpl_gender)
|
| 64 |
+
model_forward_params = dict(betas=param['betas'],
|
| 65 |
+
global_orient=param['global_orient'],
|
| 66 |
+
body_pose=param['body_pose'],
|
| 67 |
+
left_hand_pose=param['left_hand_pose'],
|
| 68 |
+
right_hand_pose=param['right_hand_pose'],
|
| 69 |
+
jaw_pose=param['jaw_pose'],
|
| 70 |
+
leye_pose=param['leye_pose'],
|
| 71 |
+
reye_pose=param['reye_pose'],
|
| 72 |
+
expression=param['expression'],
|
| 73 |
+
return_verts=True)
|
| 74 |
+
|
| 75 |
+
if noise_dict is not None:
|
| 76 |
+
model_forward_params.update(noise_dict)
|
| 77 |
+
|
| 78 |
+
smpl_out = smpl_model(**model_forward_params)
|
| 79 |
+
|
| 80 |
+
smpl_verts = (
|
| 81 |
+
(smpl_out.vertices[0] * param['scale'] + param['translation']) * scale).detach()
|
| 82 |
+
smpl_joints = (
|
| 83 |
+
(smpl_out.joints[0] * param['scale'] + param['translation']) * scale).detach()
|
| 84 |
+
smpl_mesh = trimesh.Trimesh(smpl_verts,
|
| 85 |
+
smpl_model.faces,
|
| 86 |
+
process=False, maintain_order=True)
|
| 87 |
+
|
| 88 |
+
return smpl_mesh, smpl_joints
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def load_ori_fit_body(fitted_path, smpl_type='smplx', smpl_gender='neutral'):
|
| 92 |
+
|
| 93 |
+
param = np.load(fitted_path, allow_pickle=True)
|
| 94 |
+
for key in param.keys():
|
| 95 |
+
param[key] = torch.as_tensor(param[key])
|
| 96 |
+
|
| 97 |
+
smpl_model = get_smpl_model(smpl_type, smpl_gender)
|
| 98 |
+
model_forward_params = dict(betas=param['betas'],
|
| 99 |
+
global_orient=param['global_orient'],
|
| 100 |
+
body_pose=param['body_pose'],
|
| 101 |
+
left_hand_pose=param['left_hand_pose'],
|
| 102 |
+
right_hand_pose=param['right_hand_pose'],
|
| 103 |
+
jaw_pose=param['jaw_pose'],
|
| 104 |
+
leye_pose=param['leye_pose'],
|
| 105 |
+
reye_pose=param['reye_pose'],
|
| 106 |
+
expression=param['expression'],
|
| 107 |
+
return_verts=True)
|
| 108 |
+
|
| 109 |
+
smpl_out = smpl_model(**model_forward_params)
|
| 110 |
+
|
| 111 |
+
smpl_verts = smpl_out.vertices[0].detach()
|
| 112 |
+
smpl_mesh = trimesh.Trimesh(smpl_verts,
|
| 113 |
+
smpl_model.faces,
|
| 114 |
+
process=False, maintain_order=True)
|
| 115 |
+
|
| 116 |
+
return smpl_mesh
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def save_obj_mesh(mesh_path, verts, faces):
|
| 120 |
+
file = open(mesh_path, 'w')
|
| 121 |
+
for v in verts:
|
| 122 |
+
file.write('v %.4f %.4f %.4f\n' % (v[0], v[1], v[2]))
|
| 123 |
+
for f in faces:
|
| 124 |
+
f_plus = f + 1
|
| 125 |
+
file.write('f %d %d %d\n' % (f_plus[0], f_plus[1], f_plus[2]))
|
| 126 |
+
file.close()
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# https://github.com/ratcave/wavefront_reader
|
| 130 |
+
def read_mtlfile(fname):
|
| 131 |
+
materials = {}
|
| 132 |
+
with open(fname) as f:
|
| 133 |
+
lines = f.read().splitlines()
|
| 134 |
+
|
| 135 |
+
for line in lines:
|
| 136 |
+
if line:
|
| 137 |
+
split_line = line.strip().split(' ', 1)
|
| 138 |
+
if len(split_line) < 2:
|
| 139 |
+
continue
|
| 140 |
+
|
| 141 |
+
prefix, data = split_line[0], split_line[1]
|
| 142 |
+
if 'newmtl' in prefix:
|
| 143 |
+
material = {}
|
| 144 |
+
materials[data] = material
|
| 145 |
+
elif materials:
|
| 146 |
+
if data:
|
| 147 |
+
split_data = data.strip().split(' ')
|
| 148 |
+
|
| 149 |
+
# assume texture maps are in the same level
|
| 150 |
+
# WARNING: do not include space in your filename!!
|
| 151 |
+
if 'map' in prefix:
|
| 152 |
+
material[prefix] = split_data[-1].split('\\')[-1]
|
| 153 |
+
elif len(split_data) > 1:
|
| 154 |
+
material[prefix] = tuple(float(d) for d in split_data)
|
| 155 |
+
else:
|
| 156 |
+
try:
|
| 157 |
+
material[prefix] = int(data)
|
| 158 |
+
except ValueError:
|
| 159 |
+
material[prefix] = float(data)
|
| 160 |
+
|
| 161 |
+
return materials
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def load_obj_mesh_mtl(mesh_file):
|
| 165 |
+
vertex_data = []
|
| 166 |
+
norm_data = []
|
| 167 |
+
uv_data = []
|
| 168 |
+
|
| 169 |
+
face_data = []
|
| 170 |
+
face_norm_data = []
|
| 171 |
+
face_uv_data = []
|
| 172 |
+
|
| 173 |
+
# face per material
|
| 174 |
+
face_data_mat = {}
|
| 175 |
+
face_norm_data_mat = {}
|
| 176 |
+
face_uv_data_mat = {}
|
| 177 |
+
|
| 178 |
+
# current material name
|
| 179 |
+
mtl_data = None
|
| 180 |
+
cur_mat = None
|
| 181 |
+
|
| 182 |
+
if isinstance(mesh_file, str):
|
| 183 |
+
f = open(mesh_file, "r")
|
| 184 |
+
else:
|
| 185 |
+
f = mesh_file
|
| 186 |
+
for line in f:
|
| 187 |
+
if isinstance(line, bytes):
|
| 188 |
+
line = line.decode("utf-8")
|
| 189 |
+
if line.startswith('#'):
|
| 190 |
+
continue
|
| 191 |
+
values = line.split()
|
| 192 |
+
if not values:
|
| 193 |
+
continue
|
| 194 |
+
|
| 195 |
+
if values[0] == 'v':
|
| 196 |
+
v = list(map(float, values[1:4]))
|
| 197 |
+
vertex_data.append(v)
|
| 198 |
+
elif values[0] == 'vn':
|
| 199 |
+
vn = list(map(float, values[1:4]))
|
| 200 |
+
norm_data.append(vn)
|
| 201 |
+
elif values[0] == 'vt':
|
| 202 |
+
vt = list(map(float, values[1:3]))
|
| 203 |
+
uv_data.append(vt)
|
| 204 |
+
elif values[0] == 'mtllib':
|
| 205 |
+
mtl_data = read_mtlfile(
|
| 206 |
+
mesh_file.replace(mesh_file.split('/')[-1], values[1]))
|
| 207 |
+
elif values[0] == 'usemtl':
|
| 208 |
+
cur_mat = values[1]
|
| 209 |
+
elif values[0] == 'f':
|
| 210 |
+
# local triangle data
|
| 211 |
+
l_face_data = []
|
| 212 |
+
l_face_uv_data = []
|
| 213 |
+
l_face_norm_data = []
|
| 214 |
+
|
| 215 |
+
# quad mesh
|
| 216 |
+
if len(values) > 4:
|
| 217 |
+
f = list(
|
| 218 |
+
map(
|
| 219 |
+
lambda x: int(x.split('/')[0]) if int(x.split('/')[0])
|
| 220 |
+
< 0 else int(x.split('/')[0]) - 1, values[1:4]))
|
| 221 |
+
l_face_data.append(f)
|
| 222 |
+
f = list(
|
| 223 |
+
map(
|
| 224 |
+
lambda x: int(x.split('/')[0])
|
| 225 |
+
if int(x.split('/')[0]) < 0 else int(x.split('/')[0]) -
|
| 226 |
+
1, [values[3], values[4], values[1]]))
|
| 227 |
+
l_face_data.append(f)
|
| 228 |
+
# tri mesh
|
| 229 |
+
else:
|
| 230 |
+
f = list(
|
| 231 |
+
map(
|
| 232 |
+
lambda x: int(x.split('/')[0]) if int(x.split('/')[0])
|
| 233 |
+
< 0 else int(x.split('/')[0]) - 1, values[1:4]))
|
| 234 |
+
l_face_data.append(f)
|
| 235 |
+
# deal with texture
|
| 236 |
+
if len(values[1].split('/')) >= 2:
|
| 237 |
+
# quad mesh
|
| 238 |
+
if len(values) > 4:
|
| 239 |
+
f = list(
|
| 240 |
+
map(
|
| 241 |
+
lambda x: int(x.split('/')[1])
|
| 242 |
+
if int(x.split('/')[1]) < 0 else int(
|
| 243 |
+
x.split('/')[1]) - 1, values[1:4]))
|
| 244 |
+
l_face_uv_data.append(f)
|
| 245 |
+
f = list(
|
| 246 |
+
map(
|
| 247 |
+
lambda x: int(x.split('/')[1])
|
| 248 |
+
if int(x.split('/')[1]) < 0 else int(
|
| 249 |
+
x.split('/')[1]) - 1,
|
| 250 |
+
[values[3], values[4], values[1]]))
|
| 251 |
+
l_face_uv_data.append(f)
|
| 252 |
+
# tri mesh
|
| 253 |
+
elif len(values[1].split('/')[1]) != 0:
|
| 254 |
+
f = list(
|
| 255 |
+
map(
|
| 256 |
+
lambda x: int(x.split('/')[1])
|
| 257 |
+
if int(x.split('/')[1]) < 0 else int(
|
| 258 |
+
x.split('/')[1]) - 1, values[1:4]))
|
| 259 |
+
l_face_uv_data.append(f)
|
| 260 |
+
# deal with normal
|
| 261 |
+
if len(values[1].split('/')) == 3:
|
| 262 |
+
# quad mesh
|
| 263 |
+
if len(values) > 4:
|
| 264 |
+
f = list(
|
| 265 |
+
map(
|
| 266 |
+
lambda x: int(x.split('/')[2])
|
| 267 |
+
if int(x.split('/')[2]) < 0 else int(
|
| 268 |
+
x.split('/')[2]) - 1, values[1:4]))
|
| 269 |
+
l_face_norm_data.append(f)
|
| 270 |
+
f = list(
|
| 271 |
+
map(
|
| 272 |
+
lambda x: int(x.split('/')[2])
|
| 273 |
+
if int(x.split('/')[2]) < 0 else int(
|
| 274 |
+
x.split('/')[2]) - 1,
|
| 275 |
+
[values[3], values[4], values[1]]))
|
| 276 |
+
l_face_norm_data.append(f)
|
| 277 |
+
# tri mesh
|
| 278 |
+
elif len(values[1].split('/')[2]) != 0:
|
| 279 |
+
f = list(
|
| 280 |
+
map(
|
| 281 |
+
lambda x: int(x.split('/')[2])
|
| 282 |
+
if int(x.split('/')[2]) < 0 else int(
|
| 283 |
+
x.split('/')[2]) - 1, values[1:4]))
|
| 284 |
+
l_face_norm_data.append(f)
|
| 285 |
+
|
| 286 |
+
face_data += l_face_data
|
| 287 |
+
face_uv_data += l_face_uv_data
|
| 288 |
+
face_norm_data += l_face_norm_data
|
| 289 |
+
|
| 290 |
+
if cur_mat is not None:
|
| 291 |
+
if cur_mat not in face_data_mat.keys():
|
| 292 |
+
face_data_mat[cur_mat] = []
|
| 293 |
+
if cur_mat not in face_uv_data_mat.keys():
|
| 294 |
+
face_uv_data_mat[cur_mat] = []
|
| 295 |
+
if cur_mat not in face_norm_data_mat.keys():
|
| 296 |
+
face_norm_data_mat[cur_mat] = []
|
| 297 |
+
face_data_mat[cur_mat] += l_face_data
|
| 298 |
+
face_uv_data_mat[cur_mat] += l_face_uv_data
|
| 299 |
+
face_norm_data_mat[cur_mat] += l_face_norm_data
|
| 300 |
+
|
| 301 |
+
vertices = np.array(vertex_data)
|
| 302 |
+
faces = np.array(face_data)
|
| 303 |
+
|
| 304 |
+
norms = np.array(norm_data)
|
| 305 |
+
norms = normalize_v3(norms)
|
| 306 |
+
face_normals = np.array(face_norm_data)
|
| 307 |
+
|
| 308 |
+
uvs = np.array(uv_data)
|
| 309 |
+
face_uvs = np.array(face_uv_data)
|
| 310 |
+
|
| 311 |
+
out_tuple = (vertices, faces, norms, face_normals, uvs, face_uvs)
|
| 312 |
+
|
| 313 |
+
if cur_mat is not None and mtl_data is not None:
|
| 314 |
+
for key in face_data_mat:
|
| 315 |
+
face_data_mat[key] = np.array(face_data_mat[key])
|
| 316 |
+
face_uv_data_mat[key] = np.array(face_uv_data_mat[key])
|
| 317 |
+
face_norm_data_mat[key] = np.array(face_norm_data_mat[key])
|
| 318 |
+
|
| 319 |
+
out_tuple += (face_data_mat, face_norm_data_mat, face_uv_data_mat,
|
| 320 |
+
mtl_data)
|
| 321 |
+
|
| 322 |
+
return out_tuple
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def load_scan(mesh_file, with_normal=False, with_texture=False):
|
| 326 |
+
vertex_data = []
|
| 327 |
+
norm_data = []
|
| 328 |
+
uv_data = []
|
| 329 |
+
|
| 330 |
+
face_data = []
|
| 331 |
+
face_norm_data = []
|
| 332 |
+
face_uv_data = []
|
| 333 |
+
|
| 334 |
+
if isinstance(mesh_file, str):
|
| 335 |
+
f = open(mesh_file, "r")
|
| 336 |
+
else:
|
| 337 |
+
f = mesh_file
|
| 338 |
+
for line in f:
|
| 339 |
+
if isinstance(line, bytes):
|
| 340 |
+
line = line.decode("utf-8")
|
| 341 |
+
if line.startswith('#'):
|
| 342 |
+
continue
|
| 343 |
+
values = line.split()
|
| 344 |
+
if not values:
|
| 345 |
+
continue
|
| 346 |
+
|
| 347 |
+
if values[0] == 'v':
|
| 348 |
+
v = list(map(float, values[1:4]))
|
| 349 |
+
vertex_data.append(v)
|
| 350 |
+
elif values[0] == 'vn':
|
| 351 |
+
vn = list(map(float, values[1:4]))
|
| 352 |
+
norm_data.append(vn)
|
| 353 |
+
elif values[0] == 'vt':
|
| 354 |
+
vt = list(map(float, values[1:3]))
|
| 355 |
+
uv_data.append(vt)
|
| 356 |
+
|
| 357 |
+
elif values[0] == 'f':
|
| 358 |
+
# quad mesh
|
| 359 |
+
if len(values) > 4:
|
| 360 |
+
f = list(map(lambda x: int(x.split('/')[0]), values[1:4]))
|
| 361 |
+
face_data.append(f)
|
| 362 |
+
f = list(
|
| 363 |
+
map(lambda x: int(x.split('/')[0]),
|
| 364 |
+
[values[3], values[4], values[1]]))
|
| 365 |
+
face_data.append(f)
|
| 366 |
+
# tri mesh
|
| 367 |
+
else:
|
| 368 |
+
f = list(map(lambda x: int(x.split('/')[0]), values[1:4]))
|
| 369 |
+
face_data.append(f)
|
| 370 |
+
|
| 371 |
+
# deal with texture
|
| 372 |
+
if len(values[1].split('/')) >= 2:
|
| 373 |
+
# quad mesh
|
| 374 |
+
if len(values) > 4:
|
| 375 |
+
f = list(map(lambda x: int(x.split('/')[1]), values[1:4]))
|
| 376 |
+
face_uv_data.append(f)
|
| 377 |
+
f = list(
|
| 378 |
+
map(lambda x: int(x.split('/')[1]),
|
| 379 |
+
[values[3], values[4], values[1]]))
|
| 380 |
+
face_uv_data.append(f)
|
| 381 |
+
# tri mesh
|
| 382 |
+
elif len(values[1].split('/')[1]) != 0:
|
| 383 |
+
f = list(map(lambda x: int(x.split('/')[1]), values[1:4]))
|
| 384 |
+
face_uv_data.append(f)
|
| 385 |
+
# deal with normal
|
| 386 |
+
if len(values[1].split('/')) == 3:
|
| 387 |
+
# quad mesh
|
| 388 |
+
if len(values) > 4:
|
| 389 |
+
f = list(map(lambda x: int(x.split('/')[2]), values[1:4]))
|
| 390 |
+
face_norm_data.append(f)
|
| 391 |
+
f = list(
|
| 392 |
+
map(lambda x: int(x.split('/')[2]),
|
| 393 |
+
[values[3], values[4], values[1]]))
|
| 394 |
+
face_norm_data.append(f)
|
| 395 |
+
# tri mesh
|
| 396 |
+
elif len(values[1].split('/')[2]) != 0:
|
| 397 |
+
f = list(map(lambda x: int(x.split('/')[2]), values[1:4]))
|
| 398 |
+
face_norm_data.append(f)
|
| 399 |
+
|
| 400 |
+
vertices = np.array(vertex_data)
|
| 401 |
+
faces = np.array(face_data) - 1
|
| 402 |
+
|
| 403 |
+
if with_texture and with_normal:
|
| 404 |
+
uvs = np.array(uv_data)
|
| 405 |
+
face_uvs = np.array(face_uv_data) - 1
|
| 406 |
+
norms = np.array(norm_data)
|
| 407 |
+
if norms.shape[0] == 0:
|
| 408 |
+
norms = compute_normal(vertices, faces)
|
| 409 |
+
face_normals = faces
|
| 410 |
+
else:
|
| 411 |
+
norms = normalize_v3(norms)
|
| 412 |
+
face_normals = np.array(face_norm_data) - 1
|
| 413 |
+
return vertices, faces, norms, face_normals, uvs, face_uvs
|
| 414 |
+
|
| 415 |
+
if with_texture:
|
| 416 |
+
uvs = np.array(uv_data)
|
| 417 |
+
face_uvs = np.array(face_uv_data) - 1
|
| 418 |
+
return vertices, faces, uvs, face_uvs
|
| 419 |
+
|
| 420 |
+
if with_normal:
|
| 421 |
+
norms = np.array(norm_data)
|
| 422 |
+
norms = normalize_v3(norms)
|
| 423 |
+
face_normals = np.array(face_norm_data) - 1
|
| 424 |
+
return vertices, faces, norms, face_normals
|
| 425 |
+
|
| 426 |
+
return vertices, faces
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
def normalize_v3(arr):
|
| 430 |
+
''' Normalize a numpy array of 3 component vectors shape=(n,3) '''
|
| 431 |
+
lens = np.sqrt(arr[:, 0]**2 + arr[:, 1]**2 + arr[:, 2]**2)
|
| 432 |
+
eps = 0.00000001
|
| 433 |
+
lens[lens < eps] = eps
|
| 434 |
+
arr[:, 0] /= lens
|
| 435 |
+
arr[:, 1] /= lens
|
| 436 |
+
arr[:, 2] /= lens
|
| 437 |
+
return arr
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
def compute_normal(vertices, faces):
|
| 441 |
+
# Create a zeroed array with the same type and shape as our vertices i.e., per vertex normal
|
| 442 |
+
norm = np.zeros(vertices.shape, dtype=vertices.dtype)
|
| 443 |
+
# Create an indexed view into the vertex array using the array of three indices for triangles
|
| 444 |
+
tris = vertices[faces]
|
| 445 |
+
# Calculate the normal for all the triangles, by taking the cross product of the vectors v1-v0, and v2-v0 in each triangle
|
| 446 |
+
n = np.cross(tris[::, 1] - tris[::, 0], tris[::, 2] - tris[::, 0])
|
| 447 |
+
# n is now an array of normals per triangle. The length of each normal is dependent the vertices,
|
| 448 |
+
# we need to normalize these, so that our next step weights each normal equally.
|
| 449 |
+
normalize_v3(n)
|
| 450 |
+
# now we have a normalized array of normals, one per triangle, i.e., per triangle normals.
|
| 451 |
+
# But instead of one per triangle (i.e., flat shading), we add to each vertex in that triangle,
|
| 452 |
+
# the triangles' normal. Multiple triangles would then contribute to every vertex, so we need to normalize again afterwards.
|
| 453 |
+
# The cool part, we can actually add the normals through an indexed view of our (zeroed) per vertex normal array
|
| 454 |
+
norm[faces[:, 0]] += n
|
| 455 |
+
norm[faces[:, 1]] += n
|
| 456 |
+
norm[faces[:, 2]] += n
|
| 457 |
+
normalize_v3(norm)
|
| 458 |
+
|
| 459 |
+
return norm
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def compute_normal_batch(vertices, faces):
|
| 463 |
+
|
| 464 |
+
bs, nv = vertices.shape[:2]
|
| 465 |
+
bs, nf = faces.shape[:2]
|
| 466 |
+
|
| 467 |
+
vert_norm = torch.zeros(bs * nv, 3).type_as(vertices)
|
| 468 |
+
tris = face_vertices(vertices, faces)
|
| 469 |
+
face_norm = F.normalize(torch.cross(tris[:, :, 1] - tris[:, :, 0],
|
| 470 |
+
tris[:, :, 2] - tris[:, :, 0]),
|
| 471 |
+
dim=-1)
|
| 472 |
+
|
| 473 |
+
faces = (faces +
|
| 474 |
+
(torch.arange(bs).type_as(faces) * nv)[:, None, None]).view(
|
| 475 |
+
-1, 3)
|
| 476 |
+
|
| 477 |
+
vert_norm[faces[:, 0]] += face_norm.view(-1, 3)
|
| 478 |
+
vert_norm[faces[:, 1]] += face_norm.view(-1, 3)
|
| 479 |
+
vert_norm[faces[:, 2]] += face_norm.view(-1, 3)
|
| 480 |
+
|
| 481 |
+
vert_norm = F.normalize(vert_norm, dim=-1).view(bs, nv, 3)
|
| 482 |
+
|
| 483 |
+
return vert_norm
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
# compute tangent and bitangent
|
| 487 |
+
def compute_tangent(vertices, faces, normals, uvs, faceuvs):
|
| 488 |
+
# NOTE: this could be numerically unstable around [0,0,1]
|
| 489 |
+
# but other current solutions are pretty freaky somehow
|
| 490 |
+
c1 = np.cross(normals, np.array([0, 1, 0.0]))
|
| 491 |
+
tan = c1
|
| 492 |
+
normalize_v3(tan)
|
| 493 |
+
btan = np.cross(normals, tan)
|
| 494 |
+
|
| 495 |
+
# NOTE: traditional version is below
|
| 496 |
+
|
| 497 |
+
# pts_tris = vertices[faces]
|
| 498 |
+
# uv_tris = uvs[faceuvs]
|
| 499 |
+
|
| 500 |
+
# W = np.stack([pts_tris[::, 1] - pts_tris[::, 0], pts_tris[::, 2] - pts_tris[::, 0]],2)
|
| 501 |
+
# UV = np.stack([uv_tris[::, 1] - uv_tris[::, 0], uv_tris[::, 2] - uv_tris[::, 0]], 1)
|
| 502 |
+
|
| 503 |
+
# for i in range(W.shape[0]):
|
| 504 |
+
# W[i,::] = W[i,::].dot(np.linalg.inv(UV[i,::]))
|
| 505 |
+
|
| 506 |
+
# tan = np.zeros(vertices.shape, dtype=vertices.dtype)
|
| 507 |
+
# tan[faces[:,0]] += W[:,:,0]
|
| 508 |
+
# tan[faces[:,1]] += W[:,:,0]
|
| 509 |
+
# tan[faces[:,2]] += W[:,:,0]
|
| 510 |
+
|
| 511 |
+
# btan = np.zeros(vertices.shape, dtype=vertices.dtype)
|
| 512 |
+
# btan[faces[:,0]] += W[:,:,1]
|
| 513 |
+
# btan[faces[:,1]] += W[:,:,1]
|
| 514 |
+
# btan[faces[:,2]] += W[:,:,1]
|
| 515 |
+
|
| 516 |
+
# normalize_v3(tan)
|
| 517 |
+
|
| 518 |
+
# ndott = np.sum(normals*tan, 1, keepdims=True)
|
| 519 |
+
# tan = tan - ndott * normals
|
| 520 |
+
|
| 521 |
+
# normalize_v3(btan)
|
| 522 |
+
# normalize_v3(tan)
|
| 523 |
+
|
| 524 |
+
# tan[np.sum(np.cross(normals, tan) * btan, 1) < 0,:] *= -1.0
|
| 525 |
+
|
| 526 |
+
return tan, btan
|
lib/renderer/opengl_util.py
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
| 5 |
+
# holder of all proprietary rights on this computer program.
|
| 6 |
+
# You can only use this computer program if you have closed
|
| 7 |
+
# a license agreement with MPG or you get the right to use the computer
|
| 8 |
+
# program from someone who is authorized to grant you that right.
|
| 9 |
+
# Any use of the computer program without a valid license is prohibited and
|
| 10 |
+
# liable to prosecution.
|
| 11 |
+
#
|
| 12 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
| 13 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
| 14 |
+
# for Intelligent Systems. All rights reserved.
|
| 15 |
+
#
|
| 16 |
+
# Contact: [email protected]
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
|
| 20 |
+
from lib.renderer.mesh import load_scan, compute_tangent
|
| 21 |
+
from lib.renderer.camera import Camera
|
| 22 |
+
import cv2
|
| 23 |
+
import math
|
| 24 |
+
import random
|
| 25 |
+
import numpy as np
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def render_result(rndr, shader_id, path, mask=False):
|
| 29 |
+
|
| 30 |
+
cam_render = rndr.get_color(shader_id)
|
| 31 |
+
cam_render = cv2.cvtColor(cam_render, cv2.COLOR_RGBA2BGRA)
|
| 32 |
+
|
| 33 |
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
| 34 |
+
if shader_id != 2:
|
| 35 |
+
cv2.imwrite(path, np.uint8(255.0 * cam_render))
|
| 36 |
+
else:
|
| 37 |
+
cam_render[:, :, -1] -= 0.5
|
| 38 |
+
cam_render[:, :, -1] *= 2.0
|
| 39 |
+
if not mask:
|
| 40 |
+
cv2.imwrite(path, np.uint8(255.0 / 2.0 * (cam_render + 1.0)))
|
| 41 |
+
else:
|
| 42 |
+
cv2.imwrite(path, np.uint8(-1.0 * cam_render[:, :, [3]]))
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def make_rotate(rx, ry, rz):
|
| 46 |
+
sinX = np.sin(rx)
|
| 47 |
+
sinY = np.sin(ry)
|
| 48 |
+
sinZ = np.sin(rz)
|
| 49 |
+
|
| 50 |
+
cosX = np.cos(rx)
|
| 51 |
+
cosY = np.cos(ry)
|
| 52 |
+
cosZ = np.cos(rz)
|
| 53 |
+
|
| 54 |
+
Rx = np.zeros((3, 3))
|
| 55 |
+
Rx[0, 0] = 1.0
|
| 56 |
+
Rx[1, 1] = cosX
|
| 57 |
+
Rx[1, 2] = -sinX
|
| 58 |
+
Rx[2, 1] = sinX
|
| 59 |
+
Rx[2, 2] = cosX
|
| 60 |
+
|
| 61 |
+
Ry = np.zeros((3, 3))
|
| 62 |
+
Ry[0, 0] = cosY
|
| 63 |
+
Ry[0, 2] = sinY
|
| 64 |
+
Ry[1, 1] = 1.0
|
| 65 |
+
Ry[2, 0] = -sinY
|
| 66 |
+
Ry[2, 2] = cosY
|
| 67 |
+
|
| 68 |
+
Rz = np.zeros((3, 3))
|
| 69 |
+
Rz[0, 0] = cosZ
|
| 70 |
+
Rz[0, 1] = -sinZ
|
| 71 |
+
Rz[1, 0] = sinZ
|
| 72 |
+
Rz[1, 1] = cosZ
|
| 73 |
+
Rz[2, 2] = 1.0
|
| 74 |
+
|
| 75 |
+
R = np.matmul(np.matmul(Rz, Ry), Rx)
|
| 76 |
+
return R
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def rotateSH(SH, R):
|
| 80 |
+
SHn = SH
|
| 81 |
+
|
| 82 |
+
# 1st order
|
| 83 |
+
SHn[1] = R[1, 1] * SH[1] - R[1, 2] * SH[2] + R[1, 0] * SH[3]
|
| 84 |
+
SHn[2] = -R[2, 1] * SH[1] + R[2, 2] * SH[2] - R[2, 0] * SH[3]
|
| 85 |
+
SHn[3] = R[0, 1] * SH[1] - R[0, 2] * SH[2] + R[0, 0] * SH[3]
|
| 86 |
+
|
| 87 |
+
# 2nd order
|
| 88 |
+
SHn[4:, 0] = rotateBand2(SH[4:, 0], R)
|
| 89 |
+
SHn[4:, 1] = rotateBand2(SH[4:, 1], R)
|
| 90 |
+
SHn[4:, 2] = rotateBand2(SH[4:, 2], R)
|
| 91 |
+
|
| 92 |
+
return SHn
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def rotateBand2(x, R):
|
| 96 |
+
s_c3 = 0.94617469575
|
| 97 |
+
s_c4 = -0.31539156525
|
| 98 |
+
s_c5 = 0.54627421529
|
| 99 |
+
|
| 100 |
+
s_c_scale = 1.0 / 0.91529123286551084
|
| 101 |
+
s_c_scale_inv = 0.91529123286551084
|
| 102 |
+
|
| 103 |
+
s_rc2 = 1.5853309190550713 * s_c_scale
|
| 104 |
+
s_c4_div_c3 = s_c4 / s_c3
|
| 105 |
+
s_c4_div_c3_x2 = (s_c4 / s_c3) * 2.0
|
| 106 |
+
|
| 107 |
+
s_scale_dst2 = s_c3 * s_c_scale_inv
|
| 108 |
+
s_scale_dst4 = s_c5 * s_c_scale_inv
|
| 109 |
+
|
| 110 |
+
sh0 = x[3] + x[4] + x[4] - x[1]
|
| 111 |
+
sh1 = x[0] + s_rc2 * x[2] + x[3] + x[4]
|
| 112 |
+
sh2 = x[0]
|
| 113 |
+
sh3 = -x[3]
|
| 114 |
+
sh4 = -x[1]
|
| 115 |
+
|
| 116 |
+
r2x = R[0][0] + R[0][1]
|
| 117 |
+
r2y = R[1][0] + R[1][1]
|
| 118 |
+
r2z = R[2][0] + R[2][1]
|
| 119 |
+
|
| 120 |
+
r3x = R[0][0] + R[0][2]
|
| 121 |
+
r3y = R[1][0] + R[1][2]
|
| 122 |
+
r3z = R[2][0] + R[2][2]
|
| 123 |
+
|
| 124 |
+
r4x = R[0][1] + R[0][2]
|
| 125 |
+
r4y = R[1][1] + R[1][2]
|
| 126 |
+
r4z = R[2][1] + R[2][2]
|
| 127 |
+
|
| 128 |
+
sh0_x = sh0 * R[0][0]
|
| 129 |
+
sh0_y = sh0 * R[1][0]
|
| 130 |
+
d0 = sh0_x * R[1][0]
|
| 131 |
+
d1 = sh0_y * R[2][0]
|
| 132 |
+
d2 = sh0 * (R[2][0] * R[2][0] + s_c4_div_c3)
|
| 133 |
+
d3 = sh0_x * R[2][0]
|
| 134 |
+
d4 = sh0_x * R[0][0] - sh0_y * R[1][0]
|
| 135 |
+
|
| 136 |
+
sh1_x = sh1 * R[0][2]
|
| 137 |
+
sh1_y = sh1 * R[1][2]
|
| 138 |
+
d0 += sh1_x * R[1][2]
|
| 139 |
+
d1 += sh1_y * R[2][2]
|
| 140 |
+
d2 += sh1 * (R[2][2] * R[2][2] + s_c4_div_c3)
|
| 141 |
+
d3 += sh1_x * R[2][2]
|
| 142 |
+
d4 += sh1_x * R[0][2] - sh1_y * R[1][2]
|
| 143 |
+
|
| 144 |
+
sh2_x = sh2 * r2x
|
| 145 |
+
sh2_y = sh2 * r2y
|
| 146 |
+
d0 += sh2_x * r2y
|
| 147 |
+
d1 += sh2_y * r2z
|
| 148 |
+
d2 += sh2 * (r2z * r2z + s_c4_div_c3_x2)
|
| 149 |
+
d3 += sh2_x * r2z
|
| 150 |
+
d4 += sh2_x * r2x - sh2_y * r2y
|
| 151 |
+
|
| 152 |
+
sh3_x = sh3 * r3x
|
| 153 |
+
sh3_y = sh3 * r3y
|
| 154 |
+
d0 += sh3_x * r3y
|
| 155 |
+
d1 += sh3_y * r3z
|
| 156 |
+
d2 += sh3 * (r3z * r3z + s_c4_div_c3_x2)
|
| 157 |
+
d3 += sh3_x * r3z
|
| 158 |
+
d4 += sh3_x * r3x - sh3_y * r3y
|
| 159 |
+
|
| 160 |
+
sh4_x = sh4 * r4x
|
| 161 |
+
sh4_y = sh4 * r4y
|
| 162 |
+
d0 += sh4_x * r4y
|
| 163 |
+
d1 += sh4_y * r4z
|
| 164 |
+
d2 += sh4 * (r4z * r4z + s_c4_div_c3_x2)
|
| 165 |
+
d3 += sh4_x * r4z
|
| 166 |
+
d4 += sh4_x * r4x - sh4_y * r4y
|
| 167 |
+
|
| 168 |
+
dst = x
|
| 169 |
+
dst[0] = d0
|
| 170 |
+
dst[1] = -d1
|
| 171 |
+
dst[2] = d2 * s_scale_dst2
|
| 172 |
+
dst[3] = -d3
|
| 173 |
+
dst[4] = d4 * s_scale_dst4
|
| 174 |
+
|
| 175 |
+
return dst
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def load_calib(param, render_size=512):
|
| 179 |
+
# pixel unit / world unit
|
| 180 |
+
ortho_ratio = param['ortho_ratio']
|
| 181 |
+
# world unit / model unit
|
| 182 |
+
scale = param['scale']
|
| 183 |
+
# camera center world coordinate
|
| 184 |
+
center = param['center']
|
| 185 |
+
# model rotation
|
| 186 |
+
R = param['R']
|
| 187 |
+
|
| 188 |
+
translate = -np.matmul(R, center).reshape(3, 1)
|
| 189 |
+
extrinsic = np.concatenate([R, translate], axis=1)
|
| 190 |
+
extrinsic = np.concatenate(
|
| 191 |
+
[extrinsic, np.array([0, 0, 0, 1]).reshape(1, 4)], 0)
|
| 192 |
+
# Match camera space to image pixel space
|
| 193 |
+
scale_intrinsic = np.identity(4)
|
| 194 |
+
scale_intrinsic[0, 0] = scale / ortho_ratio
|
| 195 |
+
scale_intrinsic[1, 1] = -scale / ortho_ratio
|
| 196 |
+
scale_intrinsic[2, 2] = scale / ortho_ratio
|
| 197 |
+
# Match image pixel space to image uv space
|
| 198 |
+
uv_intrinsic = np.identity(4)
|
| 199 |
+
uv_intrinsic[0, 0] = 1.0 / float(render_size // 2)
|
| 200 |
+
uv_intrinsic[1, 1] = 1.0 / float(render_size // 2)
|
| 201 |
+
uv_intrinsic[2, 2] = 1.0 / float(render_size // 2)
|
| 202 |
+
|
| 203 |
+
intrinsic = np.matmul(uv_intrinsic, scale_intrinsic)
|
| 204 |
+
calib = np.concatenate([extrinsic, intrinsic], axis=0)
|
| 205 |
+
return calib
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def render_prt_ortho(out_path,
|
| 209 |
+
folder_name,
|
| 210 |
+
subject_name,
|
| 211 |
+
shs,
|
| 212 |
+
rndr,
|
| 213 |
+
rndr_uv,
|
| 214 |
+
im_size,
|
| 215 |
+
angl_step=4,
|
| 216 |
+
n_light=1,
|
| 217 |
+
pitch=[0]):
|
| 218 |
+
cam = Camera(width=im_size, height=im_size)
|
| 219 |
+
cam.ortho_ratio = 0.4 * (512 / im_size)
|
| 220 |
+
cam.near = -100
|
| 221 |
+
cam.far = 100
|
| 222 |
+
cam.sanity_check()
|
| 223 |
+
|
| 224 |
+
# set path for obj, prt
|
| 225 |
+
mesh_file = os.path.join(folder_name, subject_name + '_100k.obj')
|
| 226 |
+
if not os.path.exists(mesh_file):
|
| 227 |
+
print('ERROR: obj file does not exist!!', mesh_file)
|
| 228 |
+
return
|
| 229 |
+
prt_file = os.path.join(folder_name, 'bounce', 'bounce0.txt')
|
| 230 |
+
if not os.path.exists(prt_file):
|
| 231 |
+
print('ERROR: prt file does not exist!!!', prt_file)
|
| 232 |
+
return
|
| 233 |
+
face_prt_file = os.path.join(folder_name, 'bounce', 'face.npy')
|
| 234 |
+
if not os.path.exists(face_prt_file):
|
| 235 |
+
print('ERROR: face prt file does not exist!!!', prt_file)
|
| 236 |
+
return
|
| 237 |
+
text_file = os.path.join(folder_name, 'tex', subject_name + '_dif_2k.jpg')
|
| 238 |
+
if not os.path.exists(text_file):
|
| 239 |
+
print('ERROR: dif file does not exist!!', text_file)
|
| 240 |
+
return
|
| 241 |
+
|
| 242 |
+
texture_image = cv2.imread(text_file)
|
| 243 |
+
texture_image = cv2.cvtColor(texture_image, cv2.COLOR_BGR2RGB)
|
| 244 |
+
|
| 245 |
+
vertices, faces, normals, faces_normals, textures, face_textures = load_scan(
|
| 246 |
+
mesh_file, with_normal=True, with_texture=True)
|
| 247 |
+
vmin = vertices.min(0)
|
| 248 |
+
vmax = vertices.max(0)
|
| 249 |
+
up_axis = 1 if (vmax - vmin).argmax() == 1 else 2
|
| 250 |
+
|
| 251 |
+
vmed = np.median(vertices, 0)
|
| 252 |
+
vmed[up_axis] = 0.5 * (vmax[up_axis] + vmin[up_axis])
|
| 253 |
+
y_scale = 180 / (vmax[up_axis] - vmin[up_axis])
|
| 254 |
+
|
| 255 |
+
rndr.set_norm_mat(y_scale, vmed)
|
| 256 |
+
rndr_uv.set_norm_mat(y_scale, vmed)
|
| 257 |
+
|
| 258 |
+
tan, bitan = compute_tangent(vertices, faces, normals, textures,
|
| 259 |
+
face_textures)
|
| 260 |
+
prt = np.loadtxt(prt_file)
|
| 261 |
+
face_prt = np.load(face_prt_file)
|
| 262 |
+
rndr.set_mesh(vertices, faces, normals, faces_normals, textures,
|
| 263 |
+
face_textures, prt, face_prt, tan, bitan)
|
| 264 |
+
rndr.set_albedo(texture_image)
|
| 265 |
+
|
| 266 |
+
rndr_uv.set_mesh(vertices, faces, normals, faces_normals, textures,
|
| 267 |
+
face_textures, prt, face_prt, tan, bitan)
|
| 268 |
+
rndr_uv.set_albedo(texture_image)
|
| 269 |
+
|
| 270 |
+
os.makedirs(os.path.join(out_path, 'GEO', 'OBJ', subject_name),
|
| 271 |
+
exist_ok=True)
|
| 272 |
+
os.makedirs(os.path.join(out_path, 'PARAM', subject_name), exist_ok=True)
|
| 273 |
+
os.makedirs(os.path.join(out_path, 'RENDER', subject_name), exist_ok=True)
|
| 274 |
+
os.makedirs(os.path.join(out_path, 'MASK', subject_name), exist_ok=True)
|
| 275 |
+
os.makedirs(os.path.join(out_path, 'UV_RENDER', subject_name),
|
| 276 |
+
exist_ok=True)
|
| 277 |
+
os.makedirs(os.path.join(out_path, 'UV_MASK', subject_name), exist_ok=True)
|
| 278 |
+
os.makedirs(os.path.join(out_path, 'UV_POS', subject_name), exist_ok=True)
|
| 279 |
+
os.makedirs(os.path.join(out_path, 'UV_NORMAL', subject_name),
|
| 280 |
+
exist_ok=True)
|
| 281 |
+
|
| 282 |
+
if not os.path.exists(os.path.join(out_path, 'val.txt')):
|
| 283 |
+
f = open(os.path.join(out_path, 'val.txt'), 'w')
|
| 284 |
+
f.close()
|
| 285 |
+
|
| 286 |
+
# copy obj file
|
| 287 |
+
cmd = 'cp %s %s' % (mesh_file,
|
| 288 |
+
os.path.join(out_path, 'GEO', 'OBJ', subject_name))
|
| 289 |
+
print(cmd)
|
| 290 |
+
os.system(cmd)
|
| 291 |
+
|
| 292 |
+
for p in pitch:
|
| 293 |
+
for y in tqdm(range(0, 360, angl_step)):
|
| 294 |
+
R = np.matmul(make_rotate(math.radians(p), 0, 0),
|
| 295 |
+
make_rotate(0, math.radians(y), 0))
|
| 296 |
+
if up_axis == 2:
|
| 297 |
+
R = np.matmul(R, make_rotate(math.radians(90), 0, 0))
|
| 298 |
+
|
| 299 |
+
rndr.rot_matrix = R
|
| 300 |
+
rndr_uv.rot_matrix = R
|
| 301 |
+
rndr.set_camera(cam)
|
| 302 |
+
rndr_uv.set_camera(cam)
|
| 303 |
+
|
| 304 |
+
for j in range(n_light):
|
| 305 |
+
sh_id = random.randint(0, shs.shape[0] - 1)
|
| 306 |
+
sh = shs[sh_id]
|
| 307 |
+
sh_angle = 0.2 * np.pi * (random.random() - 0.5)
|
| 308 |
+
sh = rotateSH(sh, make_rotate(0, sh_angle, 0).T)
|
| 309 |
+
|
| 310 |
+
dic = {
|
| 311 |
+
'sh': sh,
|
| 312 |
+
'ortho_ratio': cam.ortho_ratio,
|
| 313 |
+
'scale': y_scale,
|
| 314 |
+
'center': vmed,
|
| 315 |
+
'R': R
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
rndr.set_sh(sh)
|
| 319 |
+
rndr.analytic = False
|
| 320 |
+
rndr.use_inverse_depth = False
|
| 321 |
+
rndr.display()
|
| 322 |
+
|
| 323 |
+
out_all_f = rndr.get_color(0)
|
| 324 |
+
out_mask = out_all_f[:, :, 3]
|
| 325 |
+
out_all_f = cv2.cvtColor(out_all_f, cv2.COLOR_RGBA2BGR)
|
| 326 |
+
|
| 327 |
+
np.save(
|
| 328 |
+
os.path.join(out_path, 'PARAM', subject_name,
|
| 329 |
+
'%d_%d_%02d.npy' % (y, p, j)), dic)
|
| 330 |
+
cv2.imwrite(
|
| 331 |
+
os.path.join(out_path, 'RENDER', subject_name,
|
| 332 |
+
'%d_%d_%02d.jpg' % (y, p, j)),
|
| 333 |
+
255.0 * out_all_f)
|
| 334 |
+
cv2.imwrite(
|
| 335 |
+
os.path.join(out_path, 'MASK', subject_name,
|
| 336 |
+
'%d_%d_%02d.png' % (y, p, j)),
|
| 337 |
+
255.0 * out_mask)
|
| 338 |
+
|
| 339 |
+
rndr_uv.set_sh(sh)
|
| 340 |
+
rndr_uv.analytic = False
|
| 341 |
+
rndr_uv.use_inverse_depth = False
|
| 342 |
+
rndr_uv.display()
|
| 343 |
+
|
| 344 |
+
uv_color = rndr_uv.get_color(0)
|
| 345 |
+
uv_color = cv2.cvtColor(uv_color, cv2.COLOR_RGBA2BGR)
|
| 346 |
+
cv2.imwrite(
|
| 347 |
+
os.path.join(out_path, 'UV_RENDER', subject_name,
|
| 348 |
+
'%d_%d_%02d.jpg' % (y, p, j)),
|
| 349 |
+
255.0 * uv_color)
|
| 350 |
+
|
| 351 |
+
if y == 0 and j == 0 and p == pitch[0]:
|
| 352 |
+
uv_pos = rndr_uv.get_color(1)
|
| 353 |
+
uv_mask = uv_pos[:, :, 3]
|
| 354 |
+
cv2.imwrite(
|
| 355 |
+
os.path.join(out_path, 'UV_MASK', subject_name,
|
| 356 |
+
'00.png'), 255.0 * uv_mask)
|
| 357 |
+
|
| 358 |
+
data = {
|
| 359 |
+
'default': uv_pos[:, :, :3]
|
| 360 |
+
} # default is a reserved name
|
| 361 |
+
pyexr.write(
|
| 362 |
+
os.path.join(out_path, 'UV_POS', subject_name,
|
| 363 |
+
'00.exr'), data)
|
| 364 |
+
|
| 365 |
+
uv_nml = rndr_uv.get_color(2)
|
| 366 |
+
uv_nml = cv2.cvtColor(uv_nml, cv2.COLOR_RGBA2BGR)
|
| 367 |
+
cv2.imwrite(
|
| 368 |
+
os.path.join(out_path, 'UV_NORMAL', subject_name,
|
| 369 |
+
'00.png'), 255.0 * uv_nml)
|
lib/renderer/prt_util.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
|
| 5 |
+
# holder of all proprietary rights on this computer program.
|
| 6 |
+
# You can only use this computer program if you have closed
|
| 7 |
+
# a license agreement with MPG or you get the right to use the computer
|
| 8 |
+
# program from someone who is authorized to grant you that right.
|
| 9 |
+
# Any use of the computer program without a valid license is prohibited and
|
| 10 |
+
# liable to prosecution.
|
| 11 |
+
#
|
| 12 |
+
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
|
| 13 |
+
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
|
| 14 |
+
# for Intelligent Systems. All rights reserved.
|
| 15 |
+
#
|
| 16 |
+
# Contact: [email protected]
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
import trimesh
|
| 20 |
+
import numpy as np
|
| 21 |
+
import math
|
| 22 |
+
from scipy.special import sph_harm
|
| 23 |
+
import argparse
|
| 24 |
+
from tqdm import tqdm
|
| 25 |
+
from trimesh.util import bounds_tree
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def factratio(N, D):
|
| 29 |
+
if N >= D:
|
| 30 |
+
prod = 1.0
|
| 31 |
+
for i in range(D + 1, N + 1):
|
| 32 |
+
prod *= i
|
| 33 |
+
return prod
|
| 34 |
+
else:
|
| 35 |
+
prod = 1.0
|
| 36 |
+
for i in range(N + 1, D + 1):
|
| 37 |
+
prod *= i
|
| 38 |
+
return 1.0 / prod
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def KVal(M, L):
|
| 42 |
+
return math.sqrt(((2 * L + 1) / (4 * math.pi)) * (factratio(L - M, L + M)))
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def AssociatedLegendre(M, L, x):
|
| 46 |
+
if M < 0 or M > L or np.max(np.abs(x)) > 1.0:
|
| 47 |
+
return np.zeros_like(x)
|
| 48 |
+
|
| 49 |
+
pmm = np.ones_like(x)
|
| 50 |
+
if M > 0:
|
| 51 |
+
somx2 = np.sqrt((1.0 + x) * (1.0 - x))
|
| 52 |
+
fact = 1.0
|
| 53 |
+
for i in range(1, M + 1):
|
| 54 |
+
pmm = -pmm * fact * somx2
|
| 55 |
+
fact = fact + 2
|
| 56 |
+
|
| 57 |
+
if L == M:
|
| 58 |
+
return pmm
|
| 59 |
+
else:
|
| 60 |
+
pmmp1 = x * (2 * M + 1) * pmm
|
| 61 |
+
if L == M + 1:
|
| 62 |
+
return pmmp1
|
| 63 |
+
else:
|
| 64 |
+
pll = np.zeros_like(x)
|
| 65 |
+
for i in range(M + 2, L + 1):
|
| 66 |
+
pll = (x * (2 * i - 1) * pmmp1 - (i + M - 1) * pmm) / (i - M)
|
| 67 |
+
pmm = pmmp1
|
| 68 |
+
pmmp1 = pll
|
| 69 |
+
return pll
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def SphericalHarmonic(M, L, theta, phi):
|
| 73 |
+
if M > 0:
|
| 74 |
+
return math.sqrt(2.0) * KVal(M, L) * np.cos(
|
| 75 |
+
M * phi) * AssociatedLegendre(M, L, np.cos(theta))
|
| 76 |
+
elif M < 0:
|
| 77 |
+
return math.sqrt(2.0) * KVal(-M, L) * np.sin(
|
| 78 |
+
-M * phi) * AssociatedLegendre(-M, L, np.cos(theta))
|
| 79 |
+
else:
|
| 80 |
+
return KVal(0, L) * AssociatedLegendre(0, L, np.cos(theta))
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def save_obj(mesh_path, verts):
|
| 84 |
+
file = open(mesh_path, 'w')
|
| 85 |
+
for v in verts:
|
| 86 |
+
file.write('v %.4f %.4f %.4f\n' % (v[0], v[1], v[2]))
|
| 87 |
+
file.close()
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def sampleSphericalDirections(n):
|
| 91 |
+
xv = np.random.rand(n, n)
|
| 92 |
+
yv = np.random.rand(n, n)
|
| 93 |
+
theta = np.arccos(1 - 2 * xv)
|
| 94 |
+
phi = 2.0 * math.pi * yv
|
| 95 |
+
|
| 96 |
+
phi = phi.reshape(-1)
|
| 97 |
+
theta = theta.reshape(-1)
|
| 98 |
+
|
| 99 |
+
vx = -np.sin(theta) * np.cos(phi)
|
| 100 |
+
vy = -np.sin(theta) * np.sin(phi)
|
| 101 |
+
vz = np.cos(theta)
|
| 102 |
+
return np.stack([vx, vy, vz], 1), phi, theta
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def getSHCoeffs(order, phi, theta):
|
| 106 |
+
shs = []
|
| 107 |
+
for n in range(0, order + 1):
|
| 108 |
+
for m in range(-n, n + 1):
|
| 109 |
+
s = SphericalHarmonic(m, n, theta, phi)
|
| 110 |
+
shs.append(s)
|
| 111 |
+
|
| 112 |
+
return np.stack(shs, 1)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def computePRT(mesh_path, scale, n, order):
|
| 116 |
+
|
| 117 |
+
prt_dir = os.path.join(os.path.dirname(mesh_path), "prt")
|
| 118 |
+
bounce_path = os.path.join(prt_dir, "bounce.npy")
|
| 119 |
+
face_path = os.path.join(prt_dir, "face.npy")
|
| 120 |
+
|
| 121 |
+
os.makedirs(prt_dir, exist_ok=True)
|
| 122 |
+
|
| 123 |
+
PRT = None
|
| 124 |
+
F = None
|
| 125 |
+
|
| 126 |
+
if os.path.exists(bounce_path) and os.path.exists(face_path):
|
| 127 |
+
|
| 128 |
+
PRT = np.load(bounce_path)
|
| 129 |
+
F = np.load(face_path)
|
| 130 |
+
|
| 131 |
+
else:
|
| 132 |
+
|
| 133 |
+
mesh = trimesh.load(mesh_path,
|
| 134 |
+
skip_materials=True,
|
| 135 |
+
process=False,
|
| 136 |
+
maintain_order=True)
|
| 137 |
+
mesh.vertices *= scale
|
| 138 |
+
|
| 139 |
+
vectors_orig, phi, theta = sampleSphericalDirections(n)
|
| 140 |
+
SH_orig = getSHCoeffs(order, phi, theta)
|
| 141 |
+
|
| 142 |
+
w = 4.0 * math.pi / (n * n)
|
| 143 |
+
|
| 144 |
+
origins = mesh.vertices
|
| 145 |
+
normals = mesh.vertex_normals
|
| 146 |
+
n_v = origins.shape[0]
|
| 147 |
+
|
| 148 |
+
origins = np.repeat(origins[:, None], n, axis=1).reshape(-1, 3)
|
| 149 |
+
normals = np.repeat(normals[:, None], n, axis=1).reshape(-1, 3)
|
| 150 |
+
PRT_all = None
|
| 151 |
+
for i in range(n):
|
| 152 |
+
SH = np.repeat(SH_orig[None, (i * n):((i + 1) * n)], n_v,
|
| 153 |
+
axis=0).reshape(-1, SH_orig.shape[1])
|
| 154 |
+
vectors = np.repeat(vectors_orig[None, (i * n):((i + 1) * n)],
|
| 155 |
+
n_v,
|
| 156 |
+
axis=0).reshape(-1, 3)
|
| 157 |
+
|
| 158 |
+
dots = (vectors * normals).sum(1)
|
| 159 |
+
front = (dots > 0.0)
|
| 160 |
+
|
| 161 |
+
delta = 1e-3 * min(mesh.bounding_box.extents)
|
| 162 |
+
|
| 163 |
+
hits = mesh.ray.intersects_any(origins + delta * normals, vectors)
|
| 164 |
+
nohits = np.logical_and(front, np.logical_not(hits))
|
| 165 |
+
|
| 166 |
+
PRT = (nohits.astype(np.float) * dots)[:, None] * SH
|
| 167 |
+
|
| 168 |
+
if PRT_all is not None:
|
| 169 |
+
PRT_all += (PRT.reshape(-1, n, SH.shape[1]).sum(1))
|
| 170 |
+
else:
|
| 171 |
+
PRT_all = (PRT.reshape(-1, n, SH.shape[1]).sum(1))
|
| 172 |
+
|
| 173 |
+
PRT = w * PRT_all
|
| 174 |
+
F = mesh.faces
|
| 175 |
+
|
| 176 |
+
np.save(bounce_path, PRT)
|
| 177 |
+
np.save(face_path, F)
|
| 178 |
+
|
| 179 |
+
# NOTE: trimesh sometimes break the original vertex order, but topology will not change.
|
| 180 |
+
# when loading PRT in other program, use the triangle list from trimesh.
|
| 181 |
+
|
| 182 |
+
return PRT, F
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def testPRT(obj_path, n=40):
|
| 186 |
+
|
| 187 |
+
os.makedirs(os.path.join(os.path.dirname(obj_path),
|
| 188 |
+
f'../bounce/{os.path.basename(obj_path)[:-4]}'),
|
| 189 |
+
exist_ok=True)
|
| 190 |
+
|
| 191 |
+
PRT, F = computePRT(obj_path, n, 2)
|
| 192 |
+
np.savetxt(
|
| 193 |
+
os.path.join(os.path.dirname(obj_path),
|
| 194 |
+
f'../bounce/{os.path.basename(obj_path)[:-4]}',
|
| 195 |
+
'bounce.npy'), PRT)
|
| 196 |
+
np.save(
|
| 197 |
+
os.path.join(os.path.dirname(obj_path),
|
| 198 |
+
f'../bounce/{os.path.basename(obj_path)[:-4]}',
|
| 199 |
+
'face.npy'), F)
|