RenderManAPI  24.0
RixIESInline.h
Go to the documentation of this file.
1 /*
2 # ------------------------------------------------------------------------------
3 #
4 # Copyright (c) 2020 Pixar. All rights reserved.
5 #
6 # The information in this file (the "Software") is provided for the exclusive
7 # use of the software licensees of Pixar ("Licensees"). Licensees have the
8 # right to incorporate the Software into other products for use by other
9 # authorized software licensees of Pixar, without fee. Except as expressly
10 # permitted herein, the Software may not be disclosed to third parties, copied
11 # or duplicated in any form, in whole or in part, without the prior written
12 # permission of Pixar.
13 #
14 # The copyright notices in the Software and this entire statement, including the
15 # above license grant, this restriction and the following disclaimer, must be
16 # included in all copies of the Software, in whole or in part, and all permitted
17 # derivative works of the Software, unless such copies or derivative works are
18 # solely in the form of machine-executable object code generated by a source
19 # language processor.
20 #
21 # PIXAR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
22 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL PIXAR BE
23 # LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
24 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
25 # OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
26 # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. IN NO CASE WILL
27 # PIXAR'S TOTAL LIABILITY FOR ALL DAMAGES ARISING OUT OF OR IN CONNECTION WITH
28 # THE USE OR PERFORMANCE OF THIS SOFTWARE EXCEED $50.
29 #
30 # Pixar
31 # 1200 Park Ave
32 # Emeryville CA 94608
33 #
34 # ------------------------------------------------------------------------------
35 */
36 
37 #ifndef RixIESInline_h
38 #define RixIESInline_h
39 
40 #include <math.h> // for floorf, powf, asinf, atan2f, etc
41 #include <algorithm> // for max, min
42 #include <cfloat> // for FLT_MIN
43 #include <string> // for string
44 #include "RixInterfaces.h" // for RixTexture, etc
45 #include "RixShadingUtils.h" // for F_PI
46 #include "prmanapi.h" // for PRMAN_INLINE
47 #include "RiTypesHelper.h" // for RtColorRGB, RtFloat3, etc
48 
49 PRMAN_INLINE void
51 {
52  // if we haven't read a map...
53  if (!m_iesProfile.profile)
54  {
55  emission->r = emission->g = emission->b = 1.f;
56  return;
57  }
58 
59  float vScale = 0.5f;
60  RtFloat3 localDir = P;
61 
62  // assume projection center is at shader space 0
63  // Transform(P, &localDir, m_profileSpace, 1);
64 
65  if (!m_hasNegativeDeterminant)
66  localDir *= -1.0f;
67 
68  float u;
69  float v;
70  calcLatLongParam(1, (float*)&localDir, m_iesProfile.isExr, &u, &v);
71 
72  if ((u != u) || (v != v))
73  {
74  // FIXME ideally we would want to report an error as opposed to silently
75  // return 1
76  emission->r = emission->g = emission->b = 1.f;
77  return;
78  }
79 
80  if (u < 0.0f || u > 1.0f || v < 0.0f || v > 1.0f)
81  {
82  // FIXME ideally we would want to report an error as opposed to silently
83  // return 1
84  emission->r = emission->g = emission->b = 1.f;
85  return;
86  }
87 
88  // rescale
89  float coneSize = 1.0f;
90  float profileScale = 1.0f;
91  if (m_coneAngle > 0.0f)
92  {
93  coneSize = m_coneAngle / (float)F_PI;
94  profileScale = coneSize;
95  }
96  if (m_iesProfile.profileScale > 0.0f)
97  profileScale = m_iesProfile.profileScale;
98 
99  // on axis = 0, opposite = 1
100 
101  // map the cone to 0:1 on the map
102  v = v * coneSize; // allow overflow to be dealt with by profileScale
103 
104  // profileScale 180 (1.0) = full map < 180 restricts the region
105 
106  v = v / profileScale;
107 
108  // orient against map
109  v = 1.0f - v;
110 
111  v = std::max(std::min(v * vScale, 1.0f), 0.0f);
112 
113  // now exactly on axis == 1, opposite = 0
114 
115  float uf = u * static_cast<float>(m_iesProfile.nu);
116  if (uf >= m_iesProfile.nu)
117  uf -= static_cast<float>(m_iesProfile.nu);
118 
119  int u0 = static_cast<int>(floorf(uf));
120  int u1 = u0 + 1;
121  if (u1 >= m_iesProfile.nu)
122  u1 = 0; // wrap
123 
124  float fu = uf - static_cast<float>(u0);
125 
126  if (u0 < 0 || u0 >= m_iesProfile.nu || u1 < 0 || u1 >= m_iesProfile.nu)
127  {
128  // FIXME ideally we would want to report an error as opposed to silently
129  // return 1
130  emission->r = emission->g = emission->b = 1.f;
131  return;
132  }
133 
134  float vf = v * (static_cast<float>(m_iesProfile.nv) - 1.0f);
135  if (vf >= m_iesProfile.nv)
136  vf = static_cast<float>(m_iesProfile.nv) - 1.0f;
137 
138  int v0 = static_cast<int>(floorf(vf));
139  int v1 = v0 + 1;
140  if (v1 >= m_iesProfile.nv)
141  v1 = m_iesProfile.nv - 1;
142 
143  float fv = vf - static_cast<float>(v0);
144 
145  if (v0 < 0 || v0 >= m_iesProfile.nv || v1 < 0 || v1 >= m_iesProfile.nv)
146  {
147  // FIXME ideally we would want to report an error as opposed to silently
148  // return 1
149  emission->r = emission->g = emission->b = 1.f;
150  return;
151  }
152 
153  int v00 = u0 + v0 * m_iesProfile.nu;
154  int v10 = u1 + v0 * m_iesProfile.nu;
155  int v01 = u0 + v1 * m_iesProfile.nu;
156  int v11 = u1 + v1 * m_iesProfile.nu;
157 
158  float rfu = 1.0f - fu;
159 
160  // clang-format off
161  // Clamp to 0 as some intepolated maps have -ve emission!!
162  if (m_iesProfile.isColor)
163  {
164  emission->r =
165  (1.0f - fv) * ( rfu * m_iesProfile.profile[3 * v00 + 0]
166  + fu * m_iesProfile.profile[3 * v10 + 0])
167  + fv * ( rfu * m_iesProfile.profile[3 * v01 + 0]
168  + fu * m_iesProfile.profile[3 * v11 + 0]);
169  emission->r = std::max(0.0f, emission->r);
170 
171  emission->g =
172  (1.0f - fv) * ( rfu * m_iesProfile.profile[3 * v00 + 1]
173  + fu * m_iesProfile.profile[3 * v10 + 1])
174  + fv * ( rfu * m_iesProfile.profile[3 * v01 + 1]
175  + fu * m_iesProfile.profile[3 * v11 + 1]);
176  emission->g = std::max(0.0f, emission->g);
177 
178  emission->b =
179  (1.0f - fv) * ( rfu * m_iesProfile.profile[3 * v00 + 2]
180  + fu * m_iesProfile.profile[3 * v10 + 2])
181  + fv * ( rfu * m_iesProfile.profile[3 * v01 + 2]
182  + fu * m_iesProfile.profile[3 * v11 + 2]);
183  emission->b = std::max(0.0f, emission->b);
184  }
185  else
186  {
187  emission->r =
188  (1.0f - fv) * ( rfu * m_iesProfile.profile[v00]
189  + fu * m_iesProfile.profile[v10])
190  + fv * ( rfu * m_iesProfile.profile[v01]
191  + fu * m_iesProfile.profile[v11]);
192 
193  emission->r = std::max(0.0f, emission->r);
194  emission->g = emission->b = emission->r;
195  }
196  // clang-format on
197 }
198 
199 PRMAN_INLINE bool
200 RixIES::ReadIESProfile(RixContext const& rixCtx, RtUString const iesProfile)
201 {
202  if (m_iesProfile.profile)
203  m_iesProfile.clear(); // clear existing if we already read data before
204 
205  // AcquireTexture will crash with an empty string...
206  if (!iesProfile.Empty())
207  {
208  static std::string rtxPrefix("rtxplugin:RtxIES?filename=");
209 
210  // FIXME: this doesn't work on Windows...
211  // m_iesProfile.mapName = rtxPrefix + std::string(iesProfile);
212 
213  // FIXME: The unique string construction should be made constant over
214  // the render for performance reasons.
215  std::string mapName = rtxPrefix + std::string(iesProfile.CStr());
216  m_iesProfile.mapName = RtUString(mapName.c_str());
217 
220  // open the texture
221  RixTexture::TxProperties txProps;
222  if (tex->AcquireTexture(m_iesProfile.mapName, RixTexture::AtlasNone,
223  txProps))
224  {
225  msgs->Error(
226  "RixIES emission profile: could not open %s",
227  m_iesProfile.mapName.CStr());
228  m_iesProfile.mapName = US_NULL;
229  return false;
230  }
231  else
232  {
233  msgs->Info(
234  "Building RixIES emission profile(\"%s\")\n",
235  m_iesProfile.mapName.CStr());
236 
237  m_iesProfile.nu = txProps.width;
238  m_iesProfile.nv = txProps.height;
239  m_iesProfile.isExr = false;
240  m_iesProfile.isColor = (txProps.nchannels == 3);
241 
242  // read data buffer
243  m_iesProfile.profile = new float[
244  m_iesProfile.nu * m_iesProfile.nv * txProps.nchannels];
245 
246  RixTexture::TxParams txParams;
247  txParams.nchannels = txProps.nchannels;
248  // txParams.firstchannel = 0; // start at the first channel
249 
250  tex->TextureData(txProps, txParams, 0, 0, m_iesProfile.profile);
251  tex->ReleaseTexture(txProps);
252 
253  int nchans = txProps.nchannels;
254  int nu = m_iesProfile.nu;
255  int nv = m_iesProfile.nv;
256  float total = 0.f;
257  // nv/2 is the extent of the vertical range of the profile, but
258  // because profiles specify data at both the lower boundary and
259  // upper boundary we need to include both the nv = 0 and nv/2 rows.
260  for (int j = 0; j <= nv / 2; ++j)
261  {
262  // The area of a spherical cap is 2*pi*r^2*(1-cos(theta)), where
263  // theta is the polar angle. To get the area of a spherical
264  // strip between two polar angles, we subtract the area of the
265  // smaller cap from the area of the larger one. Our scale
266  // factor is then the ratio of the strip area to the total
267  // surface area of the sphere, so the 2*pi*r^2 factor divided by
268  // 4*pi*r^2 turns into just 0.5.
269  // At the endpoints, we clamp the angle to 0 or pi (since the
270  // strip can't extend past the poles of the sphere) and the
271  // strip ends up being just a cap for those cases.
272  float lowerAngle = (j == 0) ? 0 : F_PI * (2 * j - 1) / nv;
273  float upperAngle =
274  (j == nv / 2) ? F_PI : F_PI * (2 * j + 1) / nv;
275  float scalefactor =
276  0.5f * (cosf(lowerAngle) - cosf(upperAngle));
277  for (int i = 0; i < nu; ++i)
278  {
279  for (int k = 0; k < nchans; ++k)
280  {
281  total += scalefactor *
282  m_iesProfile.profile[((j * nu) + i) * nchans + k];
283  }
284  }
285  }
286  m_normalizationFactor = nu * nchans / total;
287  return true;
288  }
289  }
290  return false;
291 }
292 
293 PRMAN_INLINE float
295 {
296  float res = 1.0f;
297  if (m_coneAngle > 0.0f)
298  {
299  res = 0.0f;
300  RtFloat3 localDir = P;
301  if (m_hasNegativeDeterminant)
302  localDir *= -1.0f;
303 
304  if (localDir.z > 0.0f)
305  {
306  localDir.Normalize();
307  if (localDir.z >= m_cosConeAngle)
308  {
309  res = 1.0f;
310  }
311  }
312  }
313  return res;
314 }
315 
316 PRMAN_INLINE float
318  RtPoint3 const& offsetP, RtVector3 const& segmentDir,
319  float& minT, float& maxT) const
320 {
321  // TODO - this line segment vs cone algorithm comes from David Eberly. We either need
322  // to give appropriate credit, or find an alternative method.
323  float ts = lightN.Dot(offsetP + minT * segmentDir - lightP);
324  float te = lightN.Dot(offsetP + maxT * segmentDir - lightP);
325  if (ts < 0.0f && te < 0.0f) return 0.0f;
326  if (ts * te < 0.0f)
327  {
328  float planeT = lightN.Dot(lightP - offsetP) / segmentDir.Dot(lightN);
329  if (ts < 0.0f)
330  {
331  minT = planeT;
332  }
333  else
334  {
335  maxT = planeT;
336  }
337  }
338  if (m_cosConeAngle < 1e-5f) return 1.0f;
339 
340  float vDn = segmentDir.Dot(lightN);
341  RtVector3 lToP = offsetP - lightP;
342  float c2 = vDn * vDn - segmentDir.Dot(segmentDir) * m_cosConeAngle * m_cosConeAngle;
343  if (c2 == 0.0f) return 0.0f;
344  float lpDn = lToP.Dot(lightN);
345  float c1 = vDn * lpDn - lToP.Dot(segmentDir) * m_cosConeAngle * m_cosConeAngle;
346  float c0 = lpDn * lpDn - lToP.Dot(lToP) * m_cosConeAngle * m_cosConeAngle;
347 
348  float delta = c1*c1 - c0*c2;
349 
350  if (delta > 0.0f)
351  {
352  float rootDelta = sqrtf(delta);
353  float t0;
354  float t1;
355  if (c2 > 0.0f)
356  {
357  t0 = (-c1 - rootDelta) / c2;
358  t1 = (-c1 + rootDelta) / c2;
359  }
360  else
361  {
362  t1 = (-c1 - rootDelta) / c2;
363  t0 = (-c1 + rootDelta) / c2;
364  }
365  assert( t1 >= t0 );
366 
367  minT = std::max(minT, t0);
368  maxT = std::min(maxT, t1);
369  if (minT >= maxT)
370  {
371  return 0.0f;
372  }
373  return 1.0f;
374  }
375  else
376  {
377  return 0.0f;
378  }
379 
380 }
381 
382 // Utility routines
383 PRMAN_INLINE float
384 RixIES::calcLongitude(float x, float y, bool isExr) const
385 {
386  // Compensate for the fact that OpenEXR textures store the
387  // data with a latitude range of [180, -180] from left to
388  // right, while other formats store the data with a latitude
389  // range of [0, 360].
390  float s = atan2f(y, x);
391  if (isExr)
392  {
393  s *= -0.5f / F_PI;
394  if (s < 0.0f)
395  s += 1.0f;
396  }
397  else
398  {
399  s *= 0.5f / F_PI;
400  s += 0.5f;
401  }
402  return s;
403 }
404 
405 PRMAN_INLINE void RixIES::calcLatLongParam(
406  int n, // number of points in BOP
407  float* dir, // array of reflection directions
408  bool openEXR, // Is this an EXR latlong?
409  float* s, // output s array pointer
410  float* t) const // output t array pointer
411 {
412  // this func calculates st values from direction for a lat-long texture map
413 
414  float x, y, z, len, zlen;
415  for (int i = 0; i < n; i++, s++, t++, dir += 3)
416  {
417  /* compute texture coordinates from direction */
418  /* normalize */
419  x = dir[0];
420  y = dir[1];
421  z = dir[2];
422 
423  len = x * x + y * y + z * z;
424  if (len > FLT_MIN)
425  len = 1.f / sqrtf(len);
426 
427  zlen = z * len;
428 
429  /* This clamps the value passed to asinf to avoid
430  * precision issues that might occur in the above
431  * computation. */
432  if (zlen < -1.0f)
433  zlen = -1.0f;
434  if (zlen > 1.0f)
435  zlen = 1.0f;
436 
437  *t = 0.5f - asinf(zlen) * 1.0f / F_PI;
438  *s = calcLongitude(x, y, openEXR);
439  }
440 }
441 
442 #endif
#define US_NULL
Definition: RiTypesHelper.h:683
int nu
Definition: RixIES.h:95
Definition: RixInterfaces.h:304
#define F_PI
Definition: RixShadingUtils.h:65
int nchannels
Definition: RixInterfaces.h:336
pxrcore::ColorRGB RtColorRGB
Definition: RiTypesHelper.h:520
virtual int ReleaseTexture(TxProperties const &txProperties)=0
All texture access should release a texture as soon as it is finished looking up values.
Rix interfaces are obtained from an RixContext.
Definition: RixInterfaces.h:172
Definition: RixInterfaces.h:353
int nv
Definition: RixIES.h:95
Id for New Texture-2D interface.
Definition: RixInterfaces.h:113
RixTexture performs filtered texture map lookups using a texture coordinate and a region over which t...
Definition: RixInterfaces.h:289
RtFloat3 RtPoint3
Definition: RiTypesHelper.h:70
RtUString mapName
Definition: RixIES.h:92
An object that can be used to print warnings, errors, or info in a thread safe way from within the re...
Definition: RixInterfaces.h:242
float profileScale
Definition: RixIES.h:98
pxrcore::Float3 RtFloat3
Definition: RiTypesHelper.h:69
bool isExr
Definition: RixIES.h:97
Id for RixMessages interface.
Definition: RixInterfaces.h:98
int nchannels
Definition: RixInterfaces.h:396
virtual RixInterface * GetRixInterface(RixInterfaceId id) const =0
Get the specified interface from this context.
PRMAN_INLINE void EvaluateIESProfile(RtFloat3 const &P, RtColorRGB *emission) const
Definition: RixIESInline.h:50
#define PRMAN_INLINE
Definition: prmanapi.h:99
int width
Definition: RixInterfaces.h:345
virtual int TextureData(TxProperties const &txProperties, TxParams const &txParams, int mipLevel, int cubeFace, void *result)=0
Read the whole texture into a buffer at a given MIP level.
Definition: RixInterfaces.h:359
int height
Definition: RixInterfaces.h:345
void clear()
Definition: RixIES.h:82
PRMAN_INLINE bool ReadIESProfile(RixContext const &rixCtx, RtUString const iesProfile)
Definition: RixIESInline.h:200
pxrcore::UString RtUString
Definition: RiTypesHelper.h:682
virtual int AcquireTexture(RtUString const fileName, TxAtlasStyle const atlasStyle, TxProperties &txProperties)=0
All texture access must acquire a texture first.
bool isColor
Definition: RixIES.h:96
PRMAN_INLINE float EvaluateConeAngle(RtFloat3 const &P) const
Definition: RixIESInline.h:294
RtFloat3 RtVector3
Definition: RiTypesHelper.h:72
float * profile
Definition: RixIES.h:93