PSHuman / lib /renderer /prt_util.py
fffiloni's picture
Migrated from GitHub
2252f3d verified
# -*- coding: utf-8 -*-
# Max-Planck-Gesellschaft zur Förderung der Wissenschaften e.V. (MPG) is
# holder of all proprietary rights on this computer program.
# You can only use this computer program if you have closed
# a license agreement with MPG or you get the right to use the computer
# program from someone who is authorized to grant you that right.
# Any use of the computer program without a valid license is prohibited and
# liable to prosecution.
#
# Copyright©2019 Max-Planck-Gesellschaft zur Förderung
# der Wissenschaften e.V. (MPG). acting on behalf of its Max Planck Institute
# for Intelligent Systems. All rights reserved.
#
# Contact: ps-license@tuebingen.mpg.de
import os
import trimesh
import numpy as np
import math
from scipy.special import sph_harm
import argparse
from tqdm import tqdm
from trimesh.util import bounds_tree
def factratio(N, D):
if N >= D:
prod = 1.0
for i in range(D + 1, N + 1):
prod *= i
return prod
else:
prod = 1.0
for i in range(N + 1, D + 1):
prod *= i
return 1.0 / prod
def KVal(M, L):
return math.sqrt(((2 * L + 1) / (4 * math.pi)) * (factratio(L - M, L + M)))
def AssociatedLegendre(M, L, x):
if M < 0 or M > L or np.max(np.abs(x)) > 1.0:
return np.zeros_like(x)
pmm = np.ones_like(x)
if M > 0:
somx2 = np.sqrt((1.0 + x) * (1.0 - x))
fact = 1.0
for i in range(1, M + 1):
pmm = -pmm * fact * somx2
fact = fact + 2
if L == M:
return pmm
else:
pmmp1 = x * (2 * M + 1) * pmm
if L == M + 1:
return pmmp1
else:
pll = np.zeros_like(x)
for i in range(M + 2, L + 1):
pll = (x * (2 * i - 1) * pmmp1 - (i + M - 1) * pmm) / (i - M)
pmm = pmmp1
pmmp1 = pll
return pll
def SphericalHarmonic(M, L, theta, phi):
if M > 0:
return math.sqrt(2.0) * KVal(M, L) * np.cos(
M * phi) * AssociatedLegendre(M, L, np.cos(theta))
elif M < 0:
return math.sqrt(2.0) * KVal(-M, L) * np.sin(
-M * phi) * AssociatedLegendre(-M, L, np.cos(theta))
else:
return KVal(0, L) * AssociatedLegendre(0, L, np.cos(theta))
def save_obj(mesh_path, verts):
file = open(mesh_path, 'w')
for v in verts:
file.write('v %.4f %.4f %.4f\n' % (v[0], v[1], v[2]))
file.close()
def sampleSphericalDirections(n):
xv = np.random.rand(n, n)
yv = np.random.rand(n, n)
theta = np.arccos(1 - 2 * xv)
phi = 2.0 * math.pi * yv
phi = phi.reshape(-1)
theta = theta.reshape(-1)
vx = -np.sin(theta) * np.cos(phi)
vy = -np.sin(theta) * np.sin(phi)
vz = np.cos(theta)
return np.stack([vx, vy, vz], 1), phi, theta
def getSHCoeffs(order, phi, theta):
shs = []
for n in range(0, order + 1):
for m in range(-n, n + 1):
s = SphericalHarmonic(m, n, theta, phi)
shs.append(s)
return np.stack(shs, 1)
def computePRT(mesh_path, scale, n, order):
prt_dir = os.path.join(os.path.dirname(mesh_path), "prt")
bounce_path = os.path.join(prt_dir, "bounce.npy")
face_path = os.path.join(prt_dir, "face.npy")
os.makedirs(prt_dir, exist_ok=True)
PRT = None
F = None
if os.path.exists(bounce_path) and os.path.exists(face_path):
PRT = np.load(bounce_path)
F = np.load(face_path)
else:
mesh = trimesh.load(mesh_path,
skip_materials=True,
process=False,
maintain_order=True)
mesh.vertices *= scale
vectors_orig, phi, theta = sampleSphericalDirections(n)
SH_orig = getSHCoeffs(order, phi, theta)
w = 4.0 * math.pi / (n * n)
origins = mesh.vertices
normals = mesh.vertex_normals
n_v = origins.shape[0]
origins = np.repeat(origins[:, None], n, axis=1).reshape(-1, 3)
normals = np.repeat(normals[:, None], n, axis=1).reshape(-1, 3)
PRT_all = None
for i in range(n):
SH = np.repeat(SH_orig[None, (i * n):((i + 1) * n)], n_v,
axis=0).reshape(-1, SH_orig.shape[1])
vectors = np.repeat(vectors_orig[None, (i * n):((i + 1) * n)],
n_v,
axis=0).reshape(-1, 3)
dots = (vectors * normals).sum(1)
front = (dots > 0.0)
delta = 1e-3 * min(mesh.bounding_box.extents)
hits = mesh.ray.intersects_any(origins + delta * normals, vectors)
nohits = np.logical_and(front, np.logical_not(hits))
PRT = (nohits.astype(np.float32) * dots)[:, None] * SH
if PRT_all is not None:
PRT_all += (PRT.reshape(-1, n, SH.shape[1]).sum(1))
else:
PRT_all = (PRT.reshape(-1, n, SH.shape[1]).sum(1))
PRT = w * PRT_all
F = mesh.faces
np.save(bounce_path, PRT)
np.save(face_path, F)
# NOTE: trimesh sometimes break the original vertex order, but topology will not change.
# when loading PRT in other program, use the triangle list from trimesh.
return PRT, F
def testPRT(obj_path, n=40):
os.makedirs(os.path.join(os.path.dirname(obj_path),
f'../bounce/{os.path.basename(obj_path)[:-4]}'),
exist_ok=True)
PRT, F = computePRT(obj_path, n, 2)
np.savetxt(
os.path.join(os.path.dirname(obj_path),
f'../bounce/{os.path.basename(obj_path)[:-4]}',
'bounce.npy'), PRT)
np.save(
os.path.join(os.path.dirname(obj_path),
f'../bounce/{os.path.basename(obj_path)[:-4]}',
'face.npy'), F)