three-gpu-pathtracer 0.0.5 → 0.0.6

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 (42) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +781 -728
  3. package/build/index.module.js +5223 -3956
  4. package/build/index.module.js.map +1 -1
  5. package/build/index.umd.cjs +5226 -3954
  6. package/build/index.umd.cjs.map +1 -1
  7. package/package.json +68 -60
  8. package/src/core/DynamicPathTracingSceneGenerator.js +119 -111
  9. package/src/core/MaterialReducer.js +256 -256
  10. package/src/core/PathTracingRenderer.js +255 -255
  11. package/src/core/PathTracingSceneGenerator.js +68 -68
  12. package/src/index.js +33 -26
  13. package/src/materials/AlphaDisplayMaterial.js +48 -48
  14. package/src/materials/AmbientOcclusionMaterial.js +197 -197
  15. package/src/materials/BlendMaterial.js +67 -67
  16. package/src/materials/LambertPathTracingMaterial.js +285 -285
  17. package/src/materials/MaterialBase.js +56 -56
  18. package/src/materials/PhysicalPathTracingMaterial.js +922 -848
  19. package/src/{core → objects}/EquirectCamera.js +13 -13
  20. package/src/{core → objects}/PhysicalCamera.js +28 -28
  21. package/src/objects/PhysicalSpotLight.js +14 -0
  22. package/src/objects/ShapedAreaLight.js +12 -0
  23. package/src/shader/shaderEnvMapSampling.js +59 -59
  24. package/src/shader/shaderGGXFunctions.js +108 -108
  25. package/src/shader/shaderIridescenceFunctions.js +130 -0
  26. package/src/shader/shaderLightSampling.js +231 -87
  27. package/src/shader/shaderMaterialSampling.js +546 -501
  28. package/src/shader/shaderSheenFunctions.js +98 -0
  29. package/src/shader/shaderStructs.js +307 -191
  30. package/src/shader/shaderUtils.js +350 -287
  31. package/src/uniforms/EquirectHdrInfoUniform.js +259 -263
  32. package/src/uniforms/IESProfilesTexture.js +100 -0
  33. package/src/uniforms/LightsInfoUniformStruct.js +162 -0
  34. package/src/uniforms/MaterialsTexture.js +406 -319
  35. package/src/uniforms/PhysicalCameraUniform.js +36 -36
  36. package/src/uniforms/RenderTarget2DArray.js +93 -93
  37. package/src/utils/BlurredEnvMapGenerator.js +113 -113
  38. package/src/utils/GeometryPreparationUtils.js +194 -194
  39. package/src/utils/IESLoader.js +325 -0
  40. package/src/utils/UVUnwrapper.js +101 -101
  41. package/src/workers/PathTracingSceneWorker.js +42 -41
  42. package/src/uniforms/LightsTexture.js +0 -83
@@ -1,287 +1,350 @@
1
- export const shaderUtils = /* glsl */`
2
-
3
- // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
4
- float schlickFresnel( float cosine, float f0 ) {
5
-
6
- return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
7
-
8
- }
9
-
10
- // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
11
- float schlickFresnelFromIor( float cosine, float iorRatio ) {
12
-
13
- // Schlick approximation
14
- float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
15
- return schlickFresnel( cosine, r_0 );
16
-
17
- }
18
-
19
- // forms a basis with the normal vector as Z
20
- mat3 getBasisFromNormal( vec3 normal ) {
21
-
22
- vec3 other;
23
- if ( abs( normal.x ) > 0.5 ) {
24
-
25
- other = vec3( 0.0, 1.0, 0.0 );
26
-
27
- } else {
28
-
29
- other = vec3( 1.0, 0.0, 0.0 );
30
-
31
- }
32
-
33
- vec3 ortho = normalize( cross( normal, other ) );
34
- vec3 ortho2 = normalize( cross( normal, ortho ) );
35
- return mat3( ortho2, ortho, normal );
36
-
37
- }
38
-
39
- vec3 getHalfVector( vec3 a, vec3 b ) {
40
-
41
- return normalize( a + b );
42
-
43
- }
44
-
45
- // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
46
- // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
47
- // we find a ray like that we ignore it to avoid artifacts.
48
- // This function returns if the direction is on the same side of both planes.
49
- bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
50
-
51
- bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
52
- bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
53
- return aboveSurfaceNormal == aboveGeometryNormal;
54
-
55
- }
56
-
57
- vec3 getHemisphereSample( vec3 n, vec2 uv ) {
58
-
59
- // https://www.rorydriscoll.com/2009/01/07/better-sampling/
60
- // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
61
- float sign = n.z == 0.0 ? 1.0 : sign( n.z );
62
- float a = - 1.0 / ( sign + n.z );
63
- float b = n.x * n.y * a;
64
- vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
65
- vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
66
-
67
- float r = sqrt( uv.x );
68
- float theta = 2.0 * PI * uv.y;
69
- float x = r * cos( theta );
70
- float y = r * sin( theta );
71
- return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
72
-
73
- }
74
-
75
- // https://www.shadertoy.com/view/wltcRS
76
- uvec4 s0;
77
-
78
- void rng_initialize(vec2 p, int frame) {
79
-
80
- // white noise seed
81
- s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
82
-
83
- }
84
-
85
- // https://www.pcg-random.org/
86
- void pcg4d( inout uvec4 v ) {
87
-
88
- v = v * 1664525u + 1013904223u;
89
- v.x += v.y * v.w;
90
- v.y += v.z * v.x;
91
- v.z += v.x * v.y;
92
- v.w += v.y * v.z;
93
- v = v ^ ( v >> 16u );
94
- v.x += v.y*v.w;
95
- v.y += v.z*v.x;
96
- v.z += v.x*v.y;
97
- v.w += v.y*v.z;
98
-
99
- }
100
-
101
- // returns [ 0, 1 ]
102
- float rand() {
103
-
104
- pcg4d(s0);
105
- return float( s0.x ) / float( 0xffffffffu );
106
-
107
- }
108
-
109
- vec2 rand2() {
110
-
111
- pcg4d( s0 );
112
- return vec2( s0.xy ) / float(0xffffffffu);
113
-
114
- }
115
-
116
- vec3 rand3() {
117
-
118
- pcg4d(s0);
119
- return vec3( s0.xyz ) / float( 0xffffffffu );
120
-
121
- }
122
-
123
- vec4 rand4() {
124
-
125
- pcg4d(s0);
126
- return vec4(s0)/float(0xffffffffu);
127
-
128
- }
129
-
130
- // https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
131
- vec3 randDirection() {
132
-
133
- vec2 r = rand2();
134
- float u = ( r.x - 0.5 ) * 2.0;
135
- float t = r.y * PI * 2.0;
136
- float f = sqrt( 1.0 - u * u );
137
-
138
- return vec3( f * cos( t ), f * sin( t ), u );
139
-
140
- }
141
-
142
- vec2 triangleSample( vec2 a, vec2 b, vec2 c ) {
143
-
144
- // get the edges of the triangle and the diagonal across the
145
- // center of the parallelogram
146
- vec2 e1 = a - b;
147
- vec2 e2 = c - b;
148
- vec2 diag = normalize( e1 + e2 );
149
-
150
- // pick a random point in the parallelogram
151
- vec2 r = rand2();
152
- if ( r.x + r.y > 1.0 ) {
153
-
154
- r = vec2( 1.0 ) - r;
155
-
156
- }
157
-
158
- return e1 * r.x + e2 * r.y;
159
-
160
- }
161
-
162
- // samples an aperture shape with the given number of sides. 0 means circle
163
- vec2 sampleAperture( int blades ) {
164
-
165
- if ( blades == 0 ) {
166
-
167
- vec2 r = rand2();
168
- float angle = 2.0 * PI * r.x;
169
- float radius = sqrt( rand() );
170
- return vec2( cos( angle ), sin( angle ) ) * radius;
171
-
172
- } else {
173
-
174
- blades = max( blades, 3 );
175
-
176
- vec3 r = rand3();
177
- float anglePerSegment = 2.0 * PI / float( blades );
178
- float segment = floor( float( blades ) * r.x );
179
-
180
- float angle1 = anglePerSegment * segment;
181
- float angle2 = angle1 + anglePerSegment;
182
- vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
183
- vec2 b = vec2( 0.0, 0.0 );
184
- vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
185
-
186
- return triangleSample( a, b, c );
187
-
188
- }
189
-
190
- }
191
-
192
- float colorToLuminance( vec3 color ) {
193
-
194
- // https://en.wikipedia.org/wiki/Relative_luminance
195
- return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
196
-
197
- }
198
-
199
- // ray sampling x and z are swapped to align with expected background view
200
- vec2 equirectDirectionToUv( vec3 direction ) {
201
-
202
- // from Spherical.setFromCartesianCoords
203
- vec2 uv = vec2( atan( direction.z, direction.x ), acos( direction.y ) );
204
- uv /= vec2( 2.0 * PI, PI );
205
-
206
- // apply adjustments to get values in range [0, 1] and y right side up
207
- uv.x += 0.5;
208
- uv.y = 1.0 - uv.y;
209
- return uv;
210
-
211
- }
212
-
213
- vec3 equirectUvToDirection( vec2 uv ) {
214
-
215
- // undo above adjustments
216
- uv.x -= 0.5;
217
- uv.y = 1.0 - uv.y;
218
-
219
- // from Vector3.setFromSphericalCoords
220
- float theta = uv.x * 2.0 * PI;
221
- float phi = uv.y * PI;
222
-
223
- float sinPhi = sin( phi );
224
-
225
- return vec3( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
226
-
227
- }
228
-
229
- // Fast arccos approximation used to remove banding artifacts caused by numerical errors in acos.
230
- // This is a cubic Lagrange interpolating polynomial for x = [-1, -1/2, 0, 1/2, 1].
231
- // For more information see: https://github.com/gkjohnson/three-gpu-pathtracer/pull/171#issuecomment-1152275248
232
- float acosApprox( float x ) {
233
-
234
- x = clamp( x, -1.0, 1.0 );
235
- return ( - 0.69813170079773212 * x * x - 0.87266462599716477 ) * x + 1.5707963267948966;
236
-
237
- }
238
-
239
- // Finds the point where the ray intersects the plane defined by u and v and checks if this point
240
- // falls in the bounds of the rectangle on that same plane.
241
- // Plane intersection: https://lousodrome.net/blog/light/2020/07/03/intersection-of-a-ray-and-a-plane/
242
- bool intersectsRectangle( vec3 center, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
243
-
244
- float t = dot( center - rayOrigin, normal ) / dot( rayDirection, normal );
245
-
246
- if ( t > EPSILON ) {
247
-
248
- vec3 p = rayOrigin + rayDirection * t;
249
- vec3 vi = p - center;
250
-
251
- // check if p falls inside the rectangle
252
- float a1 = dot( u, vi );
253
- if ( abs( a1 ) <= 0.5 ) {
254
-
255
- float a2 = dot( v, vi );
256
- if ( abs( a2 ) <= 0.5 ) {
257
-
258
- dist = t;
259
- return true;
260
-
261
- }
262
-
263
- }
264
-
265
- }
266
-
267
- return false;
268
-
269
- }
270
-
271
- // power heuristic for multiple importance sampling
272
- float misHeuristic( float a, float b ) {
273
-
274
- float aa = a * a;
275
- float bb = b * b;
276
- return aa / ( aa + bb );
277
-
278
- }
279
-
280
- // An acos with input values bound to the range [-1, 1].
281
- float acosSafe( float x ) {
282
-
283
- return acos( clamp( x, -1.0, 1.0 ) );
284
-
285
- }
286
-
287
- `;
1
+ export const shaderUtils = /* glsl */`
2
+
3
+ // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
4
+ float schlickFresnel( float cosine, float f0 ) {
5
+
6
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
7
+
8
+ }
9
+
10
+ vec3 schlickFresnel( float cosine, vec3 f0 ) {
11
+
12
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
13
+
14
+ }
15
+
16
+ // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
17
+ float schlickFresnelFromIor( float cosine, float iorRatio ) {
18
+
19
+ // Schlick approximation
20
+ float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
21
+ return schlickFresnel( cosine, r_0 );
22
+
23
+ }
24
+
25
+ // forms a basis with the normal vector as Z
26
+ mat3 getBasisFromNormal( vec3 normal ) {
27
+
28
+ vec3 other;
29
+ if ( abs( normal.x ) > 0.5 ) {
30
+
31
+ other = vec3( 0.0, 1.0, 0.0 );
32
+
33
+ } else {
34
+
35
+ other = vec3( 1.0, 0.0, 0.0 );
36
+
37
+ }
38
+
39
+ vec3 ortho = normalize( cross( normal, other ) );
40
+ vec3 ortho2 = normalize( cross( normal, ortho ) );
41
+ return mat3( ortho2, ortho, normal );
42
+
43
+ }
44
+
45
+ vec3 getHalfVector( vec3 a, vec3 b ) {
46
+
47
+ return normalize( a + b );
48
+
49
+ }
50
+
51
+ // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
52
+ // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
53
+ // we find a ray like that we ignore it to avoid artifacts.
54
+ // This function returns if the direction is on the same side of both planes.
55
+ bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
56
+
57
+ bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
58
+ bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
59
+ return aboveSurfaceNormal == aboveGeometryNormal;
60
+
61
+ }
62
+
63
+ vec3 getHemisphereSample( vec3 n, vec2 uv ) {
64
+
65
+ // https://www.rorydriscoll.com/2009/01/07/better-sampling/
66
+ // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
67
+ float sign = n.z == 0.0 ? 1.0 : sign( n.z );
68
+ float a = - 1.0 / ( sign + n.z );
69
+ float b = n.x * n.y * a;
70
+ vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
71
+ vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
72
+
73
+ float r = sqrt( uv.x );
74
+ float theta = 2.0 * PI * uv.y;
75
+ float x = r * cos( theta );
76
+ float y = r * sin( theta );
77
+ return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
78
+
79
+ }
80
+
81
+ // https://www.shadertoy.com/view/wltcRS
82
+ uvec4 s0;
83
+
84
+ void rng_initialize(vec2 p, int frame) {
85
+
86
+ // white noise seed
87
+ s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
88
+
89
+ }
90
+
91
+ // https://www.pcg-random.org/
92
+ void pcg4d( inout uvec4 v ) {
93
+
94
+ v = v * 1664525u + 1013904223u;
95
+ v.x += v.y * v.w;
96
+ v.y += v.z * v.x;
97
+ v.z += v.x * v.y;
98
+ v.w += v.y * v.z;
99
+ v = v ^ ( v >> 16u );
100
+ v.x += v.y*v.w;
101
+ v.y += v.z*v.x;
102
+ v.z += v.x*v.y;
103
+ v.w += v.y*v.z;
104
+
105
+ }
106
+
107
+ // returns [ 0, 1 ]
108
+ float rand() {
109
+
110
+ pcg4d(s0);
111
+ return float( s0.x ) / float( 0xffffffffu );
112
+
113
+ }
114
+
115
+ vec2 rand2() {
116
+
117
+ pcg4d( s0 );
118
+ return vec2( s0.xy ) / float(0xffffffffu);
119
+
120
+ }
121
+
122
+ vec3 rand3() {
123
+
124
+ pcg4d(s0);
125
+ return vec3( s0.xyz ) / float( 0xffffffffu );
126
+
127
+ }
128
+
129
+ vec4 rand4() {
130
+
131
+ pcg4d(s0);
132
+ return vec4(s0)/float(0xffffffffu);
133
+
134
+ }
135
+
136
+ // https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
137
+ vec3 randDirection() {
138
+
139
+ vec2 r = rand2();
140
+ float u = ( r.x - 0.5 ) * 2.0;
141
+ float t = r.y * PI * 2.0;
142
+ float f = sqrt( 1.0 - u * u );
143
+
144
+ return vec3( f * cos( t ), f * sin( t ), u );
145
+
146
+ }
147
+
148
+ vec2 triangleSample( vec2 a, vec2 b, vec2 c ) {
149
+
150
+ // get the edges of the triangle and the diagonal across the
151
+ // center of the parallelogram
152
+ vec2 e1 = a - b;
153
+ vec2 e2 = c - b;
154
+ vec2 diag = normalize( e1 + e2 );
155
+
156
+ // pick a random point in the parallelogram
157
+ vec2 r = rand2();
158
+ if ( r.x + r.y > 1.0 ) {
159
+
160
+ r = vec2( 1.0 ) - r;
161
+
162
+ }
163
+
164
+ return e1 * r.x + e2 * r.y;
165
+
166
+ }
167
+
168
+ // samples an aperture shape with the given number of sides. 0 means circle
169
+ vec2 sampleAperture( int blades ) {
170
+
171
+ if ( blades == 0 ) {
172
+
173
+ vec2 r = rand2();
174
+ float angle = 2.0 * PI * r.x;
175
+ float radius = sqrt( rand() );
176
+ return vec2( cos( angle ), sin( angle ) ) * radius;
177
+
178
+ } else {
179
+
180
+ blades = max( blades, 3 );
181
+
182
+ vec3 r = rand3();
183
+ float anglePerSegment = 2.0 * PI / float( blades );
184
+ float segment = floor( float( blades ) * r.x );
185
+
186
+ float angle1 = anglePerSegment * segment;
187
+ float angle2 = angle1 + anglePerSegment;
188
+ vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
189
+ vec2 b = vec2( 0.0, 0.0 );
190
+ vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
191
+
192
+ return triangleSample( a, b, c );
193
+
194
+ }
195
+
196
+ }
197
+
198
+ float colorToLuminance( vec3 color ) {
199
+
200
+ // https://en.wikipedia.org/wiki/Relative_luminance
201
+ return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
202
+
203
+ }
204
+
205
+ // ray sampling x and z are swapped to align with expected background view
206
+ vec2 equirectDirectionToUv( vec3 direction ) {
207
+
208
+ // from Spherical.setFromCartesianCoords
209
+ vec2 uv = vec2( atan( direction.z, direction.x ), acos( direction.y ) );
210
+ uv /= vec2( 2.0 * PI, PI );
211
+
212
+ // apply adjustments to get values in range [0, 1] and y right side up
213
+ uv.x += 0.5;
214
+ uv.y = 1.0 - uv.y;
215
+ return uv;
216
+
217
+ }
218
+
219
+ vec3 equirectUvToDirection( vec2 uv ) {
220
+
221
+ // undo above adjustments
222
+ uv.x -= 0.5;
223
+ uv.y = 1.0 - uv.y;
224
+
225
+ // from Vector3.setFromSphericalCoords
226
+ float theta = uv.x * 2.0 * PI;
227
+ float phi = uv.y * PI;
228
+
229
+ float sinPhi = sin( phi );
230
+
231
+ return vec3( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
232
+
233
+ }
234
+
235
+ // Fast arccos approximation used to remove banding artifacts caused by numerical errors in acos.
236
+ // This is a cubic Lagrange interpolating polynomial for x = [-1, -1/2, 0, 1/2, 1].
237
+ // For more information see: https://github.com/gkjohnson/three-gpu-pathtracer/pull/171#issuecomment-1152275248
238
+ float acosApprox( float x ) {
239
+
240
+ x = clamp( x, -1.0, 1.0 );
241
+ return ( - 0.69813170079773212 * x * x - 0.87266462599716477 ) * x + 1.5707963267948966;
242
+
243
+ }
244
+
245
+ // An acos with input values bound to the range [-1, 1].
246
+ float acosSafe( float x ) {
247
+
248
+ return acos( clamp( x, -1.0, 1.0 ) );
249
+
250
+ }
251
+
252
+ float saturateCos( float val ) {
253
+
254
+ return clamp( val, 0.001, 1.0 );
255
+
256
+ }
257
+
258
+ float square( float t ) {
259
+
260
+ return t * t;
261
+
262
+ }
263
+
264
+ vec2 square( vec2 t) {
265
+
266
+ return t * t;
267
+
268
+ }
269
+
270
+ vec3 square( vec3 t ) {
271
+
272
+ return t * t;
273
+
274
+ }
275
+
276
+ vec4 square( vec4 t ) {
277
+
278
+ return t * t;
279
+
280
+ }
281
+
282
+ // Finds the point where the ray intersects the plane defined by u and v and checks if this point
283
+ // falls in the bounds of the rectangle on that same plane.
284
+ // Plane intersection: https://lousodrome.net/blog/light/2020/07/03/intersection-of-a-ray-and-a-plane/
285
+ bool intersectsRectangle( vec3 center, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
286
+
287
+ float t = dot( center - rayOrigin, normal ) / dot( rayDirection, normal );
288
+
289
+ if ( t > EPSILON ) {
290
+
291
+ vec3 p = rayOrigin + rayDirection * t;
292
+ vec3 vi = p - center;
293
+
294
+ // check if p falls inside the rectangle
295
+ float a1 = dot( u, vi );
296
+ if ( abs( a1 ) <= 0.5 ) {
297
+
298
+ float a2 = dot( v, vi );
299
+ if ( abs( a2 ) <= 0.5 ) {
300
+
301
+ dist = t;
302
+ return true;
303
+
304
+ }
305
+
306
+ }
307
+
308
+ }
309
+
310
+ return false;
311
+
312
+ }
313
+
314
+ // Finds the point where the ray intersects the plane defined by u and v and checks if this point
315
+ // falls in the bounds of the circle on that same plane. See above URL for a description of the plane intersection algorithm.
316
+ bool intersectsCircle( vec3 position, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
317
+
318
+ float t = dot( position - rayOrigin, normal ) / dot( rayDirection, normal );
319
+
320
+ if ( t > EPSILON ) {
321
+
322
+ vec3 hit = rayOrigin + rayDirection * t;
323
+ vec3 vi = hit - position;
324
+
325
+ float a1 = dot( u, vi );
326
+ float a2 = dot( v, vi );
327
+
328
+ if( length( vec2( a1, a2 ) ) <= 0.5 ) {
329
+
330
+ dist = t;
331
+ return true;
332
+
333
+ }
334
+
335
+ }
336
+
337
+ return false;
338
+
339
+ }
340
+
341
+ // power heuristic for multiple importance sampling
342
+ float misHeuristic( float a, float b ) {
343
+
344
+ float aa = a * a;
345
+ float bb = b * b;
346
+ return aa / ( aa + bb );
347
+
348
+ }
349
+
350
+ `;