-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmerl.py
177 lines (139 loc) · 6.16 KB
/
merl.py
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
"""
:mod: `merl` -- MERL BRDF Python support
========================================
.. module:: merl
:synopsis: This module implements the support for MERL BRDF material
https://www.merl.com/brdf/
.. moduleauthor:: Alban Fichet <[email protected]>
"""
import struct
import math
class Merl:
sampling_theta_h = 90
sampling_theta_d = 90
sampling_phi_d = 180
scale = [1 / 1500, 1.15 / 1500, 1.66 / 1500]
def __init__(self, merl_file):
"""
Initialize and load a MERL BRDF file
:param merl_file: The path of the file to load
"""
with open(merl_file, 'rb') as f:
data = f.read()
length = self.sampling_theta_h * self.sampling_theta_d * self.sampling_phi_d
n = struct.unpack_from('3i', data)
if n[0] * n[1] * n[2] != length:
raise IOError("Dimmensions doe not match")
self.brdf = struct.unpack_from(str(3 * length) + 'd', data,
offset=struct.calcsize('3i'))
def eval_raw(self, theta_h, theta_d, phi_d):
"""
Lookup the BRDF value for given half diff coordinates
:param theta_h: half vector elevation angle in radians
:param theta_d: diff vector elevation angle in radians
:param phi_d: diff vector azimuthal angle in radians
:return: A list of 3 elements giving the BRDF value for R, G, B in
linear RGB
"""
return self.__eval_idx(self.__theta_h_idx(theta_h),
self.__theta_d_idx(theta_d),
self.__phi_d_idx(phi_d))
def eval_interp(self, theta_h, theta_d, phi_d):
"""
Lookup the BRDF value for given half diff coordinates and perform an
interpolation over theta_h, theta_d and phi_d
:param theta_h: half vector elevation angle in radians
:param theta_d: diff vector elevation angle in radians
:param phi_d: diff vector azimuthal angle in radians
:return: A list of 3 elements giving the BRDF value for R, G, B in
linear RGB
"""
idx_th_p = self.__theta_h_idx(theta_h)
idx_td_p = self.__theta_d_idx(theta_d)
idx_pd_p = self.__phi_d_idx(phi_d)
# Calculate the indexes for interpolation
idx_th_p = idx_th_p if idx_th_p < self.sampling_theta_h - 1 else self.sampling_theta_h - 2
idx_td_p = idx_td_p if idx_td_p < self.sampling_theta_d - 1 else self.sampling_theta_d - 2
idx_th = [idx_th_p, idx_th_p + 1]
idx_td = [idx_td_p, idx_td_p + 1]
idx_pd = [idx_pd_p, idx_pd_p + 1]
# Calculate the weights
weight_th = [abs(self.__theta_h_from_idx(i) - theta_h) for i in idx_th]
weight_td = [abs(self.__theta_d_from_idx(i) - theta_d) for i in idx_td]
weight_pd = [abs(self.__phi_d_from_idx(i) - phi_d) for i in idx_pd]
# Normalize the weights
weight_th = [1 - w / sum(weight_th) for w in weight_th]
weight_td = [1 - w / sum(weight_td) for w in weight_td]
weight_pd = [1 - w / sum(weight_pd) for w in weight_pd]
idx_pd[1] = idx_pd[1] if idx_pd[1] < self.sampling_phi_d else 0
ret_val = [0] * 3
for ith, wth in zip(idx_th, weight_th):
for itd, wtd in zip(idx_td, weight_td):
for ipd, wpd in zip(idx_pd, weight_pd):
ret_val = [r + x * wth * wtd * wpd
for r, x, in zip(ret_val, self.__eval_idx(ith, itd, ipd))]
return ret_val
def __eval_idx(self, ith, itd, ipd):
"""
Lookup the BRDF value for a given set of indexes
:param ith: theta_h index
:param itd: theta_d index
:param ipd: phi_d index
:return: A list of 3 elements giving the BRDF value for R, G, B in
linear RGB
"""
ind = ipd + self.sampling_phi_d * (itd + ith * self.sampling_theta_d)
stride = self.sampling_theta_h * self.sampling_theta_d * self.sampling_phi_d
return [self.brdf[ind + color * stride] * s
for s, color in zip(self.scale, range(0, 3))]
def __theta_h_from_idx(self, theta_h_idx):
"""
Get the theta_h value corresponding to a given index
:param theta_h_idx: Index for theta_h
:return: A theta_h value in radians
"""
ret_val = theta_h_idx / self.sampling_theta_h
return ret_val * ret_val * math.pi / 2
def __theta_h_idx(self, theta_h):
"""
Get the index corresponding to a given theta_h value
:param theta_h: Value for theta_h in radians
:return: The corresponding index for the given theta_h
"""
if theta_h < 0:
return 0
th = self.sampling_theta_h * math.sqrt(theta_h / (math.pi / 2))
return max(0, min(self.sampling_theta_h - 1,
math.floor(th)))
def __theta_d_from_idx(self, theta_d_idx):
"""
Get the theta_d value corresponding to a given index
:param theta_d_idx: Index for theta_d
:return: A theta_d value in radians
"""
return theta_d_idx / self.sampling_theta_d * math.pi / 2
def __theta_d_idx(self, theta_d):
"""
Get the index corresponding to a given theta_d value
:param theta_d: Value for theta_d in radians
:return: The corresponding index for the given theta_d
"""
return max(0, min(self.sampling_theta_d - 1,
math.floor(self.sampling_theta_d * theta_d / (math.pi / 2))))
def __phi_d_from_idx(self, phi_d_idx):
"""
Get the phi_d value corresponding to a given index
:param phi_d_idx: Index for phi_d
:return: A phi_d value in radians
"""
return phi_d_idx / self.sampling_phi_d * math.pi
def __phi_d_idx(self, phi_d):
"""
Get the index corresponding to a given phi_d value
:param theta_h: Value for phi_d in radians
:return: The corresponding index for the given phi_d
"""
while phi_d < 0:
phi_d += math.pi
return max(0, min(self.sampling_phi_d - 1,
math.floor(self.sampling_phi_d * phi_d / math.pi)))