-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathpolygon_sampling.glsl
More file actions
883 lines (822 loc) · 40.7 KB
/
polygon_sampling.glsl
File metadata and controls
883 lines (822 loc) · 40.7 KB
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
// Copyright (C) 2021, Christoph Peters, Karlsruhe Institute of Technology
//
// This source code file is licensed under both the three-clause BSD license
// and the GPLv3. You may select, at your option, one of the two licenses. The
// corresponding license headers follow:
//
//
// Three-clause BSD license:
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//
// GPLv3:
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "math_constants.glsl"
/*! This structure carries intermediate results that only need to be computed
once per polygon and shading point to take samples proportional to solid
angle. Sampling is performed by subdividing the convex polygon into
triangles as triangle fan around vertex 0 and then using our variant of
Arvo's method.*/
struct solid_angle_polygon_t {
//! The number of vertices that form the polygon
uint vertex_count;
//! Normalized direction vectors from the shading point to each vertex
vec3 vertex_dirs[MAX_POLYGON_VERTEX_COUNT];
/*! A few intermediate quantities about the triangle consisting of vertices
i + 1, 0, and i + 2. If the three vertices are v0, v1, v2, the entries
are determinant(mat3(v0, v1, v2)), dot(v0 + v1, v2) and
1.0f + dot(v0, v1).*/
vec3 triangle_parameters[MAX_POLYGON_VERTEX_COUNT - 2];
//! At index i, this array holds the solid angle of the triangle fan formed
//! by vertices 0 to i + 2.
float fan_solid_angles[MAX_POLYGON_VERTEX_COUNT - 2];
//! The total solid angle of the polygon
float solid_angle;
};
/*! A piecewise polynomial approximation to positive_atan(y). The maximal
absolute error is 1.16e-05f. At least on Turing GPUs, it is faster but also
significantly less accurate. The proper atan has at most 2 ulps of error
there.*/
float fast_positive_atan(float y) {
float rx;
float ry;
float rz;
rx = (abs(y) > 1.0f) ? (1.0f / abs(y)) : abs(y);
ry = rx * rx;
rz = fma(ry, 0.02083509974181652f, -0.08513300120830536);
rz = fma(ry, rz, 0.18014100193977356f);
rz = fma(ry, rz, -0.3302994966506958f);
ry = fma(ry, rz, 0.9998660087585449f);
rz = fma(-2.0f * ry, rx, M_HALF_PI);
rz = (abs(y) > 1.0f) ? rz : 0.0f;
rx = fma(rx, ry, rz);
return (y < 0.0f) ? (M_PI - rx) : rx;
}
/*! Returns an angle between 0 and M_PI such that tan(angle) == tangent. In
other words, it is a version of atan() that is offset to be non-negative.
Note that it may be switched to an approximate mode by the
USE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING flag.*/
float positive_atan(float tangent) {
#ifdef USE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING
return fast_positive_atan(tangent);
#else
float offset = (tangent < 0.0f) ? M_PI : 0.0f;
return atan(tangent) + offset;
#endif
}
/*! Prepares all intermediate values to sample a triangle fan around vertex 0
(e.g. a convex polygon) proportional to solid angle using our method.
\param vertex_count Number of vertices forming the polygon.
\param vertices List of vertex locations.
\param shading_position The location of the shading point.
\return Input for sample_solid_angle_polygon().*/
solid_angle_polygon_t prepare_solid_angle_polygon_sampling(uint vertex_count, vec3 vertices[MAX_POLYGON_VERTEX_COUNT], vec3 shading_position) {
solid_angle_polygon_t polygon;
polygon.vertex_count = vertex_count;
// Normalize vertex directions
[[unroll]]
for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) {
polygon.vertex_dirs[i] = normalize(vertices[i] - shading_position);
}
// Prepare a Householder transform that maps vertex 0 onto (+/-1, 0, 0). We
// only store the yz-components of that Householder vector and a factor of
// 2.0f / sqrt(abs(polygon.vertex_dirs[0].x) + 1.0f) is pulled in there to
// save on multiplications later. This approach is necessary to avoid
// numerical instabilities in determinant computation below.
float householder_sign = (polygon.vertex_dirs[0].x > 0.0f) ? -1.0f : 1.0f;
vec2 householder_yz = polygon.vertex_dirs[0].yz * (1.0f / (abs(polygon.vertex_dirs[0].x) + 1.0f));
// Compute solid angles and prepare sampling
polygon.solid_angle = 0.0f;
float previous_dot_1_2 = dot(polygon.vertex_dirs[0], polygon.vertex_dirs[1]);
[[unroll]]
for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 2; ++i) {
if (i >= 1 && i + 2 >= vertex_count) break;
// We look at one triangle of the triangle fan at a time
vec3 vertices[3] = {
polygon.vertex_dirs[i + 1],
polygon.vertex_dirs[0],
polygon.vertex_dirs[i + 2]};
float dot_0_1 = previous_dot_1_2;
float dot_0_2 = dot(vertices[0], vertices[2]);
float dot_1_2 = dot(vertices[1], vertices[2]);
previous_dot_1_2 = dot_1_2;
// Compute the bottom right minor of vertices after application of the
// Householder transform
float dot_householder_0 = fma(-householder_sign, vertices[0].x, dot_0_1);
float dot_householder_2 = fma(-householder_sign, vertices[2].x, dot_1_2);
mat2 bottom_right_minor = mat2(
fma(vec2(-dot_householder_0), householder_yz, vertices[0].yz),
fma(vec2(-dot_householder_2), householder_yz, vertices[2].yz));
// The absolute value of the determinant of vertices equals the 2x2
// determinant because the Householder transform turns the first column
// into (+/-1, 0, 0)
float simplex_volume = abs(determinant(bottom_right_minor));
// Compute the solid angle of the triangle using a formula proposed by:
// A. Van Oosterom and J. Strackee, 1983, The Solid Angle of a
// Plane Triangle, IEEE Transactions on Biomedical Engineering 30:2
// https://doi.org/10.1109/TBME.1983.325207
float dot_0_2_plus_1_2 = dot_0_2 + dot_1_2;
float one_plus_dot_0_1 = 1.0f + dot_0_1;
float tangent = simplex_volume / (one_plus_dot_0_1 + dot_0_2_plus_1_2);
float triangle_solid_angle = 2.0f * positive_atan(tangent);
polygon.solid_angle += triangle_solid_angle;
polygon.fan_solid_angles[i] = polygon.solid_angle;
// Some intermediate results from above help us with sampling
polygon.triangle_parameters[i] = vec3(simplex_volume, dot_0_2_plus_1_2, one_plus_dot_0_1);
}
return polygon;
}
/*! An implementation of mix() using two fused-multiply add instructions. Used
because the native mix() implementation had stability issues in a few
spots. Credit to Fabian Giessen's blog, see:
https://fgiesen.wordpress.com/2012/08/15/linear-interpolation-past-present-and-future/
*/
float mix_fma(float x, float y, float a) {
return fma(a, y, fma(-a, x, x));
}
/*! Given the output of prepare_solid_angle_polygon_sampling(), this function
maps the given random numbers in the range from 0 to 1 to a normalized
direction vector providing a sample of the solid angle of the polygon in
the original space (used for arguments of
prepare_solid_angle_polygon_sampling()). Samples are distributed in
proportion to solid angle assuming uniform inputs.*/
vec3 sample_solid_angle_polygon(solid_angle_polygon_t polygon, vec2 random_numbers) {
// Decide which triangle needs to be sampled
float target_solid_angle = polygon.solid_angle * random_numbers[0];
float subtriangle_solid_angle = target_solid_angle;
vec3 parameters = polygon.triangle_parameters[0];
vec3 vertices[3] = {
polygon.vertex_dirs[1], polygon.vertex_dirs[0], polygon.vertex_dirs[2]
};
[[unroll]]
for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 3; ++i) {
if (i + 3 >= polygon.vertex_count || polygon.fan_solid_angles[i] >= target_solid_angle) break;
subtriangle_solid_angle = target_solid_angle - polygon.fan_solid_angles[i];
vertices[0] = polygon.vertex_dirs[i + 2];
vertices[2] = polygon.vertex_dirs[i + 3];
parameters = polygon.triangle_parameters[i + 1];
}
// Construct a new vertex 2 on the arc between vertices 0 and 2 such that
// the resulting triangle has solid angle subtriangle_solid_angle
vec2 cos_sin = vec2(cos(0.5f * subtriangle_solid_angle), sin(0.5f * subtriangle_solid_angle));
vec3 offset = vertices[0] * (parameters[0] * cos_sin.x - parameters[1] * cos_sin.y) + vertices[2] * (parameters[2] * cos_sin.y);
vec3 new_vertex_2 = fma(2.0f * vec3(dot(vertices[0], offset) / dot(offset, offset)), offset, -vertices[0]);
// Now sample the line between vertex 1 and the newly created vertex 2
float s2 = dot(vertices[1], new_vertex_2);
float s = mix_fma(1.0f, s2, random_numbers[1]);
float denominator = fma(-s2, s2, 1.0f);
float t_normed = sqrt(fma(-s, s, 1.0f) / denominator);
// s2 may exceed one due to rounding error. random_numbers[1] is the
// limit of t_normed for s2 -> 1.
t_normed = (denominator > 0.0f) ? t_normed : random_numbers[1];
return fma(-t_normed, s2, s) * vertices[1] + t_normed * new_vertex_2;
}
/*! This structure carries intermediate results that only need to be computed
once per polygon and shading point to take samples proportional to
projected solid angle.*/
struct projected_solid_angle_polygon_t {
//! The number of vertices that form the polygon
uint vertex_count;
/*! The x- and y-coordinates of each polygon vertex in a coordinate system
where the normal is the z-axis. The vertices are sorted
counterclockwise.*/
vec2 vertices[MAX_POLYGON_VERTEX_COUNT];
/*! For each vertex in vertices, this vector describes the ellipse for the
next edge in counterclockwise direction. The last entry is meaningless,
except in the central case. For vertex 0, it holds the outer ellipse.
\see ellipse_from_edge() */
vec2 ellipses[MAX_POLYGON_VERTEX_COUNT];
//! The inner ellipse adjacent to vertex 0. If the x-component is positive,
//! the central case is present.
vec2 inner_ellipse_0;
/*! At index i, this array holds the projected solid angle of the polygon
in the sector between (sorted) vertices i and (i + 1) % vertex_count
In the central case, entry vertex_count - 1 is meaningful, otherwise
not.*/
float sector_projected_solid_angles[MAX_POLYGON_VERTEX_COUNT];
//! The total projected solid angle of the polygon
float projected_solid_angle;
};
//! Computes a * b - c * d with at most 1.5 ulps of error in the result. See
//! https://pharr.org/matt/blog/2019/11/03/difference-of-floats.html or
//! Claude-Pierre Jeannerod, Nicolas Louvet and Jean-Michel Muller, 2013,
//! Further analysis of Kahan's algorithm for the accurate computation of 2x2
//! determinants, AMS Mathematics of Computation 82:284,
//! https://doi.org/10.1090/S0025-5718-2013-02679-8
float kahan(float a, float b, float c, float d) {
// Uncomment the line below to improve efficiency but reduce accuracy
// return a * b - c * d;
float cd = c * d;
float error = fma(c, d, -cd);
float result = fma(a, b, -cd);
return result - error;
}
//! Implements a cross product using Kahan's algorithm for every single entry,
//! i.e. the error in each output entry is at most 1.5 ulps
vec3 cross_stable(vec3 lhs, vec3 rhs) {
return vec3(
kahan(lhs.y, rhs.z, lhs.z, rhs.y),
kahan(lhs.z, rhs.x, lhs.x, rhs.z),
kahan(lhs.x, rhs.y, lhs.y, rhs.x)
);
}
//! \return The given vector, rotated 90 degrees counterclockwise around the
//! origin
vec2 rotate_90(vec2 input_vector) {
return vec2(-input_vector.y, input_vector.x);
}
//! \return true iff the given ellipse is marked as inner ellipse, i.e. iff the
//! spherical polygon that is bounded by it is further away from the zenith
//! than this ellipse.
bool is_inner_ellipse(vec2 ellipse) {
// If the implementation below causes you trouble, e.g. because you want to
// port to a different language, you may replace it by the commented line
// below but it will lead to seldom artifacts when ellipse.x == 0.0f.
// return ellipse.x < 0.0f;
// Extract the sign bit from ellipse.x (to be able to tell apart +0 and -0)
return (floatBitsToUint(ellipse.x) & 0x80000000) != 0;
}
//! \return true iff the given polygon contains the zenith (also known as
//! normal vector).
bool is_central_case(projected_solid_angle_polygon_t polygon) {
return polygon.inner_ellipse_0.x > 0.0f;
}
/*! Takes the great circle for the plane through the origin and the given two
points and constructs an ellipse for its projection to the xy-plane.
\return A vector ellipse such that a point is on the ellipse if and only if
dot(ellipse, point) * dot(ellipse, point) + dot(point, point) == 1.0f.
In other words, it is a normal vector of the great circle in half-
vector space. The sign bit of x encodes whether the edge runs clockwise
from vertex_0 to vertex_1 (inner ellipse) or not.
\see is_inner_ellipse() */
vec2 ellipse_from_edge(vec3 vertex_0, vec3 vertex_1) {
vec3 normal = cross_stable(vertex_0, vertex_1);
float scaling = 1.0f / normal.z;
scaling = is_inner_ellipse(normal.xy) ? -scaling : scaling;
vec2 ellipse = normal.xy * scaling;
// By convention, degenerate ellipses are outer ellipses, i.e. the first
// component is infinite
ellipse.x = (normal.z != 0.0f) ? ellipse.x : M_INFINITY;
return ellipse;
}
//! Transforms the given point using the matrix that characterizes the given
//! ellipse (as produced by ellipse_from_edge()). To be precise, this matrix is
//! identity + outerProduct(ellipse, ellipse).
vec2 ellipse_transform(vec2 ellipse, vec2 point) {
return fma(vec2(dot(ellipse, point)), ellipse, point);
}
//! Given an ellipse in the format produced by ellipse_from_edge(), this
//! function returns the determinant of the matrix characterizing this
//! ellipse.
float get_ellipse_det(vec2 ellipse) {
return fma(ellipse.x, ellipse.x, fma(ellipse.y, ellipse.y, 1.0f));
}
//! Returns the reciprocal square root of the ellipse determinant produced by
//! get_ellipse_det().
float get_ellipse_rsqrt_det(vec2 ellipse) {
return inversesqrt(get_ellipse_det(ellipse));
}
//! \return Reciprocal square of get_ellipse_direction_factor(ellipse, dir)
float get_ellipse_direction_factor_rsq(vec2 ellipse, vec2 dir) {
float ellipse_dot_dir = dot(ellipse, dir);
float dir_dot_dir = dot(dir, dir);
return fma(ellipse_dot_dir, ellipse_dot_dir, dir_dot_dir);
}
/*! Computes a factor by which a direction vector has to be multiplied to
obtain a point on the given ellipse.
\param ellipse An ellipse as produced by ellipse_from_edge().
\param dir The direction vector to be scaled onto the ellipse.
\return get_ellipse_direction_factor(ellipse, dir) * dir is a point on
the ellipse.*/
float get_ellipse_direction_factor(vec2 ellipse, vec2 dir) {
return inversesqrt(get_ellipse_direction_factor_rsq(ellipse, dir));
}
//! Like get_ellipse_direction_factor() but assumes that the given direction is
//! normalized. Faster.
float get_ellipse_normalized_direction_factor(vec2 ellipse, vec2 normalized_dir) {
float ellipse_dot_dir = dot(ellipse, normalized_dir);
return inversesqrt(fma(ellipse_dot_dir, ellipse_dot_dir, 1.0f));
}
//! Helper for get_area_between_ellipses_in_sector() and
//! sample_sector_between_ellipses()
float get_area_between_ellipses_in_sector_from_tangents(float inner_rsqrt_det, float inner_tangent, float outer_rsqrt_det, float outer_tangent) {
float inner_area = inner_rsqrt_det * positive_atan(inner_tangent);
float result = fma(outer_rsqrt_det, positive_atan(outer_tangent), -inner_area);
// Sort out NaNs and negative results
return (result > 0.0f) ? (0.5f * result) : 0.0f;
}
/*! Returns the signed area between the given outer and inner ellipses within
the sector enclosed by dir_0 and dir_1. Besides ellipses as produced by
ellipse_from_edge(), you also have to pass output of
get_ellipse_rsqrt_det(). Faster than calling get_ellipse_area_in_sector()
twice.*/
float get_area_between_ellipses_in_sector(vec2 inner_ellipse, float inner_rsqrt_det, vec2 outer_ellipse, float outer_rsqrt_det, vec2 dir_0, vec2 dir_1) {
float det_dirs = max(+0.0f, dot(dir_1, rotate_90(dir_0)));
float inner_dot = inner_rsqrt_det * dot(dir_0, ellipse_transform(inner_ellipse, dir_1));
float outer_dot = outer_rsqrt_det * dot(dir_0, ellipse_transform(outer_ellipse, dir_1));
return get_area_between_ellipses_in_sector_from_tangents(
inner_rsqrt_det, det_dirs / inner_dot,
outer_rsqrt_det, det_dirs / outer_dot);
}
/*! Computes the area for the intersection of the given ellipse and the sector
between the given two directions (going counterclockwise from dir_0 to
dir_1 for at most 180 degrees). The scaling of the directions is
irrelevant.
\see ellipse_from_edge() */
float get_ellipse_area_in_sector(vec2 ellipse, vec2 dir_0, vec2 dir_1) {
float ellipse_rsqrt_det = get_ellipse_rsqrt_det(ellipse);
float det_dirs = max(+0.0f, dot(dir_1, rotate_90(dir_0)));
float ellipse_dot = ellipse_rsqrt_det * dot(dir_0, ellipse_transform(ellipse, dir_1));
float area = 0.5f * ellipse_rsqrt_det * positive_atan(det_dirs / ellipse_dot);
// For degenerate ellipses, the result may be NaN but must be 0.0f
return (ellipse_rsqrt_det > 0.0f) ? area : 0.0f;
}
/*! Swaps vertices lhs and rhs (along with corresponding ellipses) of the given
polygon if the shorter path from lhs to rhs is clockwise. If the vertices
have identical directions in the xy-plane, vertices with degenerate
ellipses come first.
\note To avoid costly register spilling, lhs and rhs must be compile time
constants.*/
void compare_and_swap(inout projected_solid_angle_polygon_t polygon, uint lhs, uint rhs) {
vec2 lhs_copy = polygon.vertices[lhs];
// This line is designed to agree with the implementation of cross_stable
// for the z-coordinate, which determines if ellipses are inner or outer
float normal_z = kahan(lhs_copy.x, -polygon.vertices[rhs].y, lhs_copy.y, -polygon.vertices[rhs].x);
// Tie breaker: If both vertices are at the same angle (i.e. on a common
// great circle through the zenith), the one with the degenerate ellipse
// comes first
bool swap = (normal_z == 0.0f) ? isinf(polygon.ellipses[rhs].x) : (normal_z > 0.0f);
polygon.vertices[lhs] = swap ? polygon.vertices[rhs] : lhs_copy;
polygon.vertices[rhs] = swap ? lhs_copy : polygon.vertices[rhs];
lhs_copy = polygon.ellipses[lhs];
polygon.ellipses[lhs] = swap ? polygon.ellipses[rhs] : lhs_copy;
polygon.ellipses[rhs] = swap ? lhs_copy : polygon.ellipses[rhs];
}
//! Sorts the vertices of the given convex polygon counterclockwise using a
//! special sorting network. For non-convex polygons, the method may fail.
void sort_convex_polygon_vertices(inout projected_solid_angle_polygon_t polygon) {
if (polygon.vertex_count == 3) {
compare_and_swap(polygon, 1, 2);
}
#if MAX_POLYGON_VERTEX_COUNT >= 4
else if (polygon.vertex_count == 4) {
compare_and_swap(polygon, 1, 3);
}
#endif
#if MAX_POLYGON_VERTEX_COUNT >= 5
else if (polygon.vertex_count == 5) {
compare_and_swap(polygon, 2, 4);
compare_and_swap(polygon, 1, 3);
compare_and_swap(polygon, 1, 2);
compare_and_swap(polygon, 0, 3);
compare_and_swap(polygon, 3, 4);
}
#endif
#if MAX_POLYGON_VERTEX_COUNT >= 6
else if (polygon.vertex_count == 6) {
compare_and_swap(polygon, 3, 5);
compare_and_swap(polygon, 2, 4);
compare_and_swap(polygon, 1, 5);
compare_and_swap(polygon, 0, 4);
compare_and_swap(polygon, 4, 5);
compare_and_swap(polygon, 1, 3);
}
#endif
#if MAX_POLYGON_VERTEX_COUNT >= 7
else if (polygon.vertex_count == 7) {
compare_and_swap(polygon, 2, 5);
compare_and_swap(polygon, 1, 6);
compare_and_swap(polygon, 5, 6);
compare_and_swap(polygon, 3, 4);
compare_and_swap(polygon, 0, 4);
compare_and_swap(polygon, 4, 6);
compare_and_swap(polygon, 1, 3);
compare_and_swap(polygon, 3, 5);
compare_and_swap(polygon, 4, 5);
}
#endif
#if MAX_POLYGON_VERTEX_COUNT >= 8
else if (polygon.vertex_count == 8) {
compare_and_swap(polygon, 2, 6);
compare_and_swap(polygon, 3, 7);
compare_and_swap(polygon, 1, 5);
compare_and_swap(polygon, 0, 4);
compare_and_swap(polygon, 4, 6);
compare_and_swap(polygon, 5, 7);
compare_and_swap(polygon, 6, 7);
compare_and_swap(polygon, 4, 5);
compare_and_swap(polygon, 1, 3);
}
#endif
// This comparison is shared by all sorting networks
compare_and_swap(polygon, 0, 2);
#if MAX_POLYGON_VERTEX_COUNT >= 4
if (polygon.vertex_count >= 4) {
// This comparison is shared by all sorting networks except the one for
// triangles
compare_and_swap(polygon, 2, 3);
}
#endif
// This comparison is shared by all sorting networks
compare_and_swap(polygon, 0, 1);
}
/*! Prepares all intermediate values to sample a convex polygon proportional to
projected solid angle.
\param vertex_count Number of vertices forming the polygon (at least 3).
\param vertices List of vertex locations in a coordinate system where the
shading position is the origin and the normal is the z-axis. The
polygon should be already clipped against the plane z=0. If
vertex_count < MAX_POLYGON_VERTEX_COUNT, the first vertex has to be
repeated at vertex_count. They need not be normalized but if you
encounter issues with under- or overflow (e.g. NaN or INF outputs),
normalization may help. The polygon must be convex, and the winding of
the vertices as seen from the origin must be clockwise. No three
vertices should be collinear.
\return Intermediate values for sampling.*/
projected_solid_angle_polygon_t prepare_projected_solid_angle_polygon_sampling(uint vertex_count, vec3 vertices[MAX_POLYGON_VERTEX_COUNT]) {
projected_solid_angle_polygon_t polygon;
// Copy vertices and assign ellipses
polygon.vertex_count = vertex_count;
polygon.inner_ellipse_0 = vec2(1.0f, 0.0f);
polygon.vertices[0] = vertices[0].xy;
polygon.ellipses[0] = ellipse_from_edge(vertices[0], vertices[1]);
vec2 previous_ellipse = polygon.ellipses[0];
[[unroll]]
for (uint i = 1; i != MAX_POLYGON_VERTEX_COUNT; ++i) {
polygon.vertices[i] = vertices[i].xy;
if (i > 2 && i == polygon.vertex_count) break;
vec2 ellipse = ellipse_from_edge(vertices[i], vertices[(i + 1) % MAX_POLYGON_VERTEX_COUNT]);
bool ellipse_inner = is_inner_ellipse(ellipse);
// If the edge is an inner edge, the order is going to flip
polygon.ellipses[i] = ellipse_inner ? previous_ellipse : ellipse;
// In doing so, we drop one ellipse, unless we store it explicitly
polygon.inner_ellipse_0 = (is_inner_ellipse(previous_ellipse) && !ellipse_inner) ? previous_ellipse : polygon.inner_ellipse_0;
previous_ellipse = ellipse;
}
// Same thing for the first vertex (i.e. here we close the loop)
vec2 ellipse = polygon.ellipses[0];
bool ellipse_inner = is_inner_ellipse(ellipse);
polygon.ellipses[0] = ellipse_inner ? previous_ellipse : ellipse;
polygon.inner_ellipse_0 = (is_inner_ellipse(previous_ellipse) && !ellipse_inner) ? previous_ellipse : polygon.inner_ellipse_0;
// Compute projected solid angles per sector and in total
polygon.projected_solid_angle = 0.0f;
if (is_central_case(polygon)) {
// In the central case, we have polygon.vertex_count sectors, each
// bounded by a single ellipse
[[unroll]]
for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) {
if (i > 2 && i == polygon.vertex_count) break;
polygon.sector_projected_solid_angles[i] = get_ellipse_area_in_sector(polygon.ellipses[i], polygon.vertices[i], polygon.vertices[(i + 1) % MAX_POLYGON_VERTEX_COUNT]);
polygon.projected_solid_angle += polygon.sector_projected_solid_angles[i];
}
}
else {
// Sort vertices counter clockwise
sort_convex_polygon_vertices(polygon);
// There are polygon.vertex_count - 1 sectors, each bounded by an inner
// and an outer ellipse
vec2 inner_ellipse = polygon.inner_ellipse_0;
float inner_rsqrt_det = get_ellipse_rsqrt_det(inner_ellipse);
vec2 outer_ellipse;
float outer_rsqrt_det;
[[unroll]]
for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) {
if (i > 1 && i + 1 == polygon.vertex_count) break;
vec2 vertex_ellipse = polygon.ellipses[i];
bool vertex_inner = is_inner_ellipse(vertex_ellipse);
float vertex_rsqrt_det = get_ellipse_rsqrt_det(vertex_ellipse);
if (i == 0) {
outer_ellipse = vertex_ellipse;
outer_rsqrt_det = vertex_rsqrt_det;
}
else {
inner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse;
inner_rsqrt_det = vertex_inner ? vertex_rsqrt_det : inner_rsqrt_det;
outer_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse;
outer_rsqrt_det = vertex_inner ? outer_rsqrt_det : vertex_rsqrt_det;
}
polygon.sector_projected_solid_angles[i] = get_area_between_ellipses_in_sector(
inner_ellipse, inner_rsqrt_det, outer_ellipse, outer_rsqrt_det, polygon.vertices[i], polygon.vertices[i + 1]);
polygon.projected_solid_angle += polygon.sector_projected_solid_angles[i];
}
}
return polygon;
}
/*! \return A scalar multiple of rhs that is not too far from being normalized.
For the result, length() returns something between sqrt(2.0f) and 8.0f.
The sign gets flipped such that the dot product of semi_circle and the
result is non-negative.
\note Introduces less latency than normalize() and does not use special
functions. Useful to avoid under- and overflow when working with
homogeneous coordinates. The result is undefined if rhs is zero.*/
vec2 normalize_approx_and_flip(vec2 rhs, vec2 semi_circle) {
float scaling = abs(rhs.x) + abs(rhs.y);
// By flipping each bit on the exponent E, we turn it into 1 - E, which is
// close enough to a reciprocal.
scaling = uintBitsToFloat(floatBitsToUint(scaling) ^ 0x7F800000u);
// If the line above causes you any sort of trouble (e.g. because you want
// to port the code to another language or you are doing differentiable
// rendering), just use this one instead:
// scaling = 1.0f / scaling;
// Flip the sign as needed
scaling = (dot(rhs, semi_circle) >= 0.0f) ? scaling : -scaling;
return scaling * rhs;
}
/*! Returns a solution to the given homogeneous quadratic equation, i.e. a
non-zero vector root such that dot(root, quadratic * root) == 0.0f. The
returned root depends continuously on quadratic. Pass -quadratic if you
want the other root.
\note The implementation is as proposed by Blinn, except that we do not
have a special case for quadratic[0][1] + quadratic[1][0] == 0.0f. Unlike
the standard quadratic formula, it allows us to postpone a division and is
stable in all cases.
James F. Blinn 2006, How to Solve a Quadratic Equation, Part 2, IEEE
Computer Graphics and Applications 26:2 https://doi.org/10.1109/MCG.2006.35
*/
vec2 solve_homogeneous_quadratic(mat2 quadratic) {
float coeff_xy = 0.5f * (quadratic[0][1] + quadratic[1][0]);
float sqrt_discriminant = sqrt(max(0.0f, coeff_xy * coeff_xy - quadratic[0][0] * quadratic[1][1]));
float scaled_root = abs(coeff_xy) + sqrt_discriminant;
return (coeff_xy >= 0.0f) ? vec2(scaled_root, -quadratic[0][0]) : vec2(quadratic[1][1], scaled_root);
}
/*! Generates a sample between two ellipses and in a specified sector. The
sample is distributed uniformly with respect to the area measure.
\param random_numbers A pair of independent uniform random numbers on [0,1]
\param target_area random_numbers[0] multiplied by the projected solid
angle of the area to be sampled.
\param inner_ellipse, outer_ellipse The inner and outer ellipse, as
produced by ellipse_from_edge().
\param dir_0, dir_1 Two direction vectors bounding the sector. They need
not be normalized.
\param iteration_count The number of iterations to perform. Lower values
trade speed for bias. Two iterations give practically no bias.
\return The sample in Cartesian coordinates.*/
vec2 sample_sector_between_ellipses(vec2 random_numbers, float target_area, vec2 inner_ellipse, vec2 outer_ellipse, vec2 dir_0, vec2 dir_1, uint iteration_count) {
// For the initialization, split the sector in half
vec2 quad_dirs[3];
quad_dirs[0] = normalize(dir_0);
quad_dirs[2] = normalize(dir_1);
quad_dirs[1] = quad_dirs[0] + quad_dirs[2];
// Compute where these lines intersect the ellipses. The six intersection
// points define two adjacent quads.
float normalization_factor[2][3] = {
{
get_ellipse_normalized_direction_factor(inner_ellipse, quad_dirs[0]),
get_ellipse_direction_factor(inner_ellipse, quad_dirs[1]),
get_ellipse_normalized_direction_factor(inner_ellipse, quad_dirs[2])
},
{
get_ellipse_normalized_direction_factor(outer_ellipse, quad_dirs[0]),
get_ellipse_direction_factor(outer_ellipse, quad_dirs[1]),
get_ellipse_normalized_direction_factor(outer_ellipse, quad_dirs[2])
}
};
// Compute the relative size of the areas inside these quads
float sector_areas[2] = {
normalization_factor[1][0] * normalization_factor[1][1] - normalization_factor[0][0] * normalization_factor[0][1],
normalization_factor[1][1] * normalization_factor[1][2] - normalization_factor[0][1] * normalization_factor[0][2]
};
// Now pick which of the two quads should be sampled for the
// initialization. If it is not the second, we move data such that the
// relevant array indices are 1 and 2 anyway.
float target_quad_area = mix_fma(-sector_areas[0], sector_areas[1], random_numbers[0]);
quad_dirs[2] = (target_quad_area <= 0.0f) ? quad_dirs[0] : quad_dirs[2];
normalization_factor[0][2] = (target_quad_area <= 0.0f) ? normalization_factor[0][0] : normalization_factor[0][2];
normalization_factor[1][2] = (target_quad_area <= 0.0f) ? normalization_factor[1][0] : normalization_factor[1][2];
target_quad_area += (target_quad_area <= 0.0f) ? sector_areas[0] : -sector_areas[1];
// We have been a bit lazy about area computation before but now we need
// all the factors (except for a factor of 0.5 that cancels with a 2 later)
target_quad_area *= abs(determinant(mat2(quad_dirs[1], quad_dirs[2])));
// Construct normal vectors for the inner and outer edge of the selected
// quad. We construct the normal like a half vector (i.e. by addition)
// because it is less prone to cancellation than an approach using the edge
// direction (i.e. subtraction of sometimes nearly identical vectors)
vec2 quad_normals[2] = {
quad_dirs[1] * normalization_factor[0][1] + quad_dirs[2] * normalization_factor[0][2],
quad_dirs[1] * normalization_factor[1][1] + quad_dirs[2] * normalization_factor[1][2]
};
quad_normals[0] = ellipse_transform(inner_ellipse, quad_normals[0]);
quad_normals[1] = ellipse_transform(outer_ellipse, quad_normals[1]);
// Construct complete line equations
float quad_offsets[2] = {
dot(quad_normals[0], quad_dirs[1]) * normalization_factor[0][1],
dot(quad_normals[1], quad_dirs[1]) * normalization_factor[1][1]
};
// Now sample the direction within the selected quad by constructing a
// quadratic equation. This is the initialization for the iteration.
mat2 quadratic = outerProduct((quad_offsets[1] * normalization_factor[1][2]) * rotate_90(quad_dirs[2]), quad_normals[0]);
quadratic -= outerProduct((quad_offsets[0] * normalization_factor[0][2]) * rotate_90(quad_dirs[2]) + target_quad_area * quad_normals[0], quad_normals[1]);
vec2 current_dir = solve_homogeneous_quadratic(quadratic);
#ifndef USE_BIASED_PROJECTED_SOLID_ANGLE_SAMPLING
// For boundary values, the initialization is perfect but the iteration may
// be unstable, so we disable it
float acceptable_error = 1.0e-5f;
iteration_count = (abs(random_numbers.x - 0.5f) <= 0.5f - acceptable_error) ? iteration_count : 0;
// Now refine this initialization iteratively
float inner_rsqrt_det = get_ellipse_rsqrt_det(inner_ellipse);
float outer_rsqrt_det = get_ellipse_rsqrt_det(outer_ellipse);
[[dont_unroll]]
for (uint i = 0; i != iteration_count; ++i) {
// Avoid under- or overflow and flip the sign so that the clamping to
// zero below makes sense
current_dir = normalize_approx_and_flip(current_dir, quad_dirs[1]);
// Transform current_dir using both ellipses
vec2 inner_dir = ellipse_transform(inner_ellipse, current_dir);
vec2 outer_dir = ellipse_transform(outer_ellipse, current_dir);
// Evaluate the objective function (reusing inner_dir and outer_dir)
float det_dirs = max(+0.0f, dot(current_dir, rotate_90(quad_dirs[0])));
float error = target_area - get_area_between_ellipses_in_sector_from_tangents(
inner_rsqrt_det, det_dirs / (inner_rsqrt_det * dot(quad_dirs[0], inner_dir)),
outer_rsqrt_det, det_dirs / (outer_rsqrt_det * dot(quad_dirs[0], outer_dir)));
// Construct a homogeneous quadratic whose solutions include the next
// step of the iteration
quadratic = outerProduct(inner_dir - outer_dir, rotate_90(current_dir)) - outerProduct((2.0f * error) * inner_dir, outer_dir);
current_dir = solve_homogeneous_quadratic(quadratic);
}
#endif
// The halved sector is at most 90 degrees large, so the dot product with
// the half vector has to be positive
current_dir = (dot(current_dir, quad_dirs[1]) >= 0.0f) ? current_dir : -current_dir;
// Sample a squared radius uniformly between the two ellipses
float inner_factor = 1.0f / get_ellipse_direction_factor_rsq(inner_ellipse, current_dir);
float outer_factor = 1.0f / get_ellipse_direction_factor_rsq(outer_ellipse, current_dir);
current_dir *= sqrt(mix_fma(inner_factor, outer_factor, random_numbers[1]));
return current_dir;
}
/*! Produces a sample in the solid angle of the given polygon. If the random
numbers are uniform in [0,1]^2, the sample is uniform in the projected
solid angle of the polygon.
\param polygon Output of prepare_projected_solid_angle_polygon_sampling().
\param random_numbers A uniform point in [0,1]^2.
\return A sample on the upper hemisphere (i.e. z>=0) in Cartesian
coordinates.*/
vec3 sample_projected_solid_angle_polygon(projected_solid_angle_polygon_t polygon, vec2 random_numbers) {
float target_projected_solid_angle = random_numbers[0] * polygon.projected_solid_angle;
// Distinguish between the central case
vec3 sampled_dir;
vec2 outer_ellipse;
vec2 dir_0;
if (is_central_case(polygon)) {
// Select a sector and copy the relevant attributes
[[unroll]]
for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT; ++i) {
if (i > 0) {
target_projected_solid_angle -= polygon.sector_projected_solid_angles[i - 1];
}
outer_ellipse = polygon.ellipses[i];
dir_0 = polygon.vertices[i];
if ((i >= 2 && i + 1 == polygon.vertex_count) || target_projected_solid_angle < polygon.sector_projected_solid_angles[i])
break;
}
// Sample a direction within the sector
float sqrt_det = sqrt(get_ellipse_det(outer_ellipse));
float angle = 2.0f * target_projected_solid_angle * sqrt_det;
sampled_dir.xy = (cos(angle) * sqrt_det) * dir_0 + sin(angle) * rotate_90(ellipse_transform(outer_ellipse, dir_0));
// Sample a squared radius uniformly within the ellipse
sampled_dir.xy *= sqrt(random_numbers[1] / get_ellipse_direction_factor_rsq(outer_ellipse, sampled_dir.xy));
}
// And the decentral case
else {
// Select a sector and copy the relevant attributes
float sector_projected_solid_angle;
vec2 inner_ellipse = polygon.inner_ellipse_0;
vec2 dir_1;
[[unroll]]
for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) {
vec2 vertex_ellipse = polygon.ellipses[i];
if (i == 0) {
outer_ellipse = vertex_ellipse;
}
else {
target_projected_solid_angle -= polygon.sector_projected_solid_angles[i - 1];
bool vertex_inner = is_inner_ellipse(vertex_ellipse);
inner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse;
outer_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse;
}
dir_0 = polygon.vertices[i];
dir_1 = polygon.vertices[i + 1];
sector_projected_solid_angle = polygon.sector_projected_solid_angles[i];
if ((i >= 1 && i + 2 == polygon.vertex_count) || target_projected_solid_angle < sector_projected_solid_angle)
break;
}
// Sample it
random_numbers[0] = target_projected_solid_angle / sector_projected_solid_angle;
sampled_dir.xy = sample_sector_between_ellipses(random_numbers, target_projected_solid_angle, inner_ellipse, outer_ellipse, dir_0, dir_1, 2);
}
// Construct the sample
sampled_dir.z = sqrt(max(0.0f, fma(-sampled_dir.x, sampled_dir.x, fma(-sampled_dir.y, sampled_dir.y, 1.0f))));
return sampled_dir;
}
/*! Determines the error of a sample from the projected solid angle of a
polygon due to the iterative procedure.
\param polygon Output of prepare_projected_solid_angle_polygon_sampling().
\param random_numbers The value passed for random_numbers in
sample_projected_solid_angle_polygon().
\param sampled_dir The direction returned by
sample_projected_solid_angle_polygon().
\return The first component is the signed backward error: The difference
between random_number_0 and the random number that would yield
sampled_dir with perfect computation. The second component is the
backward error, multiplied by the projected solid angle of the polygon.
The third component is the signed forward error (at least a first-order
estimate of it): The difference between the exact result of sampling
and the actual result in radians. Note that all of these are subject
to rounding error themselves. In the central case, zero is returned.*/
vec3 compute_projected_solid_angle_polygon_sampling_error(projected_solid_angle_polygon_t polygon, vec2 random_numbers, vec3 sampled_dir) {
float target_projected_solid_angle = random_numbers[0] * polygon.projected_solid_angle;
// In the central case, the sampling procedure is exact except for rounding
// error
if (is_central_case(polygon)) {
return vec3(0.0f);
}
// In the other case, we repeat some computations to find the error
else {
// Select a sector and copy the relevant attributes
float sector_projected_solid_angle;
vec2 outer_ellipse;
vec2 inner_ellipse = polygon.inner_ellipse_0;
vec2 dir_0;
[[unroll]]
for (uint i = 0; i != MAX_POLYGON_VERTEX_COUNT - 1; ++i) {
if ((i > 1 && i + 1 == polygon.vertex_count) || (i > 0 && target_projected_solid_angle < 0.0f))
break;
sector_projected_solid_angle = polygon.sector_projected_solid_angles[i];
target_projected_solid_angle -= sector_projected_solid_angle;
vec2 vertex_ellipse = polygon.ellipses[i];
bool vertex_inner = is_inner_ellipse(vertex_ellipse);
if (i == 0) {
outer_ellipse = vertex_ellipse;
}
else {
inner_ellipse = vertex_inner ? vertex_ellipse : inner_ellipse;
outer_ellipse = vertex_inner ? outer_ellipse : vertex_ellipse;
}
dir_0 = polygon.vertices[i];
}
target_projected_solid_angle += sector_projected_solid_angle;
// Compute the area in the sector from sampled_dir and dir_0 between
// the two ellipses
float sampled_projected_solid_angle = get_area_between_ellipses_in_sector(
inner_ellipse, get_ellipse_rsqrt_det(inner_ellipse),
outer_ellipse, get_ellipse_rsqrt_det(outer_ellipse),
dir_0, sampled_dir.xy);
// Compute error in the projected solid angle
float scaled_backward_error = target_projected_solid_angle - sampled_projected_solid_angle;
float backward_error = scaled_backward_error / polygon.projected_solid_angle;
// Evaluate the derivative of the sampled direction with respect to the
// projected solid angle
mat2 constraint_matrix;
vec2 inner_dir = ellipse_transform(inner_ellipse, sampled_dir.xy);
vec2 outer_dir = ellipse_transform(outer_ellipse, sampled_dir.xy);
float inner_factor = 1.0f / dot(sampled_dir.xy, inner_dir);
float outer_factor = 1.0f / dot(sampled_dir.xy, outer_dir);
constraint_matrix[0] = 0.5f * (inner_factor - outer_factor) * rotate_90(sampled_dir.xy);
constraint_matrix[1] = ((1.0f - random_numbers[1]) / (inner_factor * inner_factor)) * inner_dir;
constraint_matrix[1] += (random_numbers[1] / (outer_factor * outer_factor)) * outer_dir;
constraint_matrix = transpose(constraint_matrix);
vec3 sample_derivative;
sample_derivative.xy = (1.0f / determinant(constraint_matrix)) * vec2(constraint_matrix[1][1], -constraint_matrix[0][1]);
sample_derivative.z = -dot(sampled_dir.xy, sample_derivative.xy) / sampled_dir.z;
// Evaluate the forward error in radians
float forward_error = length(sample_derivative) * scaled_backward_error;
// Return both errors
return vec3(backward_error, scaled_backward_error, forward_error);
}
}