three-gpu-pathtracer 0.0.8 → 0.0.9

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.
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three'), require('three/examples/jsm/postprocessing/Pass.js'), require('three-mesh-bvh'), require('three/examples/jsm/utils/BufferGeometryUtils.js')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'three', 'three/examples/jsm/postprocessing/Pass.js', 'three-mesh-bvh', 'three/examples/jsm/utils/BufferGeometryUtils.js'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ThreePathTracer = global.ThreePathTracer || {}, global.THREE, global.THREE, global.MeshBVHLib, global.THREE));
5
- })(this, (function (exports, three, Pass_js, threeMeshBvh, BufferGeometryUtils_js) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three'), require('three/examples/jsm/postprocessing/Pass.js'), require('three/examples/jsm/postprocessing/Pass'), require('three-mesh-bvh'), require('three/examples/jsm/utils/BufferGeometryUtils.js')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'three', 'three/examples/jsm/postprocessing/Pass.js', 'three/examples/jsm/postprocessing/Pass', 'three-mesh-bvh', 'three/examples/jsm/utils/BufferGeometryUtils.js'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ThreePathTracer = global.ThreePathTracer || {}, global.THREE, global.THREE, global.THREE, global.MeshBVHLib, global.THREE));
5
+ })(this, (function (exports, three, Pass_js, Pass, threeMeshBvh, BufferGeometryUtils_js) { 'use strict';
6
6
 
7
7
  class MaterialBase extends three.ShaderMaterial {
8
8
 
@@ -124,6 +124,339 @@
124
124
 
125
125
  }
126
126
 
127
+ // References
128
+ // - https://jcgt.org/published/0009/04/01/
129
+ // - Code from https://www.shadertoy.com/view/WtGyDm
130
+
131
+ // functions to generate multi-dimensions variables of the same functions
132
+ // to support 1, 2, 3, and 4 dimensional sobol sampling.
133
+ function generateSobolFunctionVariants( dim = 1 ) {
134
+
135
+ let type = 'uint';
136
+ if ( dim > 1 ) {
137
+
138
+ type = 'uvec' + dim;
139
+
140
+ }
141
+
142
+ return /* glsl */`
143
+ ${ type } sobolReverseBits( ${ type } x ) {
144
+
145
+ x = ( ( ( x & 0xaaaaaaaau ) >> 1 ) | ( ( x & 0x55555555u ) << 1 ) );
146
+ x = ( ( ( x & 0xccccccccu ) >> 2 ) | ( ( x & 0x33333333u ) << 2 ) );
147
+ x = ( ( ( x & 0xf0f0f0f0u ) >> 4 ) | ( ( x & 0x0f0f0f0fu ) << 4 ) );
148
+ x = ( ( ( x & 0xff00ff00u ) >> 8 ) | ( ( x & 0x00ff00ffu ) << 8 ) );
149
+ return ( ( x >> 16 ) | ( x << 16 ) );
150
+
151
+ }
152
+
153
+ ${ type } sobolHashCombine( uint seed, ${ type } v ) {
154
+
155
+ return seed ^ ( v + ${ type }( ( seed << 6 ) + ( seed >> 2 ) ) );
156
+
157
+ }
158
+
159
+ ${ type } sobolLaineKarrasPermutation( ${ type } x, ${ type } seed ) {
160
+
161
+ x += seed;
162
+ x ^= x * 0x6c50b47cu;
163
+ x ^= x * 0xb82f1e52u;
164
+ x ^= x * 0xc7afe638u;
165
+ x ^= x * 0x8d22f6e6u;
166
+ return x;
167
+
168
+ }
169
+
170
+ ${ type } nestedUniformScrambleBase2( ${ type } x, ${ type } seed ) {
171
+
172
+ x = sobolLaineKarrasPermutation( x, seed );
173
+ x = sobolReverseBits( x );
174
+ return x;
175
+
176
+ }
177
+ `;
178
+
179
+ }
180
+
181
+ function generateSobolSampleFunctions( dim = 1 ) {
182
+
183
+ let utype = 'uint';
184
+ let vtype = 'float';
185
+ let num = '';
186
+ let components = '.r';
187
+ let combineValues = '1u';
188
+ if ( dim > 1 ) {
189
+
190
+ utype = 'uvec' + dim;
191
+ vtype = 'vec' + dim;
192
+ num = dim + '';
193
+ if ( dim === 2 ) {
194
+
195
+ components = '.rg';
196
+ combineValues = 'uvec2( 1u, 2u )';
197
+
198
+ } else if ( dim === 3 ) {
199
+
200
+ components = '.rgb';
201
+ combineValues = 'uvec3( 1u, 2u, 3u )';
202
+
203
+ } else {
204
+
205
+ components = '';
206
+ combineValues = 'uvec4( 1u, 2u, 3u, 4u )';
207
+
208
+ }
209
+
210
+ }
211
+
212
+ return /* glsl */`
213
+
214
+ ${ vtype } sobol${ num }( int effect ) {
215
+
216
+ uint seed = sobolGetSeed( sobolBounceIndex, uint( effect ) );
217
+ uint index = sobolPathIndex;
218
+
219
+ uint shuffle_seed = sobolHashCombine( seed, 0u );
220
+ uint shuffled_index = nestedUniformScrambleBase2( sobolReverseBits( index ), shuffle_seed );
221
+ ${ vtype } sobol_pt = sobolGetTexturePoint( shuffled_index )${ components };
222
+ ${ utype } result = ${ utype }( sobol_pt * 16777216.0 );
223
+
224
+ ${ utype } seed2 = sobolHashCombine( seed, ${ combineValues } );
225
+ result = nestedUniformScrambleBase2( result, seed2 );
226
+
227
+ return SOBOL_FACTOR * ${ vtype }( result >> 8 );
228
+
229
+ }
230
+ `;
231
+
232
+ }
233
+
234
+ const shaderSobolCommon = /* glsl */`
235
+
236
+ // Utils
237
+ const float SOBOL_FACTOR = 1.0 / 16777216.0;
238
+ const uint SOBOL_MAX_POINTS = 256u * 256u;
239
+
240
+ ${ generateSobolFunctionVariants( 1 ) }
241
+ ${ generateSobolFunctionVariants( 2 ) }
242
+ ${ generateSobolFunctionVariants( 3 ) }
243
+ ${ generateSobolFunctionVariants( 4 ) }
244
+
245
+ uint sobolHash( uint x ) {
246
+
247
+ // finalizer from murmurhash3
248
+ x ^= x >> 16;
249
+ x *= 0x85ebca6bu;
250
+ x ^= x >> 13;
251
+ x *= 0xc2b2ae35u;
252
+ x ^= x >> 16;
253
+ return x;
254
+
255
+ }
256
+
257
+ `;
258
+
259
+ const shaderSobolGeneration = /* glsl */`
260
+
261
+ const uint SOBOL_DIRECTIONS_1[ 32 ] = uint[ 32 ](
262
+ 0x80000000u, 0xc0000000u, 0xa0000000u, 0xf0000000u,
263
+ 0x88000000u, 0xcc000000u, 0xaa000000u, 0xff000000u,
264
+ 0x80800000u, 0xc0c00000u, 0xa0a00000u, 0xf0f00000u,
265
+ 0x88880000u, 0xcccc0000u, 0xaaaa0000u, 0xffff0000u,
266
+ 0x80008000u, 0xc000c000u, 0xa000a000u, 0xf000f000u,
267
+ 0x88008800u, 0xcc00cc00u, 0xaa00aa00u, 0xff00ff00u,
268
+ 0x80808080u, 0xc0c0c0c0u, 0xa0a0a0a0u, 0xf0f0f0f0u,
269
+ 0x88888888u, 0xccccccccu, 0xaaaaaaaau, 0xffffffffu
270
+ );
271
+
272
+ const uint SOBOL_DIRECTIONS_2[ 32 ] = uint[ 32 ](
273
+ 0x80000000u, 0xc0000000u, 0x60000000u, 0x90000000u,
274
+ 0xe8000000u, 0x5c000000u, 0x8e000000u, 0xc5000000u,
275
+ 0x68800000u, 0x9cc00000u, 0xee600000u, 0x55900000u,
276
+ 0x80680000u, 0xc09c0000u, 0x60ee0000u, 0x90550000u,
277
+ 0xe8808000u, 0x5cc0c000u, 0x8e606000u, 0xc5909000u,
278
+ 0x6868e800u, 0x9c9c5c00u, 0xeeee8e00u, 0x5555c500u,
279
+ 0x8000e880u, 0xc0005cc0u, 0x60008e60u, 0x9000c590u,
280
+ 0xe8006868u, 0x5c009c9cu, 0x8e00eeeeu, 0xc5005555u
281
+ );
282
+
283
+ const uint SOBOL_DIRECTIONS_3[ 32 ] = uint[ 32 ](
284
+ 0x80000000u, 0xc0000000u, 0x20000000u, 0x50000000u,
285
+ 0xf8000000u, 0x74000000u, 0xa2000000u, 0x93000000u,
286
+ 0xd8800000u, 0x25400000u, 0x59e00000u, 0xe6d00000u,
287
+ 0x78080000u, 0xb40c0000u, 0x82020000u, 0xc3050000u,
288
+ 0x208f8000u, 0x51474000u, 0xfbea2000u, 0x75d93000u,
289
+ 0xa0858800u, 0x914e5400u, 0xdbe79e00u, 0x25db6d00u,
290
+ 0x58800080u, 0xe54000c0u, 0x79e00020u, 0xb6d00050u,
291
+ 0x800800f8u, 0xc00c0074u, 0x200200a2u, 0x50050093u
292
+ );
293
+
294
+ const uint SOBOL_DIRECTIONS_4[ 32 ] = uint[ 32 ](
295
+ 0x80000000u, 0x40000000u, 0x20000000u, 0xb0000000u,
296
+ 0xf8000000u, 0xdc000000u, 0x7a000000u, 0x9d000000u,
297
+ 0x5a800000u, 0x2fc00000u, 0xa1600000u, 0xf0b00000u,
298
+ 0xda880000u, 0x6fc40000u, 0x81620000u, 0x40bb0000u,
299
+ 0x22878000u, 0xb3c9c000u, 0xfb65a000u, 0xddb2d000u,
300
+ 0x78022800u, 0x9c0b3c00u, 0x5a0fb600u, 0x2d0ddb00u,
301
+ 0xa2878080u, 0xf3c9c040u, 0xdb65a020u, 0x6db2d0b0u,
302
+ 0x800228f8u, 0x400b3cdcu, 0x200fb67au, 0xb00ddb9du
303
+ );
304
+
305
+ uint getMaskedSobol( uint index, uint directions[ 32 ] ) {
306
+
307
+ uint X = 0u;
308
+ for ( int bit = 0; bit < 32; bit ++ ) {
309
+
310
+ uint mask = ( index >> bit ) & 1u;
311
+ X ^= mask * directions[ bit ];
312
+
313
+ }
314
+ return X;
315
+
316
+ }
317
+
318
+ vec4 generateSobolPoint( uint index ) {
319
+
320
+ if ( index >= SOBOL_MAX_POINTS ) {
321
+
322
+ return vec4( 0.0 );
323
+
324
+ }
325
+
326
+ // NOTEL this sobol "direction" is also available but we can't write out 5 components
327
+ // uint x = index & 0x00ffffffu;
328
+ uint x = sobolReverseBits( getMaskedSobol( index, SOBOL_DIRECTIONS_1 ) ) & 0x00ffffffu;
329
+ uint y = sobolReverseBits( getMaskedSobol( index, SOBOL_DIRECTIONS_2 ) ) & 0x00ffffffu;
330
+ uint z = sobolReverseBits( getMaskedSobol( index, SOBOL_DIRECTIONS_3 ) ) & 0x00ffffffu;
331
+ uint w = sobolReverseBits( getMaskedSobol( index, SOBOL_DIRECTIONS_4 ) ) & 0x00ffffffu;
332
+
333
+ return vec4( x, y, z, w ) * SOBOL_FACTOR;
334
+
335
+ }
336
+
337
+ `;
338
+
339
+ const shaderSobolSampling = /* glsl */`
340
+
341
+ // Seeds
342
+ uniform sampler2D sobolTexture;
343
+ uint sobolPixelIndex;
344
+ uint sobolPathIndex;
345
+ uint sobolBounceIndex;
346
+
347
+ uint sobolGetSeed( uint bounce, uint effect ) {
348
+
349
+ return sobolHash(
350
+ sobolHashCombine(
351
+ sobolHashCombine(
352
+ sobolHash( bounce ),
353
+ sobolPixelIndex
354
+ ),
355
+ effect
356
+ )
357
+ );
358
+
359
+ }
360
+
361
+ vec4 sobolGetTexturePoint( uint index ) {
362
+
363
+ if ( index >= SOBOL_MAX_POINTS ) {
364
+
365
+ index = index % SOBOL_MAX_POINTS;
366
+
367
+ }
368
+
369
+ uvec2 dim = uvec2( textureSize( sobolTexture, 0 ).xy );
370
+ uint y = index / dim.x;
371
+ uint x = index - y * dim.x;
372
+ vec2 uv = vec2( x, y ) / vec2( dim );
373
+ return texture( sobolTexture, uv );
374
+
375
+ }
376
+
377
+ ${ generateSobolSampleFunctions( 1 ) }
378
+ ${ generateSobolSampleFunctions( 2 ) }
379
+ ${ generateSobolSampleFunctions( 3 ) }
380
+ ${ generateSobolSampleFunctions( 4 ) }
381
+
382
+ `;
383
+
384
+ class SobolNumbersMaterial extends MaterialBase {
385
+
386
+ constructor() {
387
+
388
+ super( {
389
+
390
+ blending: three.NoBlending,
391
+
392
+ uniforms: {
393
+
394
+ resolution: { value: new three.Vector2() },
395
+
396
+ },
397
+
398
+ vertexShader: /* glsl */`
399
+
400
+ varying vec2 vUv;
401
+ void main() {
402
+
403
+ vUv = uv;
404
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
405
+
406
+ }
407
+ `,
408
+
409
+ fragmentShader: /* glsl */`
410
+
411
+ ${ shaderSobolCommon }
412
+ ${ shaderSobolGeneration }
413
+
414
+ varying vec2 vUv;
415
+ uniform vec2 resolution;
416
+ void main() {
417
+
418
+ uint index = uint( gl_FragCoord.y ) * uint( resolution.x ) + uint( gl_FragCoord.x );
419
+ gl_FragColor = generateSobolPoint( index );
420
+
421
+ }
422
+ `,
423
+
424
+ } );
425
+
426
+ }
427
+
428
+ }
429
+
430
+ class SobolNumberMapGenerator {
431
+
432
+ generate( renderer, dimensions = 256 ) {
433
+
434
+ const target = new three.WebGLRenderTarget( dimensions, dimensions, {
435
+
436
+ type: three.FloatType,
437
+ format: three.RGBAFormat,
438
+ minFilter: three.NearestFilter,
439
+ magFilter: three.NearestFilter,
440
+ generateMipmaps: false,
441
+
442
+ } );
443
+
444
+ const ogTarget = renderer.getRenderTarget();
445
+ renderer.setRenderTarget( target );
446
+
447
+ const quad = new Pass.FullScreenQuad( new SobolNumbersMaterial() );
448
+ quad.material.resolution.set( dimensions, dimensions );
449
+ quad.render( renderer );
450
+
451
+ renderer.setRenderTarget( ogTarget );
452
+ quad.dispose();
453
+
454
+ return target;
455
+
456
+ }
457
+
458
+ }
459
+
127
460
  function* renderTask() {
128
461
 
129
462
  const {
@@ -132,6 +465,7 @@
132
465
  _blendQuad,
133
466
  _primaryTarget,
134
467
  _blendTargets,
468
+ _sobolTarget,
135
469
  alpha,
136
470
  camera,
137
471
  material,
@@ -158,6 +492,7 @@
158
492
  const w = _primaryTarget.width;
159
493
  const h = _primaryTarget.height;
160
494
  material.resolution.set( w, h );
495
+ material.sobolTexture = _sobolTarget.texture;
161
496
  material.seed ++;
162
497
 
163
498
  const tilesX = this.tiles.x || 1;
@@ -306,6 +641,7 @@
306
641
  this._task = null;
307
642
  this._currentTile = 0;
308
643
 
644
+ this._sobolTarget = new SobolNumberMapGenerator().generate( renderer );
309
645
  this._primaryTarget = new three.WebGLRenderTarget( 1, 1, {
310
646
  format: three.RGBAFormat,
311
647
  type: three.FloatType,
@@ -337,6 +673,7 @@
337
673
  this._primaryTarget.dispose();
338
674
  this._blendTargets[ 0 ].dispose();
339
675
  this._blendTargets[ 1 ].dispose();
676
+ this._sobolTarget.dispose();
340
677
 
341
678
  this._fsQuad.dispose();
342
679
  this._blendQuad.dispose();
@@ -1202,6 +1539,14 @@
1202
1539
 
1203
1540
  }
1204
1541
 
1542
+ // we must hash the texture to determine uniqueness using the encoding, as well, because the
1543
+ // when rendering each texture to the texture array they must have a consistent color space.
1544
+ function getTextureHash( t ) {
1545
+
1546
+ return `${ t.source.uuid }:${ t.encoding }`;
1547
+
1548
+ }
1549
+
1205
1550
  // reduce the set of textures to just those with a unique source while retaining
1206
1551
  // the order of the textures.
1207
1552
  function reduceTexturesToUniqueSources( textures ) {
@@ -1211,9 +1556,10 @@
1211
1556
  for ( let i = 0, l = textures.length; i < l; i ++ ) {
1212
1557
 
1213
1558
  const tex = textures[ i ];
1214
- if ( ! sourceSet.has( tex.source ) ) {
1559
+ const hash = getTextureHash( tex );
1560
+ if ( ! sourceSet.has( hash ) ) {
1215
1561
 
1216
- sourceSet.add( tex.source );
1562
+ sourceSet.add( hash );
1217
1563
  result.push( tex );
1218
1564
 
1219
1565
  }
@@ -1284,8 +1630,8 @@
1284
1630
 
1285
1631
  if ( key in material && material[ key ] ) {
1286
1632
 
1287
- const source = material[ key ].source;
1288
- return uniqueTextures.findIndex( tex => tex.source === source );
1633
+ const hash = getTextureHash( material[ key ] );
1634
+ return uniqueTextureLookup[ hash ];
1289
1635
 
1290
1636
  } else {
1291
1637
 
@@ -1375,6 +1721,12 @@
1375
1721
 
1376
1722
  // get the list of textures with unique sources
1377
1723
  const uniqueTextures = reduceTexturesToUniqueSources( textures );
1724
+ const uniqueTextureLookup = {};
1725
+ for ( let i = 0, l = uniqueTextures.length; i < l; i ++ ) {
1726
+
1727
+ uniqueTextureLookup[ getTextureHash( uniqueTextures[ i ] ) ] = i;
1728
+
1729
+ }
1378
1730
 
1379
1731
  if ( image.width !== dimension ) {
1380
1732
 
@@ -1406,9 +1758,9 @@
1406
1758
  // sample 1
1407
1759
  // metalness & roughness
1408
1760
  floatArray[ index ++ ] = getField( m, 'metalness', 0.0 );
1409
- floatArray[ index ++ ] = uniqueTextures.indexOf( m.metalnessMap );
1761
+ floatArray[ index ++ ] = getTexture( m, 'metalnessMap' );
1410
1762
  floatArray[ index ++ ] = getField( m, 'roughness', 0.0 );
1411
- floatArray[ index ++ ] = uniqueTextures.indexOf( m.roughnessMap );
1763
+ floatArray[ index ++ ] = getTexture( m, 'roughnessMap' );
1412
1764
 
1413
1765
  // sample 2
1414
1766
  // transmission & emissiveIntensity
@@ -1586,7 +1938,7 @@
1586
1938
  // sample 14
1587
1939
  index ++; // matte
1588
1940
  index ++; // shadow
1589
- floatArray[ index ++ ] = Number( m.vertexColors ); // vertexColors
1941
+ floatArray[ index ++ ] = Number( m.vertexColors ) | ( Number( m.flatShading ) << 1 ); // vertexColors & flatShading
1590
1942
  floatArray[ index ++ ] = Number( m.transparent ); // transparent
1591
1943
 
1592
1944
  // map transform 15
@@ -2025,6 +2377,8 @@
2025
2377
  const RECT_AREA_LIGHT = 0;
2026
2378
  const CIRC_AREA_LIGHT = 1;
2027
2379
  const SPOT_LIGHT = 2;
2380
+ const DIR_LIGHT = 3;
2381
+ const POINT_LIGHT = 4;
2028
2382
  class LightsInfoUniformStruct {
2029
2383
 
2030
2384
  constructor() {
@@ -2083,8 +2437,24 @@
2083
2437
 
2084
2438
  // type
2085
2439
  let type = RECT_AREA_LIGHT;
2086
- if ( l.isRectAreaLight && l.isCircular ) type = CIRC_AREA_LIGHT;
2087
- else if ( l.isSpotLight ) type = SPOT_LIGHT;
2440
+ if ( l.isRectAreaLight && l.isCircular ) {
2441
+
2442
+ type = CIRC_AREA_LIGHT;
2443
+
2444
+ } else if ( l.isSpotLight ) {
2445
+
2446
+ type = SPOT_LIGHT;
2447
+
2448
+ } else if ( l.isDirectionalLight ) {
2449
+
2450
+ type = DIR_LIGHT;
2451
+
2452
+ } else if ( l.isPointLight ) {
2453
+
2454
+ type = POINT_LIGHT;
2455
+
2456
+ }
2457
+
2088
2458
  floatArray[ baseIndex + ( index ++ ) ] = type;
2089
2459
 
2090
2460
  // sample 2
@@ -2171,6 +2541,33 @@
2171
2541
  // iesProfile
2172
2542
  floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
2173
2543
 
2544
+ } else if ( l.isPointLight ) {
2545
+
2546
+ const worldPosition = l.getWorldPosition( u );
2547
+ floatArray[ baseIndex + ( index ++ ) ] = worldPosition.x;
2548
+ floatArray[ baseIndex + ( index ++ ) ] = worldPosition.y;
2549
+ floatArray[ baseIndex + ( index ++ ) ] = worldPosition.z;
2550
+ index ++;
2551
+
2552
+ // sample 4
2553
+ index += 4;
2554
+
2555
+ // sample 5
2556
+ index += 2;
2557
+
2558
+ floatArray[ baseIndex + ( index ++ ) ] = l.decay;
2559
+ floatArray[ baseIndex + ( index ++ ) ] = l.distance;
2560
+
2561
+ } else if ( l.isDirectionalLight ) {
2562
+
2563
+ const worldPosition = l.getWorldPosition( u );
2564
+ const targetPosition = l.target.getWorldPosition( v );
2565
+
2566
+ target.subVectors( worldPosition, targetPosition ).normalize();
2567
+ floatArray[ baseIndex + ( index ++ ) ] = target.x;
2568
+ floatArray[ baseIndex + ( index ++ ) ] = target.y;
2569
+ floatArray[ baseIndex + ( index ++ ) ] = target.z;
2570
+
2174
2571
  }
2175
2572
 
2176
2573
  }
@@ -2719,74 +3116,7 @@
2719
3116
 
2720
3117
  }
2721
3118
 
2722
- // https://www.shadertoy.com/view/wltcRS
2723
- uvec4 s0;
2724
-
2725
- void rng_initialize(vec2 p, int frame) {
2726
-
2727
- // white noise seed
2728
- s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
2729
-
2730
- }
2731
-
2732
- // https://www.pcg-random.org/
2733
- void pcg4d( inout uvec4 v ) {
2734
-
2735
- v = v * 1664525u + 1013904223u;
2736
- v.x += v.y * v.w;
2737
- v.y += v.z * v.x;
2738
- v.z += v.x * v.y;
2739
- v.w += v.y * v.z;
2740
- v = v ^ ( v >> 16u );
2741
- v.x += v.y*v.w;
2742
- v.y += v.z*v.x;
2743
- v.z += v.x*v.y;
2744
- v.w += v.y*v.z;
2745
-
2746
- }
2747
-
2748
- // returns [ 0, 1 ]
2749
- float rand() {
2750
-
2751
- pcg4d(s0);
2752
- return float( s0.x ) / float( 0xffffffffu );
2753
-
2754
- }
2755
-
2756
- vec2 rand2() {
2757
-
2758
- pcg4d( s0 );
2759
- return vec2( s0.xy ) / float(0xffffffffu);
2760
-
2761
- }
2762
-
2763
- vec3 rand3() {
2764
-
2765
- pcg4d(s0);
2766
- return vec3( s0.xyz ) / float( 0xffffffffu );
2767
-
2768
- }
2769
-
2770
- vec4 rand4() {
2771
-
2772
- pcg4d(s0);
2773
- return vec4(s0)/float(0xffffffffu);
2774
-
2775
- }
2776
-
2777
- // https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
2778
- vec3 randDirection() {
2779
-
2780
- vec2 r = rand2();
2781
- float u = ( r.x - 0.5 ) * 2.0;
2782
- float t = r.y * PI * 2.0;
2783
- float f = sqrt( 1.0 - u * u );
2784
-
2785
- return vec3( f * cos( t ), f * sin( t ), u );
2786
-
2787
- }
2788
-
2789
- vec2 triangleSample( vec2 a, vec2 b, vec2 c ) {
3119
+ vec2 sampleTriangle( vec2 a, vec2 b, vec2 c, vec2 r ) {
2790
3120
 
2791
3121
  // get the edges of the triangle and the diagonal across the
2792
3122
  // center of the parallelogram
@@ -2794,8 +3124,7 @@
2794
3124
  vec2 e2 = c - b;
2795
3125
  vec2 diag = normalize( e1 + e2 );
2796
3126
 
2797
- // pick a random point in the parallelogram
2798
- vec2 r = rand2();
3127
+ // pick the point in the parallelogram
2799
3128
  if ( r.x + r.y > 1.0 ) {
2800
3129
 
2801
3130
  r = vec2( 1.0 ) - r;
@@ -2806,40 +3135,48 @@
2806
3135
 
2807
3136
  }
2808
3137
 
2809
- // samples an aperture shape with the given number of sides. 0 means circle
2810
- vec2 sampleAperture( int blades ) {
3138
+ vec2 sampleCircle( vec2 uv ) {
2811
3139
 
2812
- if ( blades == 0 ) {
3140
+ float angle = 2.0 * PI * uv.x;
3141
+ float radius = sqrt( uv.y );
3142
+ return vec2( cos( angle ), sin( angle ) ) * radius;
2813
3143
 
2814
- vec2 r = rand2();
2815
- float angle = 2.0 * PI * r.x;
2816
- float radius = sqrt( rand() );
2817
- return vec2( cos( angle ), sin( angle ) ) * radius;
3144
+ }
2818
3145
 
2819
- } else {
3146
+ vec3 sampleSphere( vec2 uv ) {
2820
3147
 
2821
- blades = max( blades, 3 );
3148
+ float u = ( uv.x - 0.5 ) * 2.0;
3149
+ float t = uv.y * PI * 2.0;
3150
+ float f = sqrt( 1.0 - u * u );
2822
3151
 
2823
- vec3 r = rand3();
2824
- float anglePerSegment = 2.0 * PI / float( blades );
2825
- float segment = floor( float( blades ) * r.x );
3152
+ return vec3( f * cos( t ), f * sin( t ), u );
2826
3153
 
2827
- float angle1 = anglePerSegment * segment;
2828
- float angle2 = angle1 + anglePerSegment;
2829
- vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
2830
- vec2 b = vec2( 0.0, 0.0 );
2831
- vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
3154
+ }
2832
3155
 
2833
- return triangleSample( a, b, c );
3156
+ vec2 sampleRegularNGon( int sides, vec3 uvw ) {
2834
3157
 
2835
- }
3158
+ sides = max( sides, 3 );
3159
+
3160
+ vec3 r = uvw;
3161
+ float anglePerSegment = 2.0 * PI / float( sides );
3162
+ float segment = floor( float( sides ) * r.x );
3163
+
3164
+ float angle1 = anglePerSegment * segment;
3165
+ float angle2 = angle1 + anglePerSegment;
3166
+ vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
3167
+ vec2 b = vec2( 0.0, 0.0 );
3168
+ vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
3169
+
3170
+ return sampleTriangle( a, b, c, r.yz );
2836
3171
 
2837
3172
  }
2838
3173
 
2839
- float colorToLuminance( vec3 color ) {
3174
+ // samples an aperture shape with the given number of sides. 0 means circle
3175
+ vec2 sampleAperture( int blades, vec3 uvw ) {
2840
3176
 
2841
- // https://en.wikipedia.org/wiki/Relative_luminance
2842
- return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
3177
+ return blades == 0 ?
3178
+ sampleCircle( uvw.xy ) :
3179
+ sampleRegularNGon( blades, uvw );
2843
3180
 
2844
3181
  }
2845
3182
 
@@ -2920,6 +3257,17 @@
2920
3257
 
2921
3258
  }
2922
3259
 
3260
+ vec2 rotateVector( vec2 v, float t ) {
3261
+
3262
+ float ac = cos( t );
3263
+ float as = sin( t );
3264
+ return vec2(
3265
+ v.x * ac - v.y * as,
3266
+ v.x * as + v.y * ac
3267
+ );
3268
+
3269
+ }
3270
+
2923
3271
  // Finds the point where the ray intersects the plane defined by u and v and checks if this point
2924
3272
  // falls in the bounds of the rectangle on that same plane.
2925
3273
  // Plane intersection: https://lousodrome.net/blog/light/2020/07/03/intersection-of-a-ray-and-a-plane/
@@ -2988,6 +3336,13 @@
2988
3336
 
2989
3337
  }
2990
3338
 
3339
+ // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
3340
+ // erichlof/THREE.js-PathTracing-Renderer/
3341
+ float tentFilter( float x ) {
3342
+
3343
+ return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
3344
+
3345
+ }
2991
3346
  `;
2992
3347
 
2993
3348
  class PMREMCopyMaterial extends MaterialBase {
@@ -3567,6 +3922,7 @@
3567
3922
  int sheenRoughnessMap;
3568
3923
 
3569
3924
  bool vertexColors;
3925
+ bool flatShading;
3570
3926
  bool transparent;
3571
3927
 
3572
3928
  mat3 mapTransform;
@@ -3679,7 +4035,8 @@
3679
4035
 
3680
4036
  m.matte = bool( s14.r );
3681
4037
  m.castShadow = ! bool( s14.g );
3682
- m.vertexColors = bool( s14.b );
4038
+ m.vertexColors = bool( int( s14.b ) & 1 );
4039
+ m.flatShading = bool( int( s14.b ) & 2 );
3683
4040
  m.transparent = bool( s14.a );
3684
4041
 
3685
4042
  uint firstTextureTransformIdx = i + 15u;
@@ -3711,6 +4068,8 @@
3711
4068
  #define RECT_AREA_LIGHT_TYPE 0
3712
4069
  #define CIRC_AREA_LIGHT_TYPE 1
3713
4070
  #define SPOT_LIGHT_TYPE 2
4071
+ #define DIR_LIGHT_TYPE 3
4072
+ #define POINT_LIGHT_TYPE 4
3714
4073
 
3715
4074
  struct LightsInfo {
3716
4075
 
@@ -3762,7 +4121,7 @@
3762
4121
  l.v = s3.rgb;
3763
4122
  l.area = s3.a;
3764
4123
 
3765
- if ( l.type == SPOT_LIGHT_TYPE ) {
4124
+ if ( l.type == SPOT_LIGHT_TYPE || l.type == POINT_LIGHT_TYPE ) {
3766
4125
 
3767
4126
  vec4 s4 = texelFetch1D( tex, i + 4u );
3768
4127
  vec4 s5 = texelFetch1D( tex, i + 5u );
@@ -3815,14 +4174,14 @@
3815
4174
 
3816
4175
  // trowbridge-reitz === GGX === GTR
3817
4176
 
3818
- vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float random1, float random2 ) {
4177
+ vec3 ggxDirection( vec3 incidentDir, vec2 roughness, vec2 uv ) {
3819
4178
 
3820
4179
  // TODO: try GGXVNDF implementation from reference [2], here. Needs to update ggxDistribution
3821
4180
  // function below, as well
3822
4181
 
3823
4182
  // Implementation from reference [1]
3824
4183
  // stretch view
3825
- vec3 V = normalize( vec3( roughnessX * incidentDir.x, roughnessY * incidentDir.y, incidentDir.z ) );
4184
+ vec3 V = normalize( vec3( roughness * incidentDir.xy, incidentDir.z ) );
3826
4185
 
3827
4186
  // orthonormal basis
3828
4187
  vec3 T1 = ( V.z < 0.9999 ) ? normalize( cross( V, vec3( 0.0, 0.0, 1.0 ) ) ) : vec3( 1.0, 0.0, 0.0 );
@@ -3830,16 +4189,16 @@ vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float r
3830
4189
 
3831
4190
  // sample point with polar coordinates (r, phi)
3832
4191
  float a = 1.0 / ( 1.0 + V.z );
3833
- float r = sqrt( random1 );
3834
- float phi = ( random2 < a ) ? random2 / a * PI : PI + ( random2 - a ) / ( 1.0 - a ) * PI;
4192
+ float r = sqrt( uv.x );
4193
+ float phi = ( uv.y < a ) ? uv.y / a * PI : PI + ( uv.y - a ) / ( 1.0 - a ) * PI;
3835
4194
  float P1 = r * cos( phi );
3836
- float P2 = r * sin( phi ) * ( ( random2 < a ) ? 1.0 : V.z );
4195
+ float P2 = r * sin( phi ) * ( ( uv.y < a ) ? 1.0 : V.z );
3837
4196
 
3838
4197
  // compute normal
3839
4198
  vec3 N = P1 * T1 + P2 * T2 + V * sqrt( max( 0.0, 1.0 - P1 * P1 - P2 * P2 ) );
3840
4199
 
3841
4200
  // unstretch
3842
- N = normalize( vec3( roughnessX * N.x, roughnessY * N.y, max( 0.0, N.z ) ) );
4201
+ N = normalize( vec3( roughness * N.xy, max( 0.0, N.z ) ) );
3843
4202
 
3844
4203
  return N;
3845
4204
 
@@ -4227,7 +4586,7 @@ float diffuseEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf, out vec3 color )
4227
4586
 
4228
4587
  vec3 diffuseDirection( vec3 wo, SurfaceRec surf ) {
4229
4588
 
4230
- vec3 lightDirection = randDirection();
4589
+ vec3 lightDirection = sampleSphere( sobol2( 11 ) );
4231
4590
  lightDirection.z += 1.0;
4232
4591
  lightDirection = normalize( lightDirection );
4233
4592
 
@@ -4281,10 +4640,8 @@ vec3 specularDirection( vec3 wo, SurfaceRec surf ) {
4281
4640
  float filteredRoughness = surf.filteredRoughness;
4282
4641
  vec3 halfVector = ggxDirection(
4283
4642
  wo,
4284
- filteredRoughness,
4285
- filteredRoughness,
4286
- rand(),
4287
- rand()
4643
+ vec2( filteredRoughness ),
4644
+ sobol2( 12 )
4288
4645
  );
4289
4646
 
4290
4647
  // apply to new ray by reflecting off the new normal
@@ -4321,10 +4678,8 @@ vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
4321
4678
  // sample ggx vndf distribution which gives a new normal
4322
4679
  vec3 halfVector = ggxDirection(
4323
4680
  wo,
4324
- filteredRoughness,
4325
- filteredRoughness,
4326
- rand(),
4327
- rand()
4681
+ vec2( filteredRoughness ),
4682
+ sobol2( 13 )
4328
4683
  );
4329
4684
 
4330
4685
 
@@ -4364,7 +4719,7 @@ vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
4364
4719
 
4365
4720
  float roughness = surf.roughness;
4366
4721
  float eta = surf.eta;
4367
- vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + randDirection() * roughness );
4722
+ vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( sobol2( 13 ) ) * roughness );
4368
4723
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
4369
4724
 
4370
4725
  if ( surf.thinFilm ) {
@@ -4412,10 +4767,8 @@ vec3 clearcoatDirection( vec3 wo, SurfaceRec surf ) {
4412
4767
  float filteredClearcoatRoughness = surf.filteredClearcoatRoughness;
4413
4768
  vec3 halfVector = ggxDirection(
4414
4769
  wo,
4415
- filteredClearcoatRoughness,
4416
- filteredClearcoatRoughness,
4417
- rand(),
4418
- rand()
4770
+ vec2( filteredClearcoatRoughness ),
4771
+ sobol2( 14 )
4419
4772
  );
4420
4773
 
4421
4774
  // apply to new ray by reflecting off the new normal
@@ -4604,7 +4957,7 @@ SampleRec bsdfSample( vec3 wo, vec3 clearcoatWo, mat3 normalBasis, mat3 invBasis
4604
4957
  vec3 wi;
4605
4958
  vec3 clearcoatWi;
4606
4959
 
4607
- float r = rand();
4960
+ float r = sobol( 15 );
4608
4961
  if ( r <= cdf[0] ) { // diffuse
4609
4962
 
4610
4963
  wi = diffuseDirection( wo, surf );
@@ -4660,13 +5013,13 @@ float envMapDirectionPdf( vec3 direction ) {
4660
5013
 
4661
5014
  }
4662
5015
 
4663
- float envMapSample( vec3 direction, EquirectHdrInfo info, out vec3 color ) {
5016
+ float sampleEnvMap( EquirectHdrInfo info, vec3 direction, out vec3 color ) {
4664
5017
 
4665
5018
  vec2 uv = equirectDirectionToUv( direction );
4666
5019
  color = texture2D( info.map, uv ).rgb;
4667
5020
 
4668
5021
  float totalSum = info.totalSumWhole + info.totalSumDecimal;
4669
- float lum = colorToLuminance( color );
5022
+ float lum = luminance( color );
4670
5023
  ivec2 resolution = textureSize( info.map, 0 );
4671
5024
  float pdf = lum / totalSum;
4672
5025
 
@@ -4674,10 +5027,9 @@ float envMapSample( vec3 direction, EquirectHdrInfo info, out vec3 color ) {
4674
5027
 
4675
5028
  }
4676
5029
 
4677
- float randomEnvMapSample( EquirectHdrInfo info, out vec3 color, out vec3 direction ) {
5030
+ float sampleEnvMapProbability( EquirectHdrInfo info, vec2 r, out vec3 color, out vec3 direction ) {
4678
5031
 
4679
5032
  // sample env map cdf
4680
- vec2 r = rand2();
4681
5033
  float v = texture2D( info.marginalWeights, vec2( r.x, 0.0 ) ).x;
4682
5034
  float u = texture2D( info.conditionalWeights, vec2( r.y, v ) ).x;
4683
5035
  vec2 uv = vec2( u, v );
@@ -4687,7 +5039,7 @@ float randomEnvMapSample( EquirectHdrInfo info, out vec3 color, out vec3 directi
4687
5039
  color = texture2D( info.map, uv ).rgb;
4688
5040
 
4689
5041
  float totalSum = info.totalSumWhole + info.totalSumDecimal;
4690
- float lum = colorToLuminance( color );
5042
+ float lum = luminance( color );
4691
5043
  ivec2 resolution = textureSize( info.map, 0 );
4692
5044
  float pdf = lum / totalSum;
4693
5045
 
@@ -4766,6 +5118,7 @@ LightSampleRec lightsClosestHit( sampler2D lights, uint lightCount, vec3 rayOrig
4766
5118
 
4767
5119
  float dist;
4768
5120
 
5121
+ // MIS / light intersection is not supported for punctual lights.
4769
5122
  if(
4770
5123
  ( light.type == RECT_AREA_LIGHT_TYPE && intersectsRectangle( light.position, normal, u, v, rayOrigin, rayDirection, dist ) ) ||
4771
5124
  ( light.type == CIRC_AREA_LIGHT_TYPE && intersectsCircle( light.position, normal, u, v, rayOrigin, rayDirection, dist ) )
@@ -4784,38 +5137,6 @@ LightSampleRec lightsClosestHit( sampler2D lights, uint lightCount, vec3 rayOrig
4784
5137
 
4785
5138
  }
4786
5139
 
4787
- } else if ( light.type == SPOT_LIGHT_TYPE ) {
4788
-
4789
- // TODO: forward path tracing sampling needs to be made consistent with direct light sampling logic
4790
- // float radius = light.radius;
4791
- // vec3 lightNormal = normalize( cross( light.u, light.v ) );
4792
- // float angle = acos( light.coneCos );
4793
- // float angleTan = tan( angle );
4794
- // float startDistance = radius / max( angleTan, EPSILON );
4795
-
4796
- // u = light.u / radius;
4797
- // v = light.v / radius;
4798
-
4799
- // if (
4800
- // intersectsCircle( light.position - normal * startDistance, normal, u, v, rayOrigin, rayDirection, dist ) &&
4801
- // ( dist < lightSampleRec.dist || ! lightSampleRec.hit )
4802
- // ) {
4803
-
4804
- // float cosTheta = dot( rayDirection, normal );
4805
- // float spotAttenuation = light.iesProfile != - 1 ?
4806
- // getPhotometricAttenuation( iesProfiles, light.iesProfile, rayDirection, normal, u, v )
4807
- // : getSpotAttenuation( light.coneCos, light.penumbraCos, cosTheta );
4808
-
4809
- // float distanceAttenuation = getDistanceAttenuation( dist, light.distance, light.decay );
4810
-
4811
- // lightSampleRec.hit = true;
4812
- // lightSampleRec.dist = dist;
4813
- // lightSampleRec.direction = rayDirection;
4814
- // lightSampleRec.emission = light.color * light.intensity * distanceAttenuation * spotAttenuation;
4815
- // lightSampleRec.pdf = ( dist * dist ) / ( light.area * cosTheta );
4816
-
4817
- // }
4818
-
4819
5140
  }
4820
5141
 
4821
5142
  }
@@ -4824,7 +5145,7 @@ LightSampleRec lightsClosestHit( sampler2D lights, uint lightCount, vec3 rayOrig
4824
5145
 
4825
5146
  }
4826
5147
 
4827
- LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin ) {
5148
+ LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin, vec2 ruv ) {
4828
5149
 
4829
5150
  LightSampleRec lightSampleRec;
4830
5151
  lightSampleRec.hit = true;
@@ -4836,13 +5157,13 @@ LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin ) {
4836
5157
  if( light.type == RECT_AREA_LIGHT_TYPE ) {
4837
5158
 
4838
5159
  // rectangular area light
4839
- randomPos = light.position + light.u * ( rand() - 0.5 ) + light.v * ( rand() - 0.5 );
5160
+ randomPos = light.position + light.u * ( ruv.x - 0.5 ) + light.v * ( ruv.y - 0.5 );
4840
5161
 
4841
- } else if( light.type == 1 ) {
5162
+ } else if( light.type == CIRC_AREA_LIGHT_TYPE ) {
4842
5163
 
4843
5164
  // circular area light
4844
- float r = 0.5 * sqrt( rand() );
4845
- float theta = rand() * 2.0 * PI;
5165
+ float r = 0.5 * sqrt( ruv.x );
5166
+ float theta = ruv.y * 2.0 * PI;
4846
5167
  float x = r * cos( theta );
4847
5168
  float y = r * sin( theta );
4848
5169
 
@@ -4864,10 +5185,10 @@ LightSampleRec randomAreaLightSample( Light light, vec3 rayOrigin ) {
4864
5185
 
4865
5186
  }
4866
5187
 
4867
- LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, vec3 rayOrigin ) {
5188
+ LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, vec3 rayOrigin, vec2 ruv ) {
4868
5189
 
4869
- float radius = light.radius * sqrt( rand() );
4870
- float theta = rand() * 2.0 * PI;
5190
+ float radius = light.radius * sqrt( ruv.x );
5191
+ float theta = ruv.y * 2.0 * PI;
4871
5192
  float x = radius * cos( theta );
4872
5193
  float y = radius * sin( theta );
4873
5194
 
@@ -4888,8 +5209,8 @@ LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, v
4888
5209
  float cosTheta = dot( direction, normal );
4889
5210
 
4890
5211
  float spotAttenuation = light.iesProfile != - 1 ?
4891
- getPhotometricAttenuation( iesProfiles, light.iesProfile, direction, normal, u, v )
4892
- : getSpotAttenuation( light.coneCos, light.penumbraCos, cosTheta );
5212
+ getPhotometricAttenuation( iesProfiles, light.iesProfile, direction, normal, u, v ) :
5213
+ getSpotAttenuation( light.coneCos, light.penumbraCos, cosTheta );
4893
5214
 
4894
5215
  float distanceAttenuation = getDistanceAttenuation( dist, light.distance, light.decay );
4895
5216
  LightSampleRec lightSampleRec;
@@ -4898,30 +5219,59 @@ LightSampleRec randomSpotLightSample( Light light, sampler2DArray iesProfiles, v
4898
5219
  lightSampleRec.dist = dist;
4899
5220
  lightSampleRec.direction = direction;
4900
5221
  lightSampleRec.emission = light.color * light.intensity * distanceAttenuation * spotAttenuation;
4901
-
4902
- // TODO: this makes the result consistent between MIS and non MIS paths but at radius 0 the pdf is infinite
4903
- // and the intensity of the light is not correct
4904
5222
  lightSampleRec.pdf = 1.0;
4905
- // lightSampleRec.pdf = lightDistSq / ( light.area * cosTheta );
4906
5223
 
4907
5224
  return lightSampleRec;
4908
5225
 
4909
5226
  }
4910
5227
 
4911
- LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles, uint lightCount, vec3 rayOrigin ) {
5228
+ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles, uint lightCount, vec3 rayOrigin, vec3 ruv ) {
4912
5229
 
4913
5230
  // pick a random light
4914
- uint l = uint( rand() * float( lightCount ) );
5231
+ uint l = uint( ruv.x * float( lightCount ) );
4915
5232
  Light light = readLightInfo( lights, l );
4916
5233
 
4917
5234
  if ( light.type == SPOT_LIGHT_TYPE ) {
4918
5235
 
4919
- return randomSpotLightSample( light, iesProfiles, rayOrigin );
5236
+ return randomSpotLightSample( light, iesProfiles, rayOrigin, ruv.yz );
5237
+
5238
+ } else if ( light.type == POINT_LIGHT_TYPE ) {
5239
+
5240
+ vec3 lightRay = light.u - rayOrigin;
5241
+ float lightDist = length( lightRay );
5242
+ float cutoffDistance = light.distance;
5243
+ float distanceFalloff = 1.0 / max( pow( lightDist, light.decay ), 0.01 );
5244
+ if ( cutoffDistance > 0.0 ) {
5245
+
5246
+ distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDist / cutoffDistance ) ) );
5247
+
5248
+ }
5249
+
5250
+ LightSampleRec rec;
5251
+ rec.hit = true;
5252
+ rec.direction = normalize( lightRay );
5253
+ rec.dist = length( lightRay );
5254
+ rec.pdf = 1.0;
5255
+ rec.emission = light.color * light.intensity * distanceFalloff;
5256
+ rec.type = light.type;
5257
+ return rec;
5258
+
5259
+ } else if ( light.type == DIR_LIGHT_TYPE ) {
5260
+
5261
+ LightSampleRec rec;
5262
+ rec.hit = true;
5263
+ rec.dist = 1e10;
5264
+ rec.direction = light.u;
5265
+ rec.pdf = 1.0;
5266
+ rec.emission = light.color * light.intensity;
5267
+ rec.type = light.type;
5268
+
5269
+ return rec;
4920
5270
 
4921
5271
  } else {
4922
5272
 
4923
5273
  // sample the light
4924
- return randomAreaLightSample( light, rayOrigin );
5274
+ return randomAreaLightSample( light, rayOrigin, ruv.yz );
4925
5275
 
4926
5276
  }
4927
5277
 
@@ -4952,6 +5302,64 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
4952
5302
 
4953
5303
  }
4954
5304
 
5305
+ `;
5306
+
5307
+ const shaderRandFunctions = /* glsl */`
5308
+
5309
+ // https://www.shadertoy.com/view/wltcRS
5310
+ uvec4 WHITE_NOISE_SEED;
5311
+
5312
+ void rng_initialize( vec2 p, int frame ) {
5313
+
5314
+ // white noise seed
5315
+ WHITE_NOISE_SEED = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
5316
+
5317
+ }
5318
+
5319
+ // https://www.pcg-random.org/
5320
+ void pcg4d( inout uvec4 v ) {
5321
+
5322
+ v = v * 1664525u + 1013904223u;
5323
+ v.x += v.y * v.w;
5324
+ v.y += v.z * v.x;
5325
+ v.z += v.x * v.y;
5326
+ v.w += v.y * v.z;
5327
+ v = v ^ ( v >> 16u );
5328
+ v.x += v.y*v.w;
5329
+ v.y += v.z*v.x;
5330
+ v.z += v.x*v.y;
5331
+ v.w += v.y*v.z;
5332
+
5333
+ }
5334
+
5335
+ // returns [ 0, 1 ]
5336
+ float rand() {
5337
+
5338
+ pcg4d( WHITE_NOISE_SEED );
5339
+ return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
5340
+
5341
+ }
5342
+
5343
+ vec2 rand2() {
5344
+
5345
+ pcg4d( WHITE_NOISE_SEED );
5346
+ return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
5347
+
5348
+ }
5349
+
5350
+ vec3 rand3() {
5351
+
5352
+ pcg4d( WHITE_NOISE_SEED );
5353
+ return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
5354
+
5355
+ }
5356
+
5357
+ vec4 rand4() {
5358
+
5359
+ pcg4d( WHITE_NOISE_SEED );
5360
+ return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
5361
+
5362
+ }
4955
5363
  `;
4956
5364
 
4957
5365
  function copyArrayToArray( fromArray, fromStride, toArray, toStride, offset ) {
@@ -5213,6 +5621,7 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5213
5621
  filterGlossyFactor: { value: 0.0 },
5214
5622
 
5215
5623
  backgroundAlpha: { value: 1.0 },
5624
+ sobolTexture: { value: null },
5216
5625
  },
5217
5626
 
5218
5627
  vertexShader: /* glsl */`
@@ -5239,6 +5648,9 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5239
5648
  vec4 envMapTexelToLinear( vec4 a ) { return a; }
5240
5649
  #include <common>
5241
5650
 
5651
+ ${ shaderRandFunctions }
5652
+ ${ shaderSobolCommon }
5653
+ ${ shaderSobolSampling }
5242
5654
  ${ threeMeshBvh.shaderStructs }
5243
5655
  ${ threeMeshBvh.shaderIntersectFunction }
5244
5656
  ${ shaderMaterialStructs }
@@ -5299,9 +5711,9 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5299
5711
 
5300
5712
  }
5301
5713
 
5302
- vec3 sampleBackground( vec3 direction ) {
5714
+ vec3 sampleBackground( vec3 direction, vec2 uv ) {
5303
5715
 
5304
- vec3 sampleDir = normalize( direction + getHemisphereSample( direction, rand2() ) * 0.5 * backgroundBlur );
5716
+ vec3 sampleDir = normalize( direction + getHemisphereSample( direction, uv ) * 0.5 * backgroundBlur );
5305
5717
 
5306
5718
  #if FEATURE_BACKGROUND_MAP
5307
5719
 
@@ -5327,6 +5739,8 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5327
5739
 
5328
5740
  color = vec3( 1.0 );
5329
5741
 
5742
+ // TODO: we should be using sobol sampling here instead of rand but the sobol bounce and path indices need to be incremented
5743
+ // and then reset.
5330
5744
  for ( int i = 0; i < traversals; i ++ ) {
5331
5745
 
5332
5746
  if ( bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
@@ -5453,14 +5867,6 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5453
5867
 
5454
5868
  }
5455
5869
 
5456
- // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
5457
- // erichlof/THREE.js-PathTracing-Renderer/
5458
- float tentFilter( float x ) {
5459
-
5460
- return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
5461
-
5462
- }
5463
-
5464
5870
  vec3 ndcToRayOrigin( vec2 coord ) {
5465
5871
 
5466
5872
  vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( coord, - 1.0, 1.0 );
@@ -5472,13 +5878,13 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5472
5878
  vec2 ssd = vec2( 1.0 ) / resolution;
5473
5879
 
5474
5880
  // Jitter the camera ray by finding a uv coordinate at a random sample
5475
- // around this pixel's UV coordinate
5476
- vec2 jitteredUv = vUv + vec2( tentFilter( rand() ) * ssd.x, tentFilter( rand() ) * ssd.y );
5881
+ // around this pixel's UV coordinate for AA
5882
+ vec2 ruv = sobol2( 0 );
5883
+ vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
5477
5884
 
5478
5885
  #if CAMERA_TYPE == 2
5479
5886
 
5480
5887
  // Equirectangular projection
5481
-
5482
5888
  vec4 rayDirection4 = vec4( equirectUvToDirection( jitteredUv ), 0.0 );
5483
5889
  vec4 rayOrigin4 = vec4( 0.0, 0.0, 0.0, 1.0 );
5484
5890
 
@@ -5492,20 +5898,17 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5492
5898
 
5493
5899
  // get [- 1, 1] normalized device coordinates
5494
5900
  vec2 ndc = 2.0 * jitteredUv - vec2( 1.0 );
5495
-
5496
5901
  rayOrigin = ndcToRayOrigin( ndc );
5497
5902
 
5498
5903
  #if CAMERA_TYPE == 1
5499
5904
 
5500
5905
  // Orthographic projection
5501
-
5502
5906
  rayDirection = ( cameraWorldMatrix * vec4( 0.0, 0.0, - 1.0, 0.0 ) ).xyz;
5503
5907
  rayDirection = normalize( rayDirection );
5504
5908
 
5505
5909
  #else
5506
5910
 
5507
5911
  // Perspective projection
5508
-
5509
5912
  rayDirection = normalize( mat3(cameraWorldMatrix) * ( invProjectionMatrix * vec4( ndc, 0.0, 1.0 ) ).xyz );
5510
5913
 
5511
5914
  #endif
@@ -5519,17 +5922,17 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5519
5922
  vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
5520
5923
 
5521
5924
  // get the aperture sample
5522
- vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
5925
+ // if blades === 0 then we assume a circle
5926
+ vec3 shapeUVW= sobol3( 1 );
5927
+ int blades = physicalCamera.apertureBlades;
5928
+ float anamorphicRatio = physicalCamera.anamorphicRatio;
5929
+ vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularNGon( blades, shapeUVW );
5930
+ apertureSample *= physicalCamera.bokehSize * 0.5 * 1e-3;
5523
5931
 
5524
5932
  // rotate the aperture shape
5525
- float ac = cos( physicalCamera.apertureRotation );
5526
- float as = sin( physicalCamera.apertureRotation );
5527
- apertureSample = vec2(
5528
- apertureSample.x * ac - apertureSample.y * as,
5529
- apertureSample.x * as + apertureSample.y * ac
5530
- );
5531
- apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
5532
- apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
5933
+ apertureSample =
5934
+ rotateVector( apertureSample, physicalCamera.apertureRotation ) *
5935
+ saturate( vec2( anamorphicRatio, 1.0 / anamorphicRatio ) );
5533
5936
 
5534
5937
  // create the new ray
5535
5938
  rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
@@ -5545,6 +5948,8 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5545
5948
  void main() {
5546
5949
 
5547
5950
  rng_initialize( gl_FragCoord.xy, seed );
5951
+ sobolPixelIndex = ( uint( gl_FragCoord.x ) << 16 ) | ( uint( gl_FragCoord.y ) );
5952
+ sobolPathIndex = uint( seed );
5548
5953
 
5549
5954
  vec3 rayDirection;
5550
5955
  vec3 rayOrigin;
@@ -5578,6 +5983,8 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5578
5983
 
5579
5984
  for ( i = 0; i < bounces; i ++ ) {
5580
5985
 
5986
+ sobolBounceIndex ++;
5987
+
5581
5988
  bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
5582
5989
 
5583
5990
  LightSampleRec lightHit = lightsClosestHit( lights.tex, lights.count, rayOrigin, rayDirection );
@@ -5592,9 +5999,8 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5592
5999
 
5593
6000
  #if FEATURE_MIS
5594
6001
 
5595
- // NOTE: we skip MIS for spotlights since we haven't fixed the forward
5596
- // path tracing code path, yet
5597
- if ( lightHit.type == SPOT_LIGHT_TYPE ) {
6002
+ // NOTE: we skip MIS for punctual lights since they are not supported in forward PT case
6003
+ if ( lightHit.type == SPOT_LIGHT_TYPE || lightHit.type == DIR_LIGHT_TYPE || lightHit.type == POINT_LIGHT_TYPE ) {
5598
6004
 
5599
6005
  gl_FragColor.rgb += lightHit.emission * throughputColor;
5600
6006
 
@@ -5621,7 +6027,7 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5621
6027
 
5622
6028
  if ( i == 0 || transmissiveRay ) {
5623
6029
 
5624
- gl_FragColor.rgb += sampleBackground( envRotation3x3 * rayDirection ) * throughputColor;
6030
+ gl_FragColor.rgb += sampleBackground( envRotation3x3 * rayDirection, sobol2( 2 ) ) * throughputColor;
5625
6031
  gl_FragColor.a = backgroundAlpha;
5626
6032
 
5627
6033
  } else {
@@ -5630,7 +6036,7 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5630
6036
 
5631
6037
  // get the PDF of the hit envmap point
5632
6038
  vec3 envColor;
5633
- float envPdf = envMapSample( envRotation3x3 * rayDirection, envMapInfo, envColor );
6039
+ float envPdf = sampleEnvMap( envMapInfo, envRotation3x3 * rayDirection, envColor );
5634
6040
  envPdf /= float( lights.count + 1u );
5635
6041
 
5636
6042
  // and weight the contribution
@@ -5715,7 +6121,7 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5715
6121
  || useAlphaTest && albedo.a < alphaTest
5716
6122
 
5717
6123
  // opacity
5718
- || material.transparent && ! useAlphaTest && albedo.a < rand()
6124
+ || material.transparent && ! useAlphaTest && albedo.a < sobol( 3 )
5719
6125
  ) {
5720
6126
 
5721
6127
  vec3 point = rayOrigin + rayDirection * dist;
@@ -5776,6 +6182,15 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5776
6182
  }
5777
6183
 
5778
6184
  // normal
6185
+ if ( material.flatShading ) {
6186
+
6187
+ // if we're rendering a flat shaded object then use the face normals - the face normal
6188
+ // is provided based on the side the ray hits the mesh so flip it to align with the
6189
+ // interpolated vertex normals.
6190
+ normal = faceNormal * side;
6191
+
6192
+ }
6193
+
5779
6194
  vec3 baseNormal = normal;
5780
6195
  if ( material.normalMap != - 1 ) {
5781
6196
 
@@ -5958,7 +6373,7 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5958
6373
  vec3 clearcoatOutgoing = - normalize( clearcoatInvBasis * rayDirection );
5959
6374
  sampleRec = bsdfSample( outgoing, clearcoatOutgoing, normalBasis, invBasis, clearcoatNormalBasis, clearcoatInvBasis, surfaceRec );
5960
6375
 
5961
- isShadowRay = sampleRec.specularPdf < rand();
6376
+ isShadowRay = sampleRec.specularPdf < sobol( 4 );
5962
6377
 
5963
6378
  // adjust the hit point by the surface normal by a factor of some offset and the
5964
6379
  // maximum component-wise value of the current point to accommodate floating point
@@ -5975,10 +6390,10 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
5975
6390
  #if FEATURE_MIS
5976
6391
 
5977
6392
  // uniformly pick a light or environment map
5978
- if( rand() > 1.0 / float( lights.count + 1u ) ) {
6393
+ if( sobol( 5 ) > 1.0 / float( lights.count + 1u ) ) {
5979
6394
 
5980
6395
  // sample a light or environment
5981
- LightSampleRec lightSampleRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin );
6396
+ LightSampleRec lightSampleRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, sobol3( 6 ) );
5982
6397
 
5983
6398
  bool isSampleBelowSurface = dot( faceNormal, lightSampleRec.direction ) < 0.0;
5984
6399
  if ( isSampleBelowSurface ) {
@@ -6002,7 +6417,7 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
6002
6417
 
6003
6418
  // weight the direct light contribution
6004
6419
  float lightPdf = lightSampleRec.pdf / float( lights.count + 1u );
6005
- float misWeight = misHeuristic( lightPdf, lightMaterialPdf );
6420
+ float misWeight = lightSampleRec.type == SPOT_LIGHT_TYPE || lightSampleRec.type == DIR_LIGHT_TYPE || lightSampleRec.type == POINT_LIGHT_TYPE ? 1.0 : misHeuristic( lightPdf, lightMaterialPdf );
6006
6421
  gl_FragColor.rgb += lightSampleRec.emission * throughputColor * sampleColor * misWeight / lightPdf;
6007
6422
 
6008
6423
  }
@@ -6013,7 +6428,7 @@ LightSampleRec randomLightSample( sampler2D lights, sampler2DArray iesProfiles,
6013
6428
 
6014
6429
  // find a sample in the environment map to include in the contribution
6015
6430
  vec3 envColor, envDirection;
6016
- float envPdf = randomEnvMapSample( envMapInfo, envColor, envDirection );
6431
+ float envPdf = sampleEnvMapProbability( envMapInfo, sobol2( 7 ), envColor, envDirection );
6017
6432
  envDirection = invEnvRotation3x3 * envDirection;
6018
6433
 
6019
6434
  // this env sampling is not set up for transmissive sampling and yields overly bright