three-gpu-pathtracer 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +886 -886
  3. package/build/index.module.js +6478 -6470
  4. package/build/index.module.js.map +1 -1
  5. package/build/index.umd.cjs +6473 -6465
  6. package/build/index.umd.cjs.map +1 -1
  7. package/package.json +72 -69
  8. package/src/core/DynamicPathTracingSceneGenerator.js +119 -119
  9. package/src/core/MaterialReducer.js +256 -256
  10. package/src/core/PathTracingRenderer.js +275 -275
  11. package/src/core/PathTracingSceneGenerator.js +69 -69
  12. package/src/index.js +39 -39
  13. package/src/materials/AlphaDisplayMaterial.js +48 -48
  14. package/src/materials/AmbientOcclusionMaterial.js +199 -199
  15. package/src/materials/BlendMaterial.js +67 -67
  16. package/src/materials/DenoiseMaterial.js +142 -142
  17. package/src/materials/GraphMaterial.js +243 -243
  18. package/src/materials/LambertPathTracingMaterial.js +285 -285
  19. package/src/materials/MaterialBase.js +56 -56
  20. package/src/materials/PhysicalPathTracingMaterial.js +982 -982
  21. package/src/objects/EquirectCamera.js +13 -13
  22. package/src/objects/PhysicalCamera.js +28 -28
  23. package/src/objects/PhysicalSpotLight.js +14 -14
  24. package/src/objects/ShapedAreaLight.js +12 -12
  25. package/src/shader/shaderBvhAnyHit.js +76 -0
  26. package/src/shader/shaderEnvMapSampling.js +58 -58
  27. package/src/shader/shaderGGXFunctions.js +100 -100
  28. package/src/shader/shaderIridescenceFunctions.js +130 -130
  29. package/src/shader/shaderLayerTexelFetchFunctions.js +25 -25
  30. package/src/shader/shaderLightSampling.js +229 -229
  31. package/src/shader/shaderMaterialSampling.js +506 -498
  32. package/src/shader/shaderRandFunctions.js +57 -57
  33. package/src/shader/shaderSheenFunctions.js +98 -98
  34. package/src/shader/shaderSobolSampling.js +256 -256
  35. package/src/shader/shaderStructs.js +325 -325
  36. package/src/shader/shaderUtils.js +361 -361
  37. package/src/textures/GradientEquirectTexture.js +35 -35
  38. package/src/textures/ProceduralEquirectTexture.js +75 -75
  39. package/src/uniforms/AttributesTextureArray.js +35 -35
  40. package/src/uniforms/EquirectHdrInfoUniform.js +259 -259
  41. package/src/uniforms/FloatAttributeTextureArray.js +169 -169
  42. package/src/uniforms/IESProfilesTexture.js +100 -100
  43. package/src/uniforms/LightsInfoUniformStruct.js +207 -207
  44. package/src/uniforms/MaterialsTexture.js +426 -426
  45. package/src/uniforms/PhysicalCameraUniform.js +36 -36
  46. package/src/uniforms/RenderTarget2DArray.js +97 -97
  47. package/src/uniforms/utils.js +30 -30
  48. package/src/utils/BlurredEnvMapGenerator.js +116 -116
  49. package/src/utils/GeometryPreparationUtils.js +214 -214
  50. package/src/utils/IESLoader.js +325 -325
  51. package/src/utils/SobolNumberMapGenerator.js +80 -80
  52. package/src/utils/UVUnwrapper.js +101 -101
  53. package/src/workers/PathTracingSceneWorker.js +42 -42
@@ -1,361 +1,361 @@
1
- export const shaderUtils = /* glsl */`
2
-
3
- // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume/README.md#attenuation
4
- vec3 transmissionAttenuation( float dist, vec3 attColor, float attDist ) {
5
-
6
- vec3 ot = - log( attColor ) / attDist;
7
- return exp( - ot * dist );
8
-
9
- }
10
-
11
- // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
12
- float schlickFresnel( float cosine, float f0 ) {
13
-
14
- return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
15
-
16
- }
17
-
18
- vec3 schlickFresnel( float cosine, vec3 f0 ) {
19
-
20
- return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
21
-
22
- }
23
-
24
- float dielectricFresnel( float cosThetaI, float eta ) {
25
-
26
- // https://schuttejoe.github.io/post/disneybsdf/
27
- float ni = eta;
28
- float nt = 1.0;
29
-
30
- // Check for total internal reflection
31
- float sinThetaISq = 1.0f - cosThetaI * cosThetaI;
32
- float sinThetaTSq = eta * eta * sinThetaISq;
33
- if( sinThetaTSq >= 1.0 ) {
34
-
35
- return 1.0;
36
-
37
- }
38
-
39
- float sinThetaT = sqrt( sinThetaTSq );
40
-
41
- float cosThetaT = sqrt( max( 0.0, 1.0f - sinThetaT * sinThetaT ) );
42
- float rParallel = ( ( nt * cosThetaI ) - ( ni * cosThetaT ) ) / ( ( nt * cosThetaI ) + ( ni * cosThetaT ) );
43
- float rPerpendicular = ( ( ni * cosThetaI ) - ( nt * cosThetaT ) ) / ( ( ni * cosThetaI ) + ( nt * cosThetaT ) );
44
- return ( rParallel * rParallel + rPerpendicular * rPerpendicular ) / 2.0;
45
-
46
- }
47
-
48
- // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
49
- float iorRatioToF0( float eta ) {
50
-
51
- return pow( ( 1.0 - eta ) / ( 1.0 + eta ), 2.0 );
52
-
53
- }
54
-
55
- // forms a basis with the normal vector as Z
56
- mat3 getBasisFromNormal( vec3 normal ) {
57
-
58
- vec3 other;
59
- if ( abs( normal.x ) > 0.5 ) {
60
-
61
- other = vec3( 0.0, 1.0, 0.0 );
62
-
63
- } else {
64
-
65
- other = vec3( 1.0, 0.0, 0.0 );
66
-
67
- }
68
-
69
- vec3 ortho = normalize( cross( normal, other ) );
70
- vec3 ortho2 = normalize( cross( normal, ortho ) );
71
- return mat3( ortho2, ortho, normal );
72
-
73
- }
74
-
75
- vec3 getHalfVector( vec3 wi, vec3 wo, float eta ) {
76
-
77
- // get the half vector - assuming if the light incident vector is on the other side
78
- // of the that it's transmissive.
79
- vec3 h;
80
- if ( wi.z > 0.0 ) {
81
-
82
- h = normalize( wi + wo );
83
-
84
- } else {
85
-
86
- // Scale by the ior ratio to retrieve the appropriate half vector
87
- // From Section 2.2 on computing the transmission half vector:
88
- // https://blog.selfshadow.com/publications/s2015-shading-course/burley/s2015_pbs_disney_bsdf_notes.pdf
89
- h = normalize( wi + wo * eta );
90
-
91
- }
92
-
93
- h *= sign( h.z );
94
- return h;
95
-
96
- }
97
-
98
- vec3 getHalfVector( vec3 a, vec3 b ) {
99
-
100
- return normalize( a + b );
101
-
102
- }
103
-
104
- // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
105
- // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
106
- // we find a ray like that we ignore it to avoid artifacts.
107
- // This function returns if the direction is on the same side of both planes.
108
- bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
109
-
110
- bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
111
- bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
112
- return aboveSurfaceNormal == aboveGeometryNormal;
113
-
114
- }
115
-
116
- vec3 getHemisphereSample( vec3 n, vec2 uv ) {
117
-
118
- // https://www.rorydriscoll.com/2009/01/07/better-sampling/
119
- // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
120
- float sign = n.z == 0.0 ? 1.0 : sign( n.z );
121
- float a = - 1.0 / ( sign + n.z );
122
- float b = n.x * n.y * a;
123
- vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
124
- vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
125
-
126
- float r = sqrt( uv.x );
127
- float theta = 2.0 * PI * uv.y;
128
- float x = r * cos( theta );
129
- float y = r * sin( theta );
130
- return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
131
-
132
- }
133
-
134
- vec2 sampleTriangle( vec2 a, vec2 b, vec2 c, vec2 r ) {
135
-
136
- // get the edges of the triangle and the diagonal across the
137
- // center of the parallelogram
138
- vec2 e1 = a - b;
139
- vec2 e2 = c - b;
140
- vec2 diag = normalize( e1 + e2 );
141
-
142
- // pick the point in the parallelogram
143
- if ( r.x + r.y > 1.0 ) {
144
-
145
- r = vec2( 1.0 ) - r;
146
-
147
- }
148
-
149
- return e1 * r.x + e2 * r.y;
150
-
151
- }
152
-
153
- vec2 sampleCircle( vec2 uv ) {
154
-
155
- float angle = 2.0 * PI * uv.x;
156
- float radius = sqrt( uv.y );
157
- return vec2( cos( angle ), sin( angle ) ) * radius;
158
-
159
- }
160
-
161
- vec3 sampleSphere( vec2 uv ) {
162
-
163
- float u = ( uv.x - 0.5 ) * 2.0;
164
- float t = uv.y * PI * 2.0;
165
- float f = sqrt( 1.0 - u * u );
166
-
167
- return vec3( f * cos( t ), f * sin( t ), u );
168
-
169
- }
170
-
171
- vec2 sampleRegularNGon( int sides, vec3 uvw ) {
172
-
173
- sides = max( sides, 3 );
174
-
175
- vec3 r = uvw;
176
- float anglePerSegment = 2.0 * PI / float( sides );
177
- float segment = floor( float( sides ) * r.x );
178
-
179
- float angle1 = anglePerSegment * segment;
180
- float angle2 = angle1 + anglePerSegment;
181
- vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
182
- vec2 b = vec2( 0.0, 0.0 );
183
- vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
184
-
185
- return sampleTriangle( a, b, c, r.yz );
186
-
187
- }
188
-
189
- // samples an aperture shape with the given number of sides. 0 means circle
190
- vec2 sampleAperture( int blades, vec3 uvw ) {
191
-
192
- return blades == 0 ?
193
- sampleCircle( uvw.xy ) :
194
- sampleRegularNGon( blades, uvw );
195
-
196
- }
197
-
198
- // ray sampling x and z are swapped to align with expected background view
199
- vec2 equirectDirectionToUv( vec3 direction ) {
200
-
201
- // from Spherical.setFromCartesianCoords
202
- vec2 uv = vec2( atan( direction.z, direction.x ), acos( direction.y ) );
203
- uv /= vec2( 2.0 * PI, PI );
204
-
205
- // apply adjustments to get values in range [0, 1] and y right side up
206
- uv.x += 0.5;
207
- uv.y = 1.0 - uv.y;
208
- return uv;
209
-
210
- }
211
-
212
- vec3 equirectUvToDirection( vec2 uv ) {
213
-
214
- // undo above adjustments
215
- uv.x -= 0.5;
216
- uv.y = 1.0 - uv.y;
217
-
218
- // from Vector3.setFromSphericalCoords
219
- float theta = uv.x * 2.0 * PI;
220
- float phi = uv.y * PI;
221
-
222
- float sinPhi = sin( phi );
223
-
224
- return vec3( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
225
-
226
- }
227
-
228
- // Fast arccos approximation used to remove banding artifacts caused by numerical errors in acos.
229
- // This is a cubic Lagrange interpolating polynomial for x = [-1, -1/2, 0, 1/2, 1].
230
- // For more information see: https://github.com/gkjohnson/three-gpu-pathtracer/pull/171#issuecomment-1152275248
231
- float acosApprox( float x ) {
232
-
233
- x = clamp( x, -1.0, 1.0 );
234
- return ( - 0.69813170079773212 * x * x - 0.87266462599716477 ) * x + 1.5707963267948966;
235
-
236
- }
237
-
238
- // An acos with input values bound to the range [-1, 1].
239
- float acosSafe( float x ) {
240
-
241
- return acos( clamp( x, -1.0, 1.0 ) );
242
-
243
- }
244
-
245
- float saturateCos( float val ) {
246
-
247
- return clamp( val, 0.001, 1.0 );
248
-
249
- }
250
-
251
- float square( float t ) {
252
-
253
- return t * t;
254
-
255
- }
256
-
257
- vec2 square( vec2 t ) {
258
-
259
- return t * t;
260
-
261
- }
262
-
263
- vec3 square( vec3 t ) {
264
-
265
- return t * t;
266
-
267
- }
268
-
269
- vec4 square( vec4 t ) {
270
-
271
- return t * t;
272
-
273
- }
274
-
275
- vec2 rotateVector( vec2 v, float t ) {
276
-
277
- float ac = cos( t );
278
- float as = sin( t );
279
- return vec2(
280
- v.x * ac - v.y * as,
281
- v.x * as + v.y * ac
282
- );
283
-
284
- }
285
-
286
- // Finds the point where the ray intersects the plane defined by u and v and checks if this point
287
- // falls in the bounds of the rectangle on that same plane.
288
- // Plane intersection: https://lousodrome.net/blog/light/2020/07/03/intersection-of-a-ray-and-a-plane/
289
- bool intersectsRectangle( vec3 center, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
290
-
291
- float t = dot( center - rayOrigin, normal ) / dot( rayDirection, normal );
292
-
293
- if ( t > EPSILON ) {
294
-
295
- vec3 p = rayOrigin + rayDirection * t;
296
- vec3 vi = p - center;
297
-
298
- // check if p falls inside the rectangle
299
- float a1 = dot( u, vi );
300
- if ( abs( a1 ) <= 0.5 ) {
301
-
302
- float a2 = dot( v, vi );
303
- if ( abs( a2 ) <= 0.5 ) {
304
-
305
- dist = t;
306
- return true;
307
-
308
- }
309
-
310
- }
311
-
312
- }
313
-
314
- return false;
315
-
316
- }
317
-
318
- // Finds the point where the ray intersects the plane defined by u and v and checks if this point
319
- // falls in the bounds of the circle on that same plane. See above URL for a description of the plane intersection algorithm.
320
- bool intersectsCircle( vec3 position, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
321
-
322
- float t = dot( position - rayOrigin, normal ) / dot( rayDirection, normal );
323
-
324
- if ( t > EPSILON ) {
325
-
326
- vec3 hit = rayOrigin + rayDirection * t;
327
- vec3 vi = hit - position;
328
-
329
- float a1 = dot( u, vi );
330
- float a2 = dot( v, vi );
331
-
332
- if( length( vec2( a1, a2 ) ) <= 0.5 ) {
333
-
334
- dist = t;
335
- return true;
336
-
337
- }
338
-
339
- }
340
-
341
- return false;
342
-
343
- }
344
-
345
- // power heuristic for multiple importance sampling
346
- float misHeuristic( float a, float b ) {
347
-
348
- float aa = a * a;
349
- float bb = b * b;
350
- return aa / ( aa + bb );
351
-
352
- }
353
-
354
- // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
355
- // erichlof/THREE.js-PathTracing-Renderer/
356
- float tentFilter( float x ) {
357
-
358
- return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
359
-
360
- }
361
- `;
1
+ export const shaderUtils = /* glsl */`
2
+
3
+ // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume/README.md#attenuation
4
+ vec3 transmissionAttenuation( float dist, vec3 attColor, float attDist ) {
5
+
6
+ vec3 ot = - log( attColor ) / attDist;
7
+ return exp( - ot * dist );
8
+
9
+ }
10
+
11
+ // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
12
+ float schlickFresnel( float cosine, float f0 ) {
13
+
14
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
15
+
16
+ }
17
+
18
+ vec3 schlickFresnel( float cosine, vec3 f0 ) {
19
+
20
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
21
+
22
+ }
23
+
24
+ float dielectricFresnel( float cosThetaI, float eta ) {
25
+
26
+ // https://schuttejoe.github.io/post/disneybsdf/
27
+ float ni = eta;
28
+ float nt = 1.0;
29
+
30
+ // Check for total internal reflection
31
+ float sinThetaISq = 1.0f - cosThetaI * cosThetaI;
32
+ float sinThetaTSq = eta * eta * sinThetaISq;
33
+ if( sinThetaTSq >= 1.0 ) {
34
+
35
+ return 1.0;
36
+
37
+ }
38
+
39
+ float sinThetaT = sqrt( sinThetaTSq );
40
+
41
+ float cosThetaT = sqrt( max( 0.0, 1.0f - sinThetaT * sinThetaT ) );
42
+ float rParallel = ( ( nt * cosThetaI ) - ( ni * cosThetaT ) ) / ( ( nt * cosThetaI ) + ( ni * cosThetaT ) );
43
+ float rPerpendicular = ( ( ni * cosThetaI ) - ( nt * cosThetaT ) ) / ( ( ni * cosThetaI ) + ( nt * cosThetaT ) );
44
+ return ( rParallel * rParallel + rPerpendicular * rPerpendicular ) / 2.0;
45
+
46
+ }
47
+
48
+ // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
49
+ float iorRatioToF0( float eta ) {
50
+
51
+ return pow( ( 1.0 - eta ) / ( 1.0 + eta ), 2.0 );
52
+
53
+ }
54
+
55
+ // forms a basis with the normal vector as Z
56
+ mat3 getBasisFromNormal( vec3 normal ) {
57
+
58
+ vec3 other;
59
+ if ( abs( normal.x ) > 0.5 ) {
60
+
61
+ other = vec3( 0.0, 1.0, 0.0 );
62
+
63
+ } else {
64
+
65
+ other = vec3( 1.0, 0.0, 0.0 );
66
+
67
+ }
68
+
69
+ vec3 ortho = normalize( cross( normal, other ) );
70
+ vec3 ortho2 = normalize( cross( normal, ortho ) );
71
+ return mat3( ortho2, ortho, normal );
72
+
73
+ }
74
+
75
+ vec3 getHalfVector( vec3 wi, vec3 wo, float eta ) {
76
+
77
+ // get the half vector - assuming if the light incident vector is on the other side
78
+ // of the that it's transmissive.
79
+ vec3 h;
80
+ if ( wi.z > 0.0 ) {
81
+
82
+ h = normalize( wi + wo );
83
+
84
+ } else {
85
+
86
+ // Scale by the ior ratio to retrieve the appropriate half vector
87
+ // From Section 2.2 on computing the transmission half vector:
88
+ // https://blog.selfshadow.com/publications/s2015-shading-course/burley/s2015_pbs_disney_bsdf_notes.pdf
89
+ h = normalize( wi + wo * eta );
90
+
91
+ }
92
+
93
+ h *= sign( h.z );
94
+ return h;
95
+
96
+ }
97
+
98
+ vec3 getHalfVector( vec3 a, vec3 b ) {
99
+
100
+ return normalize( a + b );
101
+
102
+ }
103
+
104
+ // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
105
+ // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
106
+ // we find a ray like that we ignore it to avoid artifacts.
107
+ // This function returns if the direction is on the same side of both planes.
108
+ bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
109
+
110
+ bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
111
+ bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
112
+ return aboveSurfaceNormal == aboveGeometryNormal;
113
+
114
+ }
115
+
116
+ vec3 getHemisphereSample( vec3 n, vec2 uv ) {
117
+
118
+ // https://www.rorydriscoll.com/2009/01/07/better-sampling/
119
+ // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
120
+ float sign = n.z == 0.0 ? 1.0 : sign( n.z );
121
+ float a = - 1.0 / ( sign + n.z );
122
+ float b = n.x * n.y * a;
123
+ vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
124
+ vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
125
+
126
+ float r = sqrt( uv.x );
127
+ float theta = 2.0 * PI * uv.y;
128
+ float x = r * cos( theta );
129
+ float y = r * sin( theta );
130
+ return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
131
+
132
+ }
133
+
134
+ vec2 sampleTriangle( vec2 a, vec2 b, vec2 c, vec2 r ) {
135
+
136
+ // get the edges of the triangle and the diagonal across the
137
+ // center of the parallelogram
138
+ vec2 e1 = a - b;
139
+ vec2 e2 = c - b;
140
+ vec2 diag = normalize( e1 + e2 );
141
+
142
+ // pick the point in the parallelogram
143
+ if ( r.x + r.y > 1.0 ) {
144
+
145
+ r = vec2( 1.0 ) - r;
146
+
147
+ }
148
+
149
+ return e1 * r.x + e2 * r.y;
150
+
151
+ }
152
+
153
+ vec2 sampleCircle( vec2 uv ) {
154
+
155
+ float angle = 2.0 * PI * uv.x;
156
+ float radius = sqrt( uv.y );
157
+ return vec2( cos( angle ), sin( angle ) ) * radius;
158
+
159
+ }
160
+
161
+ vec3 sampleSphere( vec2 uv ) {
162
+
163
+ float u = ( uv.x - 0.5 ) * 2.0;
164
+ float t = uv.y * PI * 2.0;
165
+ float f = sqrt( 1.0 - u * u );
166
+
167
+ return vec3( f * cos( t ), f * sin( t ), u );
168
+
169
+ }
170
+
171
+ vec2 sampleRegularNGon( int sides, vec3 uvw ) {
172
+
173
+ sides = max( sides, 3 );
174
+
175
+ vec3 r = uvw;
176
+ float anglePerSegment = 2.0 * PI / float( sides );
177
+ float segment = floor( float( sides ) * r.x );
178
+
179
+ float angle1 = anglePerSegment * segment;
180
+ float angle2 = angle1 + anglePerSegment;
181
+ vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
182
+ vec2 b = vec2( 0.0, 0.0 );
183
+ vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
184
+
185
+ return sampleTriangle( a, b, c, r.yz );
186
+
187
+ }
188
+
189
+ // samples an aperture shape with the given number of sides. 0 means circle
190
+ vec2 sampleAperture( int blades, vec3 uvw ) {
191
+
192
+ return blades == 0 ?
193
+ sampleCircle( uvw.xy ) :
194
+ sampleRegularNGon( blades, uvw );
195
+
196
+ }
197
+
198
+ // ray sampling x and z are swapped to align with expected background view
199
+ vec2 equirectDirectionToUv( vec3 direction ) {
200
+
201
+ // from Spherical.setFromCartesianCoords
202
+ vec2 uv = vec2( atan( direction.z, direction.x ), acos( direction.y ) );
203
+ uv /= vec2( 2.0 * PI, PI );
204
+
205
+ // apply adjustments to get values in range [0, 1] and y right side up
206
+ uv.x += 0.5;
207
+ uv.y = 1.0 - uv.y;
208
+ return uv;
209
+
210
+ }
211
+
212
+ vec3 equirectUvToDirection( vec2 uv ) {
213
+
214
+ // undo above adjustments
215
+ uv.x -= 0.5;
216
+ uv.y = 1.0 - uv.y;
217
+
218
+ // from Vector3.setFromSphericalCoords
219
+ float theta = uv.x * 2.0 * PI;
220
+ float phi = uv.y * PI;
221
+
222
+ float sinPhi = sin( phi );
223
+
224
+ return vec3( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
225
+
226
+ }
227
+
228
+ // Fast arccos approximation used to remove banding artifacts caused by numerical errors in acos.
229
+ // This is a cubic Lagrange interpolating polynomial for x = [-1, -1/2, 0, 1/2, 1].
230
+ // For more information see: https://github.com/gkjohnson/three-gpu-pathtracer/pull/171#issuecomment-1152275248
231
+ float acosApprox( float x ) {
232
+
233
+ x = clamp( x, -1.0, 1.0 );
234
+ return ( - 0.69813170079773212 * x * x - 0.87266462599716477 ) * x + 1.5707963267948966;
235
+
236
+ }
237
+
238
+ // An acos with input values bound to the range [-1, 1].
239
+ float acosSafe( float x ) {
240
+
241
+ return acos( clamp( x, -1.0, 1.0 ) );
242
+
243
+ }
244
+
245
+ float saturateCos( float val ) {
246
+
247
+ return clamp( val, 0.001, 1.0 );
248
+
249
+ }
250
+
251
+ float square( float t ) {
252
+
253
+ return t * t;
254
+
255
+ }
256
+
257
+ vec2 square( vec2 t ) {
258
+
259
+ return t * t;
260
+
261
+ }
262
+
263
+ vec3 square( vec3 t ) {
264
+
265
+ return t * t;
266
+
267
+ }
268
+
269
+ vec4 square( vec4 t ) {
270
+
271
+ return t * t;
272
+
273
+ }
274
+
275
+ vec2 rotateVector( vec2 v, float t ) {
276
+
277
+ float ac = cos( t );
278
+ float as = sin( t );
279
+ return vec2(
280
+ v.x * ac - v.y * as,
281
+ v.x * as + v.y * ac
282
+ );
283
+
284
+ }
285
+
286
+ // Finds the point where the ray intersects the plane defined by u and v and checks if this point
287
+ // falls in the bounds of the rectangle on that same plane.
288
+ // Plane intersection: https://lousodrome.net/blog/light/2020/07/03/intersection-of-a-ray-and-a-plane/
289
+ bool intersectsRectangle( vec3 center, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
290
+
291
+ float t = dot( center - rayOrigin, normal ) / dot( rayDirection, normal );
292
+
293
+ if ( t > EPSILON ) {
294
+
295
+ vec3 p = rayOrigin + rayDirection * t;
296
+ vec3 vi = p - center;
297
+
298
+ // check if p falls inside the rectangle
299
+ float a1 = dot( u, vi );
300
+ if ( abs( a1 ) <= 0.5 ) {
301
+
302
+ float a2 = dot( v, vi );
303
+ if ( abs( a2 ) <= 0.5 ) {
304
+
305
+ dist = t;
306
+ return true;
307
+
308
+ }
309
+
310
+ }
311
+
312
+ }
313
+
314
+ return false;
315
+
316
+ }
317
+
318
+ // Finds the point where the ray intersects the plane defined by u and v and checks if this point
319
+ // falls in the bounds of the circle on that same plane. See above URL for a description of the plane intersection algorithm.
320
+ bool intersectsCircle( vec3 position, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
321
+
322
+ float t = dot( position - rayOrigin, normal ) / dot( rayDirection, normal );
323
+
324
+ if ( t > EPSILON ) {
325
+
326
+ vec3 hit = rayOrigin + rayDirection * t;
327
+ vec3 vi = hit - position;
328
+
329
+ float a1 = dot( u, vi );
330
+ float a2 = dot( v, vi );
331
+
332
+ if( length( vec2( a1, a2 ) ) <= 0.5 ) {
333
+
334
+ dist = t;
335
+ return true;
336
+
337
+ }
338
+
339
+ }
340
+
341
+ return false;
342
+
343
+ }
344
+
345
+ // power heuristic for multiple importance sampling
346
+ float misHeuristic( float a, float b ) {
347
+
348
+ float aa = a * a;
349
+ float bb = b * b;
350
+ return aa / ( aa + bb );
351
+
352
+ }
353
+
354
+ // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
355
+ // erichlof/THREE.js-PathTracing-Renderer/
356
+ float tentFilter( float x ) {
357
+
358
+ return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
359
+
360
+ }
361
+ `;