Fermat
ggx_smith.h
1 /*
2  * Copyright (c) 2010-2019, NVIDIA Corporation
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of NVIDIA Corporation nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
34 #pragma once
35 
36 #include <cugar/linalg/vector.h>
37 #include <cugar/basic/numbers.h>
38 #include <cugar/bsdf/differential_geometry.h>
39 #include <cugar/bsdf/ggx_common.h>
40 
41 #define USE_APPROX_SMITH
42 
43 namespace cugar {
44 
49 struct GGXSmithMicrofacetDistribution
55 {
56  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
57  GGXSmithMicrofacetDistribution(const float _roughness) :
58  roughness(_roughness),
59  inv_roughness(1.0f / _roughness) {}
60 
61  // Smith G1(V) term
62  //
63 #if 1
64  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
65  float SmithG1V(const float NoV) const
66  {
67  const float a = roughness;
68  const float a2 = a*a;
69  const float G_V = NoV + sqrtf((NoV - NoV * a2) * NoV + a2);
70  return 2.0f * NoV / G_V;
71  }
72 #else
73  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
74  float SmithG1V(const float NoV) const
75  {
76  const float CosThetaV2 = NoV*NoV;
77  const float SinThetaV2 = 1 - CosThetaV2;
78  const float TanThetaV2 = fabsf(SinThetaV2 <= 0.0f ? 0.0f : SinThetaV2 / CosThetaV2);
79  // Perpendicular incidence -- no shadowing/masking
80  if (TanThetaV2 == 0.0f)
81  return 1.0f;
82 
83  const float alpha = roughness ;
84  const float R2 = alpha * alpha * TanThetaV2;
85  return 2.0f / (1.0f + sqrtf(1.0f + R2));
86  }
87 #endif
88 
91  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
92  float p(const Vector3f V, const Vector3f H) const
93  {
94  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
95 
96  const float VoH = fabsf( dot(V, H) );
97  const float NoV = fabsf( V.z );
98  const float NoH = fabsf( H.z );
99 
100  const float G1 = SmithG1V(NoV);
101 
102  const float D = hvd_ggx_eval(inv_alpha, NoH, H.x, H.y);
103  float p = D * G1 * VoH / NoV;
104  // NOTE: if reflection of V against H was used to generate a ray, the Jacobian 1 / (4*VoH) would elide
105  // the VoH at the numerator, leaving: D * G1 / (4 * NoV) as a solid angle probability, or D * G1 / (4 * NoV * NoL)
106  // as a projected solid angle probability
107 
108  return (!isfinite(p) || isnan(p)) ? 1.0e8f : p;
109  }
110 
113  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
115  const Vector3f u,
116  const Vector3f V,
117  float* p = NULL) const
118  {
119  const float2 alpha = make_float2(roughness, roughness);
120  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
121 
122  // flip V.z to make it positive
123  const float sgn_V = V.z >= 0.0f ? 1.0f : -1.0f;
124 
125  Vector3f H = vndf_ggx_smith_sample( make_float2(u.x,u.y), alpha, Vector3f(V.x,V.y, V.z * sgn_V));
126 
127  // flip H if needed
128  H.z *= sgn_V;
129 
130  if ( p)
131  *p = this->p(V, H);
132 
133  return H;
134  }
135 
138  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
139  float p(const DifferentialGeometry& geometry, const Vector3f V, const Vector3f H) const
140  {
141  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
142 
143  const Vector3f N = geometry.normal_s;
144 
145  const float VoH = fabsf( dot(V, H) );
146  const float NoV = fabsf( dot(N, V) );
147  const float NoH = fabsf( dot(N, H) );
148 
149  const float G1 = SmithG1V(NoV);
150 
151  const float D = hvd_ggx_eval(inv_alpha, NoH, dot(geometry.tangent, H), dot(geometry.binormal, H));
152  float p = D * G1 * VoH / NoV;
153  // NOTE: if reflection of V against H was used to generate a ray, the Jacobian 1 / (4*VoH) would elide
154  // the VoH at the numerator, leaving: D * G1 / (4 * NoV) as a solid angle probability, or D * G1 / (4 * NoV * NoL)
155  // as a projected solid angle probability
156 
157  return (!isfinite(p) || isnan(p)) ? 1.0e8f : p;
158  }
159 
162  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
164  const Vector3f u,
165  const DifferentialGeometry& geometry,
166  const Vector3f V,
167  float* p = NULL) const
168  {
169  const float2 alpha = make_float2(roughness, roughness);
170  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
171 
172  const float NoV = dot(V, geometry.normal_s);
173  const float sgn_V = NoV > 0.0f ? 1.0f : -1.0f;
174 
175  const Vector3f V_local(
176  dot(V, geometry.tangent),
177  dot(V, geometry.binormal),
178  sgn_V * NoV );
179 
180  Vector3f H = vndf_ggx_smith_sample( make_float2(u.x,u.y), alpha, V_local);
181 
182  H =
183  H.x * geometry.tangent +
184  H.y * geometry.binormal +
185  H.z * geometry.normal_s * sgn_V;
186 
187  if ( p)
188  *p = this->p(geometry, V, H);
189 
190  return H;
191  }
192 
193 public:
194  float roughness;
195  float inv_roughness;
196 };
197 
205 {
207 
215  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
216  GGXSmithBsdf(const float _roughness, bool _transmission = false, float _int_ior = 1.0f, float _ext_ior = 1.0f) :
217  roughness(_roughness),
218  inv_roughness(1.0f / _roughness),
219  int_ior(_transmission ? _int_ior : -1.0f),
220  ext_ior(_transmission ? _ext_ior : -1.0f) {}
221 
222  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
223  bool is_transmissive() const { return int_ior > 0.0f; }
224 
225  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
226  bool is_reflective() const { return int_ior < 0.0f; }
227 
230  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
231  float get_eta(const float NoV) const { return NoV >= 0.0f ? ext_ior / int_ior : int_ior / ext_ior; }
232 
235  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
237 
240  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
241  float get_inv_eta(const float NoV) const { return NoV >= 0.0f ? int_ior / ext_ior : ext_ior / int_ior; }
242 
243  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
244  static float clamp_inf(const float p) { return (!isfinite(p) || isnan(p)) ? 1.0e8f : max( p, 0.0f ); } // also clamp negative values
245 
246  // Appoximation of joint Smith term for GGX, pre-divided by the BRDF denominator (4 * NoV * NoL)
247  // [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
248  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
249  float PredividedSmithJointApprox(const float NoV, const float NoL) const
250  {
251  const float a = roughness ;
252  const float Vis_SmithV = NoL * (NoV * (1 - a) + a);
253  const float Vis_SmithL = NoV * (NoL * (1 - a) + a);
254  // Note: will generate NaNs with Roughness = 0. MinRoughness is used to prevent this
255  return 0.5f * 1.0f / (Vis_SmithV + Vis_SmithL);
256  }
257  // Height-Correlated Smith Joint Masking-Shadowing Function for GGX, pre-divided by the BRDF denominator (4 * NoV * NoL)
258  // [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
259  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
260  float PredividedSmithJoint(const float NoV, const float NoL) const
261  {
262  const float a = roughness ;
263  const float a2 = a*a;
264  const float G_V = NoV + sqrtf((NoV - NoV * a2) * NoV + a2);
265  const float G_L = NoL + sqrtf((NoL - NoL * a2) * NoL + a2);
266  return 1.0f / (G_V * G_L);
267  }
268 
269  // Smith G1(V) term, pre-divided by the BRDF denominator (4 * NoV * NoL)
270  //
271  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
272  float PredividedSmithG1V(const float NoV, const float NoL) const
273  {
274  const float a = roughness ;
275  const float a2 = a*a;
276  const float G_V = NoV + sqrtf((NoV - NoV * a2) * NoV + a2);
277  return 0.5f / (G_V * NoL);
278  }
279 
280  // Height-Correlated Smith Joint Masking-Shadowing Function for GGX
281  // [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
282  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
283  float SmithJoint(const float NoV, const float NoL) const
284  {
285  // masking
286  //const float a_V = inv_roughness / tanf(acosf(NoV));in
287  //const float LambdaV = (NoV < 1.0f) ? 0.5f * (-1.0f + sqrtf(1.0f + 1.0f / a_V / a_V)) : 0.0f;
288  const float CosThetaV2 = NoV*NoV;
289  const float SinThetaV2 = 1 - CosThetaV2;
290  const float TanThetaV2 = fabsf(SinThetaV2 <= 0.0f ? 0.0f : SinThetaV2 / CosThetaV2);
291  const float a_V2 = inv_roughness * inv_roughness / TanThetaV2;
292  const float LambdaV = (NoV < 1.0f) ? 0.5f * (-1.0f + sqrtf(1.0f + 1.0f / a_V2)) : 0.0f;
293 
294  // shadowing
295  //const float a_L = inv_roughness / tanf(acosf(NoL));
296  //const float LambdaL = (NoL < 1.0f) ? 0.5f * (-1.0f + sqrtf(1.0f + 1.0f / a_L / a_L)) : 0.0f;
297  const float CosThetaL2 = NoL*NoL;
298  const float SinThetaL2 = 1 - CosThetaL2;
299  const float TanThetaL2 = fabsf(SinThetaL2 <= 0.0f ? 0.0f : SinThetaL2 / CosThetaL2);
300  const float a_L2 = inv_roughness * inv_roughness / TanThetaL2;
301  const float LambdaL = (NoL < 1.0f) ? 0.5f * (-1.0f + sqrtf(1.0f + 1.0f / a_L2)) : 0.0f;
302 
303  // height-correlated Smith shadow-masking term (http://jcgt.org/published/0003/02/03/paper.pdf, page 91)
304  return 1.0f / (1.0f + LambdaV + LambdaL);
305  }
306 
307  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
308  float SmithG1(const float NoV) const
309  {
310  const float CosThetaV2 = NoV*NoV;
311  const float SinThetaV2 = 1 - CosThetaV2;
312  const float TanThetaV2 = fabsf(SinThetaV2 <= 0.0f ? 0.0f : SinThetaV2 / CosThetaV2);
313  // Perpendicular incidence -- no shadowing/masking
314  if (TanThetaV2 == 0.0f)
315  return 1.0f;
316 
317  const float alpha = roughness ;
318  const float R2 = alpha * alpha * TanThetaV2;
319  return 2.0f / (1.0f + sqrtf(1.0f + R2));
320  }
321 
322  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
323  float dwo_dh_transmission_factor(const float VoH, const float LoH, const float eta, const float inv_eta) const
324  {
325  // compute whether this is a ray for which we have total internal reflection,
326  // i.e. if cos_theta_t2 <= 0, and in that case return zero.
327  const float cos_theta_i = fabsf(VoH);
328  const float cos_theta_t2 = 1.f - eta * eta * (1.f - cos_theta_i * cos_theta_i);
329  if (cos_theta_t2 < 0.0f)
330  return 0.0f;
331 
332  // compute dwo_dh: equation 17 in Microfacet Models for Refraction through Rough Surfaces, Walter et al.
333  const float sqrtDenom = VoH + inv_eta * LoH;
334  const float factor = 4 * inv_eta * inv_eta
335  * fabsf( VoH * LoH ) /
336  (sqrtDenom * sqrtDenom);
337  // NOTE: the actual formula has no 4 factor at the numerator, but it contains NoV * NoL at
338  // the denominator, which is already included in our G term above. The additional multiplication
339  // by 4 is needed to account for the predivision.
340  return factor;
341  }
342 
345  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
346  Vector3f f(const DifferentialGeometry& geometry, const Vector3f V, const Vector3f L) const
347  {
348  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
349 
350  const Vector3f N = geometry.normal_s;
351 
352  const float NoL = dot(N, L);
353  const float NoV = dot(N, V);
354 
355  const float eta = get_eta(NoV);
356  const float inv_eta = get_inv_eta(NoV);
357 
358  const Vector3f H = vndf_microfacet(V,L,N,inv_eta);
359 
360  const float NoH = dot(N, H);
361 
362  const float transmission_sign = is_transmissive() ? -1.0f : 1.0f;
363 
364  // check whether there is energy exchange between different sides of the surface
365  if (transmission_sign * NoL * NoV <= 0.0f || NoH == 0.0f)
366  return Vector3f(0.0f);
367 
368  const float D = hvd_ggx_eval(inv_alpha, fabsf( NoH ), dot(geometry.tangent, H), dot(geometry.binormal, H));
369  //const float denom = (4 * NoV * NoL);
370 
371  #if defined(USE_APPROX_SMITH)
372  const float G = PredividedSmithJointApprox(fabsf(NoV), fabsf(NoL));
373  #else
374  const float G = PredividedSmithJoint(fabsf(NoV), fabsf(NoL));
375  #endif
376 
377  if (!is_transmissive())
378  return clamp_inf( G * D );
379  else
380  {
381  // compute whether this is a ray for which we have total internal reflection,
382  // i.e. if cos_theta_t2 <= 0, and in that case return zero.
383  const float VoH = dot(V, H);
384  const float LoH = dot(L, H);
385 
386  const float transmission_factor = dwo_dh_transmission_factor(VoH, LoH, eta, inv_eta);
387 
388  return clamp_inf( G * D * transmission_factor );
389  }
390  }
391 
394  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
395  Vector3f f_over_p(const DifferentialGeometry& geometry, const Vector3f V, const Vector3f L) const
396  {
397  const Vector3f N = geometry.normal_s;
398 
399  const float NoL = dot(N, L);
400  const float NoV = dot(N, V);
401 
402  const float inv_eta = get_inv_eta(NoV);
403 
404  const Vector3f H = vndf_microfacet(V,L,N,inv_eta);
405 
406  const float NoH = dot(N, H);
407 
408  const float transmission_sign = is_transmissive() ? -1.0f : 1.0f;
409 
410  // check whether there is energy exchange between different sides of the surface
411  if (transmission_sign * NoL * NoV <= 0.0f || NoH == 0.0f)
412  return Vector3f(0.0f);
413 
414  const float G1 = PredividedSmithG1V(fabsf(NoV), fabsf(NoL));
415 
416  #if defined(USE_APPROX_SMITH)
417  const float G = PredividedSmithJointApprox(fabsf(NoV), fabsf(NoL));
418  #else
419  const float G = PredividedSmithJoint(fabsf(NoV), fabsf(NoL));
420  #endif
421 
422  return Vector3f(clamp_inf( G / G1 ));
423  }
424 
427  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
428  void f_and_p(const DifferentialGeometry& geometry, const Vector3f V, const Vector3f L, Vector3f& f, float& p, const SphericalMeasure measure = kProjectedSolidAngle) const
429  {
430  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
431 
432  const Vector3f N = geometry.normal_s;
433 
434  const float NoL = dot(N, L);
435  const float NoV = dot(N, V);
436 
437  // fetch the interior/exterior IOR ratio
438  const float eta = get_eta(NoV);
439  const float inv_eta = get_inv_eta(NoV);
440 
441  const Vector3f H = vndf_microfacet(V,L,N,inv_eta);
442 
443  const float NoH = dot(N, H);
444 
445  const float transmission_sign = is_transmissive() ? -1.0f : 1.0f;
446 
447  // check whether there is energy exchange between different sides of the surface
448  if (transmission_sign * NoL * NoV <= 0.0f || NoH == 0.0f)
449  {
450  p = 0.0f;
451  f = Vector3f(0.0f);
452  return;
453  }
454 
455  const float D = hvd_ggx_eval(inv_alpha, fabsf( NoH ), dot(geometry.tangent, H), dot(geometry.binormal, H));
456 
457  #if defined(USE_APPROX_SMITH)
458  const float G = PredividedSmithJointApprox(fabsf(NoV), fabsf(NoL));
459  #else
460  const float G = PredividedSmithJoint(fabsf(NoV), fabsf(NoL));
461  #endif
462 
463  const float G1 = PredividedSmithG1V(fabsf(NoV), fabsf(NoL));
464 
465  float transmission_factor = 1.0f;
466  if (is_transmissive())
467  {
468  const float VoH = dot(V, H);
469  const float LoH = dot(L, H);
470 
471  transmission_factor = dwo_dh_transmission_factor(VoH, LoH, eta, inv_eta);
472  }
473 
474  f = clamp_inf( G * D * transmission_factor );
475  p = clamp_inf( G1 * D * transmission_factor );
476 
477  if (measure == kSolidAngle)
478  p *= fabsf( NoL );
479  }
480 
483  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
484  float p(const DifferentialGeometry& geometry, const Vector3f V, const Vector3f L, const SphericalMeasure measure = kProjectedSolidAngle) const
485  {
486  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
487 
488  const Vector3f N = geometry.normal_s;
489 
490  const float NoL = dot(N, L);
491  const float NoV = dot(N, V);
492 
493  const float eta = get_eta(NoV);
494  const float inv_eta = get_inv_eta(NoV);
495 
496  const Vector3f H = vndf_microfacet(V,L,N,inv_eta);
497 
498  const float NoH = dot(N, H);
499 
500  const float transmission_sign = is_transmissive() ? -1.0f : 1.0f;
501 
502  // check whether there is energy exchange between different sides of the surface
503  if (transmission_sign * NoL * NoV <= 0.0f || NoH == 0.0f)
504  return 0.0f;
505 
506  const float D = hvd_ggx_eval(inv_alpha, fabsf( NoH ), dot(geometry.tangent, H), dot(geometry.binormal, H));
507  const float G1 = PredividedSmithG1V(fabsf(NoV), fabsf(NoL));
508 
509  float transmission_factor = 1.0f;
510  if (is_transmissive())
511  {
512  const float VoH = dot(V, H);
513  const float LoH = dot(L, H);
514 
515  transmission_factor = dwo_dh_transmission_factor(VoH, LoH, eta, inv_eta);
516  }
517 
518  float p = clamp_inf( G1 * D * transmission_factor );
519 
520  if (measure == kSolidAngle)
521  p *= fabsf( NoL );
522 
523  return p;
524  }
525 
528  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
529  void sample(
530  const DifferentialGeometry& geometry,
531  const Vector3f H,
532  const Vector3f V,
533  Vector3f& L,
534  Vector3f& g,
535  float& p,
536  float& p_proj) const
537  {
538  const float2 alpha = make_float2(roughness, roughness);
539  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
540 
541  const Vector3f N = geometry.normal_s;
542 
543  const float NoV = dot(N, V);
544 
545  const float eta = get_eta(NoV);
546  const float inv_eta = get_inv_eta(NoV);
547 
548  if (NoV == 0.0f)
549  {
550  p = 0.0f;
551  p_proj = 0.0f;
552  g = Vector3f(0.0f);
553  return;
554  }
555 
556  if (!is_transmissive())
557  {
558  // reflect
559  L = 2 * dot(V, H) * H - V;
560  }
561  else
562  {
563  // compute whether this is a ray for which we have total internal reflection,
564  // i.e. if cos_theta_t2 <= 0, and in that case return zero.
565  const float VoH = dot(V, H);
566  const float cos_theta_i = VoH;
567  const float cos_theta_t2 = 1.f - eta * eta * (1.f - cos_theta_i * cos_theta_i);
568  if (cos_theta_t2 < 0.0f)
569  {
570  L = 2 * dot(V, H) * H - V; // initialize to reflection
571  p = 0.0f;
572  p_proj = 0.0f;
573  g = Vector3f(0.0f);
574  return;
575  }
576  const float cos_theta_t = (cos_theta_i >= 0.0f ? 1.0f : -1.0f) * sqrtf(cos_theta_t2);
577 
578  // refract
579  L = (eta * cos_theta_i - cos_theta_t) * H - eta * V;
580  }
581 
582  const float NoL = dot(N, L);
583  const float NoH = dot(N, H);
584 
585  const float transmission_sign = is_transmissive() ? -1.0f : 1.0f;
586 
587  // check whether there is energy exchange between different sides of the surface
588  if (transmission_sign * NoL * NoV <= 0.0f || NoH == 0.0f)
589  {
590  p = 0.0f;
591  p_proj = 0.0f;
592  g = Vector3f(0.0f);
593  }
594  else
595  {
596  const float D = hvd_ggx_eval(inv_alpha, fabsf( NoH ), dot(geometry.tangent, H), dot(geometry.binormal, H));
597 
598  #if defined(USE_APPROX_SMITH)
599  const float G = PredividedSmithJointApprox(fabsf(NoV), fabsf(NoL));
600  #else
601  const float G = PredividedSmithJoint(fabsf(NoV), fabsf(NoL));
602  #endif
603 
604  const float G1 = PredividedSmithG1V(fabsf(NoV), fabsf(NoL));
605 
606  float transmission_factor = 1.0f;
607  if (is_transmissive())
608  {
609  const float VoH = dot(V, H);
610  const float LoH = dot(L, H);
611 
612  transmission_factor = dwo_dh_transmission_factor(VoH, LoH, eta, inv_eta);
613  }
614 
615  p_proj = clamp_inf( G1 * D * transmission_factor );
616  p = p_proj * fabsf(NoL);
617  g = clamp_inf( G / G1 );
618  assert(is_finite(g.x) && is_finite(g.y) && is_finite(g.z));
619  }
620  }
621 
624  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
625  void sample(
626  const Vector3f u,
627  const DifferentialGeometry& geometry,
628  const Vector3f V,
629  Vector3f& L,
630  Vector3f& g,
631  float& p,
632  float& p_proj) const
633  {
634  const float2 alpha = make_float2(roughness, roughness);
635  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
636 
637  const Vector3f N = geometry.normal_s;
638 
639  const float NoV = dot(N, V);
640 
641  const float eta = get_eta(NoV);
642  const float inv_eta = get_inv_eta(NoV);
643 
644  const float sgn_V = NoV > 0.0f ? 1.0f : -1.0f;
645 
646  const Vector3f V_local(
647  dot(V, geometry.tangent),
648  dot(V, geometry.binormal),
649  sgn_V * NoV );
650 
651  if (NoV == 0.0f)
652  {
653  p = 0.0f;
654  p_proj = 0.0f;
655  g = Vector3f(0.0f);
656  return;
657  }
658 
659  Vector3f H = vndf_ggx_smith_sample( make_float2(u.x,u.y), alpha, V_local );
660  //assert(is_finite(H.x) && is_finite(H.y) && is_finite(H.z));
661 
662  H =
663  H.x * geometry.tangent +
664  H.y * geometry.binormal +
665  H.z * geometry.normal_s * sgn_V;
666 
667  if (!is_transmissive())
668  {
669  // reflect
670  L = 2 * dot(V, H) * H - V;
671  }
672  else
673  {
674  // compute whether this is a ray for which we have total internal reflection,
675  // i.e. if cos_theta_t2 <= 0, and in that case return zero.
676  const float VoH = dot(V, H);
677  const float cos_theta_i = VoH;
678  const float cos_theta_t2 = 1.f - eta * eta * (1.f - cos_theta_i * cos_theta_i);
679  if (cos_theta_t2 < 0.0f)
680  {
681  L = 2 * dot(V, H) * H - V; // initialize to reflection
682  p = 0.0f;
683  p_proj = 0.0f;
684  g = Vector3f(0.0f);
685  return;
686  }
687  const float cos_theta_t = -(cos_theta_i >= 0.0f ? 1.0f : -1.0f) * sqrtf(cos_theta_t2);
688 
689  // refract
690  L = (eta * cos_theta_i + cos_theta_t) * H - eta * V;
691  }
692 
693  const float NoL = dot(N, L);
694  const float NoH = dot(N, H);
695 
696  const float transmission_sign = is_transmissive() ? -1.0f : 1.0f;
697 
698  // check whether there is energy exchange between different sides of the surface
699  if (transmission_sign * NoL * NoV <= 0.0f || NoH == 0.0f)
700  {
701  p = 0.0f;
702  p_proj = 0.0f;
703  g = Vector3f(0.0f);
704  }
705  else
706  {
707  const float D = hvd_ggx_eval(inv_alpha, fabsf( NoH ), dot(geometry.tangent, H), dot(geometry.binormal, H));
708 
709  #if defined(USE_APPROX_SMITH)
710  const float G = PredividedSmithJointApprox(fabsf(NoV), fabsf(NoL));
711  #else
712  const float G = PredividedSmithJoint(fabsf(NoV), fabsf(NoL));
713  #endif
714 
715  const float G1 = PredividedSmithG1V(fabsf(NoV), fabsf(NoL));
716 
717  float transmission_factor = 1.0f;
718  if (is_transmissive())
719  {
720  const float VoH = dot(V, H);
721  const float LoH = dot(L, H);
722 
723  transmission_factor = dwo_dh_transmission_factor(VoH, LoH, eta, inv_eta);
724  }
725 
726  p_proj = clamp_inf( G1 * D * transmission_factor );
727  p = p_proj * fabsf(NoL);
728  g = clamp_inf( G / G1 );
729  assert(is_finite(g.x) && is_finite(g.y) && is_finite(g.z));
730  }
731  }
732 
735  template <typename RandomGeneratorT>
736  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
737  bool invert(
738  const DifferentialGeometry& geometry,
739  const Vector3f V,
740  const Vector3f L,
741  RandomGeneratorT& random,
742  Vector3f& z,
743  float& p,
744  float& p_proj) const
745  {
746  const float2 alpha = make_float2(roughness, roughness);
747  const float2 inv_alpha = make_float2(inv_roughness, inv_roughness);
748 
749  const Vector3f N = geometry.normal_s;
750 
751  const float NoV = dot(N, V);
752  const float NoL = dot(N, L);
753 
754  const float eta = get_eta(NoV);
755  const float inv_eta = get_inv_eta(NoV);
756 
757  const float sgn_N = NoV > 0.0f ? 1.0f : -1.0f;
758 
759  const Vector3f V_local(
760  dot(V, geometry.tangent),
761  dot(V, geometry.binormal),
762  sgn_N * NoV );
763 
764  const Vector3f H = vndf_microfacet(V,L,N,inv_eta);
765 
766  const float NoH = dot(N, H);
767 
768  const Vector3f H_local = Vector3f(
769  dot(H, geometry.tangent),
770  dot(H, geometry.binormal),
771  sgn_N * NoH );
772 
773  Vector2f uv = vndf_ggx_smith_invert( H_local, alpha, V_local );
774 
775  z.x = uv.x;
776  z.y = uv.y;
777  z.z = random.next();
778 
779  const float transmission_sign = is_transmissive() ? -1.0f : 1.0f;
780 
781  // check whether there is energy exchange between different sides of the surface
782  if (transmission_sign * NoL * NoV <= 0.0f || NoH == 0.0f)
783  {
784  p = 0.0f;
785  p_proj = 0.0f;
786  }
787  else
788  {
789  const float D = hvd_ggx_eval(inv_alpha, fabsf( NoH ), H_local.x, H_local.y);
790 
791  #if defined(USE_APPROX_SMITH)
792  const float G = PredividedSmithJointApprox(fabsf(NoV), fabsf(NoL));
793  #else
794  const float G = PredividedSmithJoint(fabsf(NoV), fabsf(NoL));
795  #endif
796 
797  const float G1 = PredividedSmithG1V(fabsf(NoV), fabsf(NoL));
798 
799  float transmission_factor = 1.0f;
800  if (is_transmissive())
801  {
802  const float VoH = dot(V, H);
803  const float LoH = dot(L, H);
804 
805  transmission_factor = dwo_dh_transmission_factor(VoH, LoH, eta, inv_eta);
806  }
807 
808  p_proj = clamp_inf( G1 * D * transmission_factor );
809  p = p_proj * fabsf(NoL);
810  }
811 
812  // and take the reciprocals
813  p_proj = clamp_inf( 1.0f / p_proj );
814  p = clamp_inf( 1.0f / p );
815  return true;
816  }
817 
820  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
822  const DifferentialGeometry& geometry,
823  const Vector3f V,
824  const Vector3f L,
825  const Vector3f u,
826  float& p,
827  float& p_proj) const
828  {
829  const Vector3f N = geometry.normal_s;
830 
831  p_proj = 1.0f / cugar::max(this->p(geometry, V, L, kProjectedSolidAngle), 1.0e-8f);
832  p = p_proj / cugar::max(fabsf(dot(L, N)), 1.0e-8f);
833  }
834 
835 public:
836  float roughness;
837  float inv_roughness;
838  float int_ior;
839  float ext_ior;
840 };
841 
845 } // namespace cugar
SphericalMeasure
Definition: differential_geometry.h:50
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE Vector3f f(const DifferentialGeometry &geometry, const Vector3f V, const Vector3f L) const
Definition: ggx_smith.h:346
Defines the GGX BSDF.
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE Vector3f sample(const Vector3f u, const Vector3f V, float *p=NULL) const
Definition: ggx_smith.h:114
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float p(const DifferentialGeometry &geometry, const Vector3f V, const Vector3f H) const
Definition: ggx_smith.h:139
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE void sample(const DifferentialGeometry &geometry, const Vector3f H, const Vector3f V, Vector3f &L, Vector3f &g, float &p, float &p_proj) const
Definition: ggx_smith.h:529
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float hvd_ggx_eval(const float2 &inv_alpha, const float nh, const float ht, const float hb)
Definition: ggx_common.h:92
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE bool invert(const DifferentialGeometry &geometry, const Vector3f V, const Vector3f L, RandomGeneratorT &random, Vector3f &z, float &p, float &p_proj) const
Definition: ggx_smith.h:737
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE Vector3f vndf_microfacet(const Vector3f V, const Vector3f L, const Vector3f N, const float inv_eta)
Definition: ggx_common.h:70
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE GGXSmithMicrofacetDistribution distribution() const
Definition: ggx_smith.h:236
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float p(const DifferentialGeometry &geometry, const Vector3f V, const Vector3f L, const SphericalMeasure measure=kProjectedSolidAngle) const
Definition: ggx_smith.h:484
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float2 vndf_ggx_smith_invert(const Vector3f _N, const float2 alpha, const Vector3f _V)
Definition: ggx_common.h:296
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float get_inv_eta(const float NoV) const
Definition: ggx_smith.h:241
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float p(const Vector3f V, const Vector3f H) const
Definition: ggx_smith.h:92
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float PredividedSmithJointApprox(const float NoV, const float NoL) const
Definition: ggx_smith.h:249
Definition: ggx_smith.h:54
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float get_eta(const float NoV) const
Definition: ggx_smith.h:231
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float SmithG1(const float NoV) const
Definition: ggx_smith.h:308
Definition: differential_geometry.h:59
float random()
Definition: tiled_sampling.h:44
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float3 vndf_ggx_smith_sample(const float2 samples, const float2 alpha, const Vector3f _V)
Definition: ggx_common.h:265
Definition: ggx_smith.h:204
Define a vector_view POD type and plain_view() for std::vector.
Definition: diff.h:38
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float PredividedSmithJoint(const float NoV, const float NoL) const
Definition: ggx_smith.h:260
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE Vector3f sample(const Vector3f u, const DifferentialGeometry &geometry, const Vector3f V, float *p=NULL) const
Definition: ggx_smith.h:163
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE GGXSmithBsdf(const float _roughness, bool _transmission=false, float _int_ior=1.0f, float _ext_ior=1.0f)
Definition: ggx_smith.h:216
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float PredividedSmithG1V(const float NoV, const float NoL) const
Definition: ggx_smith.h:272
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE void f_and_p(const DifferentialGeometry &geometry, const Vector3f V, const Vector3f L, Vector3f &f, float &p, const SphericalMeasure measure=kProjectedSolidAngle) const
Definition: ggx_smith.h:428
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE void inverse_pdf(const DifferentialGeometry &geometry, const Vector3f V, const Vector3f L, const Vector3f u, float &p, float &p_proj) const
Definition: ggx_smith.h:821
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE Vector3f f_over_p(const DifferentialGeometry &geometry, const Vector3f V, const Vector3f L) const
Definition: ggx_smith.h:395
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE void sample(const Vector3f u, const DifferentialGeometry &geometry, const Vector3f V, Vector3f &L, Vector3f &g, float &p, float &p_proj) const
Definition: ggx_smith.h:625