Fermat
ggx_common.h
Go to the documentation of this file.
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 
40 
41 namespace cugar {
42 
47 CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
50 Vector3f microfacet(const Vector3f V, const Vector3f L, const Vector3f N, const float inv_eta)
51 {
52  Vector3f H = (dot(V, N)*dot(L, N) >= 0.0f) ?
53  V + L :
54  V + L*inv_eta;
55 
56  // when in doubt, return N
57  if (dot(H,H) == 0.0f)
58  return N;
59 
60  // make sure H points in the same direction as N
61  if (dot(N, H) < 0.0f)
62  H = -H;
63 
64  return normalize(H);
65 }
66 
69 CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
70 Vector3f vndf_microfacet(const Vector3f V, const Vector3f L, const Vector3f N, const float inv_eta)
71 {
72  Vector3f H = (dot(V, N)*dot(L, N) >= 0.0f) ?
73  V + L :
74  V + L*inv_eta;
75 
76  // when in doubt, return N
77  if (dot(H,H) < 1.0e-12f)
78  return N;
79 
80  // make sure H points in the same direction as V
81  if (dot(V, H) < 0.0f)
82  H = -H;
83 
84  return normalize(H);
85 }
86 
91 CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
93  const float2 &inv_alpha,
94  const float nh, // dot(shading_normal, h)
95  const float ht, // dot(x_axis, h)
96  const float hb) // dot(z_axis, h)
97 {
98  const float x = ht * inv_alpha.x;
99  const float y = hb * inv_alpha.y;
100  const float aniso = x * x + y * y;
101 
102  /*original:
103  const float nh2 = nh * nh;
104  const float f = aniso / nh2 + 1.0f;
105  return (float)(1.0 / M_PI) * inv_alpha.x * inv_alpha.y / (f * f *
106  nh2 * nh);
107  */
108  const float f = aniso + nh * nh;
109  return (1.0f / M_PIf) * inv_alpha.x * inv_alpha.y / (f * f);
110 }
111 
114 CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
116  const float2 &samples,
117  const float inv_alpha)
118 {
119  // optimized version, any further optimizations introduce severe precision problems
120  const float phi = (float)(2.0 * M_PI) * samples.x;
121  float cp, sp;
122  cugar::sincosf(phi, &sp, &cp);
123 
124  const float ia2 = inv_alpha * inv_alpha;
125 
126  const float sp2 = cp;
127  const float cp2 = sp;
128 
129  const float tantheta2 = samples.y / ((1.0f - samples.y) * ia2);
130  const float sintheta = ::sqrtf(tantheta2 / (1.0f + tantheta2));
131 
132  return make_float3(
133  cp2 * sintheta,
134  sp2 * sintheta,
135  cugar::rsqrtf(1.0f + tantheta2)); // also okay on CPU so far
136 }
137 
140 CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
142  const float2 &samples,
143  const float2 &inv_alpha)
144 {
145  // optimized version, any further optimizations introduce severe precision problems
146  const float phi = (float)(2.0 * M_PI) * samples.x;
147  float cp, sp;
148  sincosf(phi, &sp, &cp);
149 
150  const float iax2 = inv_alpha.x*inv_alpha.x;
151  const float iay2 = inv_alpha.y*inv_alpha.y;
152 
153  const float is = rsqrtf(iax2 * cp*cp + iay2 * sp*sp); // also okay on CPU so far
154 
155  const float sp2 = inv_alpha.x * cp*is;
156  const float cp2 = inv_alpha.y * sp*is;
157 
158  const float tantheta2 = samples.y / ((1.0f - samples.y) * (cp2 * cp2 * iax2 + sp2 * sp2 * iay2));
159  const float sintheta = sqrtf(tantheta2 / (1.0f + tantheta2));
160 
161  return make_float3(
162  cp2 * sintheta,
163  sp2 * sintheta,
164  rsqrtf(1.0f + tantheta2)); // also okay on CPU so far
165 }
166 
167 namespace ggx {
168 
169  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
170  Vector2f Fu(const float u, const float2 ia, const float2 b)
171  {
172  const float iax2 = ia.x*ia.x;
173  const float iay2 = ia.y*ia.y;
174 
175  const float2 p = make_float2(sinf(u), cosf(u));
176 
177  return Vector2f(
178  sqrtf(iax2 * p.y*p.y + iay2 * p.x*p.x) * b.x - p.y,
179  sqrtf(iax2 * p.y*p.y + iay2 * p.x*p.x) * b.y - p.x);
180  }
181 
182  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
183  Vector2f Ju(const float u, const float2 ia, const float2 b)
184  {
185  const float iax2 = ia.x*ia.x;
186  const float iay2 = ia.y*ia.y;
187 
188  const float2 p = make_float2(sinf(u), cosf(u));
189 
190  Vector2f r;
191  r.x = (-2.0f * iax2 * p.y * p.x + 2.0f * iay2 * p.x * p.y) * b.x * 0.5f / (iax2 * p.y*p.y + iay2 * p.x*p.x) + p.x; // d/du f1
192  r.y = (-2.0f * iax2 * p.y * p.x + 2.0f * iay2 * p.x * p.y) * b.x * 0.5f / (iax2 * p.y*p.y + iay2 * p.x*p.x) - p.y; // d/du f2
193  return r;
194  }
195 
196  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
197  bool refine_solution(float& u, const float2 ia, const float2 b)
198  {
199  //const float r_old = length(Fu(u, ia, b));
200  for (uint32 i = 0; i < 100; ++i)
201  {
202  const Vector2f f = Fu(u, ia, b);
203  if (fabsf(f.x) < 1.0e-5f &&
204  fabsf(f.y) < 1.0e-5f)
205  return true;
206 
207  const Vector2f J = Ju(u, ia, b);
208 
209  const Vector2f inv_J = J / dot(J, J);
210 
211  u = u - dot(inv_J, f);
212  }
213  //const float r_new = length(Fu(u, ia, b));
214  //printf("refined %f!\n", r_new / r_old);
215  //printf("inversion failed, %f, %f!\n", r_old, r_new);
216  return false;
217  }
218 
219  CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
220  bool invert(float& u, const float2 ia, const float2 b)
221  {
222  // easy analytical inversion
223  const float cp = b.x;
224  const float sp = b.y;
225 
226  //const float phi = atan2(sp, cp);
227  const float phi = atan2f( sp, cp );
228  u = phi < 0.0f ? phi + 2.0f*float(M_PI) : phi;
229  //refine_solution(u, ia, b);
230  return true;
231  }
232 
233 } // namespace ggx
234 
237 CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
239  const float3 H,
240  const float2 inv_alpha)
241 {
242  const float tantheta2 = 1.0f / (H.z * H.z) - 1.0f;
243  const float sintheta = sqrtf(tantheta2 / (1.0f + tantheta2));
244  const float cp2 = H.x / sintheta;
245  const float sp2 = H.y / sintheta;
246 
247  const float iax2 = inv_alpha.x*inv_alpha.x;
248  const float iay2 = inv_alpha.y*inv_alpha.y;
249 
250  const float v = tantheta2 * (cp2 * cp2 * iax2 + sp2 * sp2 * iay2) / (1.0f + tantheta2 * (cp2 * cp2 * iax2 + sp2 * sp2 * iay2));
251 
252  float u(0.5f);
253 
254  ggx::invert(u, inv_alpha, make_float2(sp2 / inv_alpha.x, cp2 / inv_alpha.y));
255 
256  u /= (2.0f * M_PIf);
257 
258  return make_float2(u, v);
259 }
260 
264 CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
266  const float2 samples,
267  const float2 alpha,
268  const Vector3f _V)
269 {
270  // stretch view
271  Vector3f V = normalize(Vector3f(alpha.x * _V.x, alpha.y * _V.y, _V.z));
272 
273  // orthonormal basis
274  Vector3f T1 = (V.z < 0.9999f) ? normalize(cross(V, Vector3f(0,0,1))) : Vector3f(1,0,0);
275  Vector3f T2 = cross(T1, V);
276 
277  // sample point with polar coordinates (r, phi)
278  float a = 1.0f / (1.0f + V.z);
279  float r = sqrtf(samples.x);
280  float phi = (samples.y < a) ? samples.y / a * M_PIf : M_PIf + (samples.y - a) / (1.0f - a) * M_PIf;
281  float P1 = r*cosf(phi);
282  float P2 = r*sinf(phi) * ((samples.y < a) ? 1.0f : V.z);
283 
284  // compute normal
285  Vector3f N = P1*T1 + P2*T2 + sqrtf(max(0.0f, 1.0f - P1*P1 - P2*P2))*V;
286 
287  // unstretch
288  N = normalize(Vector3f(alpha.x*N.x, alpha.y*N.y, max(0.0f, N.z)));
289  return N;
290 }
291 
295 CUGAR_FORCEINLINE CUGAR_HOST_DEVICE
297  const Vector3f _N,
298  const float2 alpha,
299  const Vector3f _V)
300 {
301  // stretch view
302  Vector3f V = normalize(Vector3f(alpha.x * _V.x, alpha.y * _V.y, _V.z));
303 
304  // orthonormal basis
305  Vector3f T1 = (V.z < 0.9999f) ? normalize(cross(V, Vector3f(0,0,1))) : Vector3f(1,0,0);
306  Vector3f T2 = cross(T1, V);
307 
308  // solve for (X,Y,Z) the system:
309  //
310  // N = normalize(Vector3f(ax*X, ay*Y, max(0.0f, Z)));
311  //
312  // <=> N = (ax*X, ay*Y, max(0.0f, Z) / sqrt(ax*X, ay*Y, max(0.0f, Z)^2
313  //
314  // <=> Nx^2 = (ax*X)^2 / (ax*X, ay*Y, max(0.0f, Z)^2
315  // Ny^2 = (ay*Y)^2 / (ax*X, ay*Y, max(0.0f, Z)^2
316  // Nz^2 = Z^2 / (ax*X, ay*Y, max(0.0f, Z)^2
317  //
318  // <=> a0 * XX + b0 * YY + c0 * ZZ = 0
319  // a1 * XX + b1 * YY + c1 * ZZ = 0
320  // a2 * XX + b2 * YY + c2 * ZZ = 0
321  //
322  // => a linear system in XX, YY, ZZ
323  // => solve for (XX,YY,ZZ)
324  //
325  // M * (XX,YY,ZZ) = 0 with the constraint (XX + YY + ZZ) = 1
326  //
327  // => find the solution space kernel(M), and find a point of that space satisfying the constraint
328  //
329  // Geometrically: given the 1d vector space spanned by < N > (i.e. the kernel of the normalization),
330  // we need to find the point in that space with (ax X)^2 + (ax Y)^2 + Z^2 = 1.
331  // In other words, we need to intersect the ray < N > with an ellipsoid.
332  //
333  // Given M = (ax, ay, 1) * I and setting v' = M^(-1) v, we need to solve for t^2 |v'| ^2 = 1,
334  // obtaining t = |v'|^2.
335  //
336  Vector3f N = normalize( Vector3f( _N.x / alpha.x, _N.y / alpha.y, _N.z ) );
337 
338  // solve for (P1, P2)
339  const float P1 = dot( N, T1 );
340  const float P2 = dot( N, T2 );
341 
342  const float a = 1.0f / (1.0f + V.z);
343 
344  float2 samples;
345 
346  // two cases:
347  // 1. N projects along V to the tangent half disk (on the tangent plane) (green disk in the paper)
348  // 2. N projects along V to the half disk orthogonal to V (blue disk in the paper)
349  Vector3f PN = N - dot(N,V) * V;
350 
351  if (PN.z > 0.0f)
352  { // case 2: samples.y < a (blue disk : phi in [0,PI))
353  const float r2 = min( P1*P1 + P2*P2, 1.0f );
354  const float r = sqrtf(r2);
355 
356  float phi = r > 1.0e-6f ? atan2f( P2 / r, P1 / r ) : 0.0f;
357  if (phi < 0.0f)
358  phi += M_TWO_PIf;
359 
360  // solve for samples.x : r = sqrtf(samples.x);
361  samples.x = r2;
362 
363  // solve for samples.y : phi = samples.y / a * M_PIf;
364  samples.y = phi * a / M_PIf;
365  if (phi < M_PIf)
366  {
367  assert(is_finite(samples.x) && is_finite(samples.y));
368  assert(samples.y >= 0.0f && samples.y <= 1.0f);
369  return samples;
370  }
371  }
372  //else // stay on the safe side: if the first case didn't work, try the second
373  {
374  // case 1: samples.y >= a (green disk : phi in [PI,2*PI))
375  const float r2 = min( P1*P1 + P2*P2 / (V.z*V.z), 1.0f );
376  const float r = sqrtf(r2);
377 
378  float phi = r > 1.0e-6f && V.z > 1.0e-6f ? atan2f( P2 / (r * V.z), P1 / r ) : 0.0f;
379  if (phi < 0.0f)
380  phi += M_TWO_PIf;
381 
382  // solve for samples.x : r = sqrtf(samples.x);
383  samples.x = r2;
384 
385  // solve for samples.y : phi = M_PIf + (samples.y - a) / (1.0f - a) * M_PIf;
386  // => phi - M_PIf = (samples.y - a) / (1.0f - a) * M_PIf;
387  // => (phi - M_PIf) * (1.0f - a) / M_PIf; = (samples.y - a);
388  samples.y = (phi - M_PIf) * (1.0f - a) / M_PIf + a;
389  assert(is_finite(samples.x) && is_finite(samples.y));
390  assert(samples.y >= 0.0f && samples.y <= 1.0f);
391  }
392  return samples;
393 }
394 
398 } // namespace cugar
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float3 hvd_ggx_sample(const float2 &samples, const float inv_alpha)
Definition: ggx_common.h:115
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 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 float2 vndf_ggx_smith_invert(const Vector3f _N, const float2 alpha, const Vector3f _V)
Definition: ggx_common.h:296
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE Vector3f microfacet(const Vector3f V, const Vector3f L, const Vector3f N, const float inv_eta)
Definition: ggx_common.h:50
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float3 vndf_ggx_smith_sample(const float2 samples, const float2 alpha, const Vector3f _V)
Definition: ggx_common.h:265
Define a vector_view POD type and plain_view() for std::vector.
Definition: diff.h:38
CUGAR_FORCEINLINE CUGAR_HOST_DEVICE float2 hvd_ggx_invert(const float3 H, const float2 inv_alpha)
Definition: ggx_common.h:238