three-gpu-pathtracer 0.0.1

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.
@@ -0,0 +1,1840 @@
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-mesh-bvh/src/workers/GenerateMeshBVHWorker.js'), 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-mesh-bvh/src/workers/GenerateMeshBVHWorker.js', '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.MeshBVHLib, global.THREE));
5
+ })(this, (function (exports, three, Pass_js, threeMeshBvh, GenerateMeshBVHWorker_js, BufferGeometryUtils_js) { 'use strict';
6
+
7
+ function* renderTask() {
8
+
9
+ const { _fsQuad, _renderer, target, camera, material } = this;
10
+ while ( true ) {
11
+
12
+ material.opacity = 1 / ( this.samples + 1 );
13
+ material.seed ++;
14
+
15
+ const w = target.width;
16
+ const h = target.height;
17
+ camera.setViewOffset(
18
+ w, h,
19
+ Math.random() - 0.5, Math.random() - 0.5,
20
+ w, h,
21
+ );
22
+ camera.updateProjectionMatrix();
23
+
24
+ const tx = this.tiles.x || 1;
25
+ const ty = this.tiles.y || 1;
26
+ const totalTiles = tx * ty;
27
+ const dprInv = ( 1 / _renderer.getPixelRatio() );
28
+ for ( let y = 0; y < ty; y ++ ) {
29
+
30
+ for ( let x = 0; x < tx; x ++ ) {
31
+
32
+ material.cameraWorldMatrix.copy( camera.matrixWorld );
33
+ material.invProjectionMatrix.copy( camera.projectionMatrixInverse );
34
+
35
+ const ogRenderTarget = _renderer.getRenderTarget();
36
+ const ogAutoClear = _renderer.autoClear;
37
+
38
+ // three.js renderer takes values relative to the current pixel ratio
39
+ _renderer.setRenderTarget( target );
40
+ _renderer.setScissorTest( true );
41
+ _renderer.setScissor(
42
+ dprInv * Math.ceil( x * w / tx ),
43
+ dprInv * Math.ceil( ( ty - y - 1 ) * h / ty ),
44
+ dprInv * Math.ceil( w / tx ),
45
+ dprInv * Math.ceil( h / ty ) );
46
+ _renderer.autoClear = false;
47
+ _fsQuad.render( _renderer );
48
+
49
+ _renderer.setScissorTest( false );
50
+ _renderer.setRenderTarget( ogRenderTarget );
51
+ _renderer.autoClear = ogAutoClear;
52
+
53
+ this.samples += ( 1 / totalTiles );
54
+
55
+ yield;
56
+
57
+ }
58
+
59
+ }
60
+
61
+ this.samples = Math.round( this.samples );
62
+
63
+ }
64
+
65
+ }
66
+
67
+ const ogClearColor = new three.Color();
68
+ class PathTracingRenderer {
69
+
70
+ get material() {
71
+
72
+ return this._fsQuad.material;
73
+
74
+ }
75
+
76
+ set material( v ) {
77
+
78
+ this._fsQuad.material = v;
79
+
80
+ }
81
+
82
+ constructor( renderer ) {
83
+
84
+ this.camera = null;
85
+ this.tiles = new three.Vector2( 1, 1 );
86
+ this.target = new three.WebGLRenderTarget( 1, 1, {
87
+ format: three.RGBAFormat,
88
+ type: three.FloatType,
89
+ } );
90
+ this.samples = 0;
91
+ this.stableNoise = false;
92
+ this._renderer = renderer;
93
+ this._fsQuad = new Pass_js.FullScreenQuad( null );
94
+ this._task = null;
95
+
96
+ }
97
+
98
+ setSize( w, h ) {
99
+
100
+ this.target.setSize( w, h );
101
+ this.reset();
102
+
103
+ }
104
+
105
+ reset() {
106
+
107
+ const renderer = this._renderer;
108
+ const target = this.target;
109
+ const ogRenderTarget = renderer.getRenderTarget();
110
+ const ogClearAlpha = renderer.getClearAlpha();
111
+ renderer.getClearColor( ogClearColor );
112
+
113
+ renderer.setRenderTarget( target );
114
+ renderer.setClearColor( 0, 0 );
115
+ renderer.clearColor();
116
+
117
+ renderer.setClearColor( ogClearColor, ogClearAlpha );
118
+ renderer.setRenderTarget( ogRenderTarget );
119
+
120
+ this.samples = 0;
121
+ this._task = null;
122
+
123
+ if ( this.stableNoise ) {
124
+
125
+ this.material.seed = 0;
126
+
127
+ }
128
+
129
+ }
130
+
131
+ update() {
132
+
133
+ if ( ! this._task ) {
134
+
135
+ this._task = renderTask.call( this );
136
+
137
+ }
138
+
139
+ this._task.next();
140
+
141
+ }
142
+
143
+ }
144
+
145
+ function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
146
+
147
+ if ( ! Array.isArray( materials ) ) {
148
+
149
+ materials = [ materials ];
150
+
151
+ }
152
+
153
+ const vertCount = geometry.attributes.position.count;
154
+ const materialArray = new Uint8Array( vertCount );
155
+ let groups = geometry.groups;
156
+ if ( groups.length === 0 ) {
157
+
158
+ groups = [ { count: vertCount, start: 0, materialIndex: 0 } ];
159
+
160
+ }
161
+
162
+ for ( let i = 0; i < groups.length; i ++ ) {
163
+
164
+ const group = groups[ i ];
165
+ const { count, start } = group;
166
+ const endCount = Math.min( count, vertCount - start );
167
+ const mat = materials[ group.materialIndex ];
168
+ const materialIndex = allMaterials.indexOf( mat );
169
+
170
+ for ( let j = 0; j < endCount; j ++ ) {
171
+
172
+ materialArray[ start + j ] = materialIndex;
173
+
174
+ }
175
+
176
+ }
177
+
178
+ return new three.BufferAttribute( materialArray, 1, false );
179
+
180
+ }
181
+
182
+ function mergeMeshes( meshes, options = {} ) {
183
+
184
+ options = { attributes: null, cloneGeometry: true, ...options };
185
+
186
+ const transformedGeometry = [];
187
+ const materialSet = new Set();
188
+ for ( let i = 0, l = meshes.length; i < l; i ++ ) {
189
+
190
+ // save any materials
191
+ const mesh = meshes[ i ];
192
+ if ( mesh.visible === false ) continue;
193
+
194
+ if ( Array.isArray( mesh.material ) ) {
195
+
196
+ mesh.material.forEach( m => materialSet.add( m ) );
197
+
198
+ } else {
199
+
200
+ materialSet.add( mesh.material );
201
+
202
+ }
203
+
204
+ }
205
+
206
+ const materials = Array.from( materialSet );
207
+ for ( let i = 0, l = meshes.length; i < l; i ++ ) {
208
+
209
+ // ensure the matrix world is up to date
210
+ const mesh = meshes[ i ];
211
+ if ( mesh.visible === false ) continue;
212
+
213
+ mesh.updateMatrixWorld();
214
+
215
+ // apply the matrix world to the geometry
216
+ const originalGeometry = meshes[ i ].geometry;
217
+ let geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
218
+ geometry.applyMatrix4( mesh.matrixWorld );
219
+
220
+ const attrs = options.attributes;
221
+ if ( ! geometry.attributes.normal && ( attrs && attrs.includes( 'normal' ) ) ) {
222
+
223
+ geometry.computeVertexNormals();
224
+
225
+ }
226
+
227
+ if ( ! geometry.attributes.uv && ( attrs && attrs.includes( 'uv' ) ) ) {
228
+
229
+ const vertCount = geometry.attributes.position.count;
230
+ geometry.setAttribute( 'uv', new three.BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
231
+
232
+ }
233
+
234
+ if ( ! geometry.attributes.tangent && ( attrs && attrs.includes( 'tangent' ) ) ) {
235
+
236
+ if ( mesh.material.normalMap ) {
237
+
238
+ // computeTangents requires an index buffer
239
+ if ( geometry.index === null ) {
240
+
241
+ geometry = BufferGeometryUtils_js.mergeVertices( geometry );
242
+
243
+ }
244
+
245
+ geometry.computeTangents();
246
+
247
+ } else {
248
+
249
+ const vertCount = geometry.attributes.position.count;
250
+ geometry.setAttribute( 'tangent', new three.BufferAttribute( new Float32Array( vertCount * 4 ), 4, false ) );
251
+
252
+ }
253
+
254
+ }
255
+
256
+ if ( ! geometry.index ) {
257
+
258
+ // TODO: compute a typed array
259
+ const indexCount = geometry.attributes.position.count;
260
+ const array = new Array( indexCount );
261
+ for ( let i = 0; i < indexCount; i ++ ) {
262
+
263
+ array[ i ] = i;
264
+
265
+ }
266
+
267
+ geometry.setIndex( array );
268
+
269
+ }
270
+
271
+ // trim any unneeded attributes
272
+ if ( options.attributes ) {
273
+
274
+ for ( const key in geometry.attributes ) {
275
+
276
+ if ( ! options.attributes.includes( key ) ) {
277
+
278
+ geometry.deleteAttribute( key );
279
+
280
+ }
281
+
282
+ }
283
+
284
+ }
285
+
286
+ // create the material index attribute
287
+ const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
288
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
289
+
290
+ transformedGeometry.push( geometry );
291
+
292
+ }
293
+
294
+ const textureSet = new Set();
295
+ materials.forEach( material => {
296
+
297
+ for ( const key in material ) {
298
+
299
+ const value = material[ key ];
300
+ if ( value && value.isTexture ) {
301
+
302
+ textureSet.add( value );
303
+
304
+ }
305
+
306
+ }
307
+
308
+ } );
309
+
310
+ const geometry = BufferGeometryUtils_js.mergeBufferGeometries( transformedGeometry, false );
311
+ const textures = Array.from( textureSet );
312
+ return { geometry, materials, textures };
313
+
314
+ }
315
+
316
+ class PathTracingSceneGenerator {
317
+
318
+ constructor() {
319
+
320
+ this.bvhGenerator = new GenerateMeshBVHWorker_js.GenerateMeshBVHWorker();
321
+
322
+ }
323
+
324
+ async generate( scene, options = {} ) {
325
+
326
+ const { bvhGenerator } = this;
327
+ const meshes = [];
328
+
329
+ scene.traverse( c => {
330
+
331
+ if ( c.isMesh ) {
332
+
333
+ meshes.push( c );
334
+
335
+ }
336
+
337
+ } );
338
+
339
+ const { geometry, materials, textures } = mergeMeshes( meshes, { attributes: [ 'position', 'normal', 'tangent', 'uv' ] } );
340
+ const bvhPromise = bvhGenerator.generate( geometry, { strategy: threeMeshBvh.SAH, ...options, maxLeafTris: 1 } );
341
+
342
+ return {
343
+ scene,
344
+ materials,
345
+ textures,
346
+ bvh: await bvhPromise,
347
+ };
348
+
349
+ }
350
+
351
+ dispose() {
352
+
353
+ this.bvhGenerator.terminate();
354
+
355
+ }
356
+
357
+ }
358
+
359
+ // https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
360
+
361
+ function isTypedArray( arr ) {
362
+
363
+ return arr.buffer instanceof ArrayBuffer && 'BYTES_PER_ELEMENT' in arr;
364
+
365
+ }
366
+
367
+ class MaterialReducer {
368
+
369
+ constructor() {
370
+
371
+ const ignoreKeys = new Set();
372
+ ignoreKeys.add( 'uuid' );
373
+
374
+ this.ignoreKeys = ignoreKeys;
375
+ this.shareTextures = true;
376
+ this.textures = [];
377
+ this.materials = [];
378
+
379
+ }
380
+
381
+ areEqual( objectA, objectB ) {
382
+
383
+ const keySet = new Set();
384
+ const traverseSet = new Set();
385
+ const ignoreKeys = this.ignoreKeys;
386
+
387
+ const traverse = ( a, b ) => {
388
+
389
+ if ( a === b ) {
390
+
391
+ return true;
392
+
393
+ }
394
+
395
+ if ( a && b && a instanceof Object && b instanceof Object ) {
396
+
397
+ if ( traverseSet.has( a ) || traverseSet.has( b ) ) {
398
+
399
+ throw new Error( 'MaterialReducer: Material is recursive.' );
400
+
401
+ }
402
+
403
+ const aIsElement = a instanceof Element;
404
+ const bIsElement = b instanceof Element;
405
+ if ( aIsElement || bIsElement ) {
406
+
407
+ if ( aIsElement !== bIsElement || ! ( a instanceof Image ) || ! ( b instanceof Image ) ) {
408
+
409
+ return false;
410
+
411
+ }
412
+
413
+ return a.src === b.src;
414
+
415
+ }
416
+
417
+ const aIsImageBitmap = a instanceof ImageBitmap;
418
+ const bIsImageBitmap = b instanceof ImageBitmap;
419
+ if ( aIsImageBitmap || bIsImageBitmap ) {
420
+
421
+ return false;
422
+
423
+ }
424
+
425
+ if ( a.equals ) {
426
+
427
+ return a.equals( b );
428
+
429
+ }
430
+
431
+ const aIsTypedArray = isTypedArray( a );
432
+ const bIsTypedArray = isTypedArray( b );
433
+ if ( aIsTypedArray || bIsTypedArray ) {
434
+
435
+ if ( aIsTypedArray !== bIsTypedArray || a.constructor !== b.constructor || a.length !== b.length ) {
436
+
437
+ return false;
438
+
439
+ }
440
+
441
+ for ( let i = 0, l = a.length; i < l; i ++ ) {
442
+
443
+ if ( a[ i ] !== b[ i ] ) return false;
444
+
445
+ }
446
+
447
+ return true;
448
+
449
+ }
450
+
451
+ traverseSet.add( a );
452
+ traverseSet.add( b );
453
+
454
+ keySet.clear();
455
+ for ( const key in a ) {
456
+
457
+ if ( ! a.hasOwnProperty( key ) || a[ key ] instanceof Function || ignoreKeys.has( key ) ) {
458
+
459
+ continue;
460
+
461
+ }
462
+
463
+ keySet.add( key );
464
+
465
+ }
466
+
467
+ for ( const key in b ) {
468
+
469
+ if ( ! b.hasOwnProperty( key ) || b[ key ] instanceof Function || ignoreKeys.has( key ) ) {
470
+
471
+ continue;
472
+
473
+ }
474
+
475
+ keySet.add( key );
476
+
477
+ }
478
+
479
+ const keys = Array.from( keySet.values() );
480
+ let result = true;
481
+ for ( const i in keys ) {
482
+
483
+ const key = keys[ i ];
484
+ if ( ignoreKeys.has( key ) ) {
485
+
486
+ continue;
487
+
488
+ }
489
+
490
+ result = traverse( a[ key ], b[ key ] );
491
+ if ( ! result ) {
492
+
493
+ break;
494
+
495
+ }
496
+
497
+ }
498
+
499
+ traverseSet.delete( a );
500
+ traverseSet.delete( b );
501
+ return result;
502
+
503
+ }
504
+
505
+ return false;
506
+
507
+ };
508
+
509
+ return traverse( objectA, objectB );
510
+
511
+ }
512
+
513
+ process( object ) {
514
+
515
+ const { textures, materials } = this;
516
+ let replaced = 0;
517
+
518
+ const processMaterial = material => {
519
+
520
+ // Check if another material matches this one
521
+ let foundMaterial = null;
522
+ for ( const i in materials ) {
523
+
524
+ const otherMaterial = materials[ i ];
525
+ if ( this.areEqual( material, otherMaterial ) ) {
526
+
527
+ foundMaterial = otherMaterial;
528
+
529
+ }
530
+
531
+ }
532
+
533
+ if ( foundMaterial ) {
534
+
535
+ replaced ++;
536
+ return foundMaterial;
537
+
538
+ } else {
539
+
540
+ materials.push( material );
541
+
542
+ if ( this.shareTextures ) {
543
+
544
+ // See if there's another texture that matches the ones on this material
545
+ for ( const key in material ) {
546
+
547
+ if ( ! material.hasOwnProperty( key ) ) continue;
548
+
549
+ const value = material[ key ];
550
+ if ( value && value.isTexture && value.image instanceof Image ) {
551
+
552
+ let foundTexture = null;
553
+ for ( const i in textures ) {
554
+
555
+ const texture = textures[ i ];
556
+ if ( this.areEqual( texture, value ) ) {
557
+
558
+ foundTexture = texture;
559
+ break;
560
+
561
+ }
562
+
563
+ }
564
+
565
+ if ( foundTexture ) {
566
+
567
+ material[ key ] = foundTexture;
568
+
569
+ } else {
570
+
571
+ textures.push( value );
572
+
573
+ }
574
+
575
+ }
576
+
577
+ }
578
+
579
+ }
580
+
581
+ return material;
582
+
583
+ }
584
+
585
+ };
586
+
587
+ object.traverse( c => {
588
+
589
+ if ( c.isMesh && c.material ) {
590
+
591
+ const material = c.material;
592
+ if ( Array.isArray( material ) ) {
593
+
594
+ for ( let i = 0; i < material.length; i ++ ) {
595
+
596
+ material[ i ] = processMaterial( material[ i ] );
597
+
598
+ }
599
+
600
+ } else {
601
+
602
+ c.material = processMaterial( material );
603
+
604
+ }
605
+
606
+ }
607
+
608
+ } );
609
+
610
+ return { replaced, retained: materials.length };
611
+
612
+ }
613
+
614
+ }
615
+
616
+ class MaterialStructUniform {
617
+
618
+ constructor() {
619
+
620
+ this.init();
621
+
622
+ }
623
+
624
+ init() {
625
+
626
+ this.color = new three.Color( 0xffffff );
627
+ this.map = - 1;
628
+
629
+ this.metalness = 1.0;
630
+ this.metalnessMap = - 1;
631
+
632
+ this.roughness = 1.0;
633
+ this.roughnessMap = - 1;
634
+
635
+ this.ior = 1.0;
636
+ this.transmission = 0.0;
637
+ this.transmissionMap = - 1;
638
+
639
+ this.emissive = new three.Color( 0 );
640
+ this.emissiveIntensity = 1.0;
641
+ this.emissiveMap = - 1;
642
+
643
+ this.normalMap = - 1;
644
+ this.normalScale = new three.Vector2( 1, 1 );
645
+
646
+ this.opacity = 1.0;
647
+ this.alphaTest = 0.0;
648
+
649
+ // TODO: Clearcoat
650
+
651
+ // TODO: Sheen
652
+
653
+ }
654
+
655
+ updateFrom( material, textures = [] ) {
656
+
657
+ this.init();
658
+
659
+ // color
660
+ if ( 'color' in material ) this.color.copy( material.color );
661
+ else material.color.set( 0xffffff );
662
+
663
+ this.map = textures.indexOf( material.map );
664
+
665
+ // metalness
666
+ if ( 'metalness' in material ) this.metalness = material.metalness;
667
+ else this.metalness = 1.0;
668
+
669
+ this.metalnessMap = textures.indexOf( material.metalnessMap );
670
+
671
+ // roughness
672
+ if ( 'roughness' in material ) this.roughness = material.roughness;
673
+ else this.roughness = 1.0;
674
+
675
+ this.roughnessMap = textures.indexOf( material.roughnessMap );
676
+
677
+ // transmission
678
+ if ( 'ior' in material ) this.ior = material.ior;
679
+ else this.ior = 1.0;
680
+
681
+ if ( 'transmission' in material ) this.transmission = material.transmission;
682
+ else this.transmission = 0.0;
683
+
684
+ if ( 'transmissionMap' in material ) this.transmissionMap = textures.indexOf( material.transmissionMap );
685
+
686
+ // emission
687
+ if ( 'emissive' in material ) this.emissive.copy( material.emissive );
688
+ else this.emissive.set( 0 );
689
+
690
+ if ( 'emissiveIntensity' in material ) this.emissiveIntensity = material.emissiveIntensity;
691
+ else this.emissiveIntensity = 1.0;
692
+
693
+ this.emissiveMap = textures.indexOf( material.emissiveMap );
694
+
695
+ // normals
696
+ this.normalMap = textures.indexOf( material.normalMap );
697
+ if ( 'normalScale' in material ) this.normalScale.copy( material.normalScale );
698
+ else this.normalScale.set( 1, 1 );
699
+
700
+ // opacity
701
+ this.opacity = material.opacity;
702
+
703
+ // alpha test
704
+ this.alphaTest = material.alphaTest;
705
+
706
+ }
707
+
708
+ }
709
+
710
+ class MaterialStructArrayUniform extends Array {
711
+
712
+ updateFrom( materials, textures ) {
713
+
714
+ while ( this.length > materials.length ) this.pop();
715
+ while ( this.length < materials.length ) this.push( new MaterialStructUniform() );
716
+
717
+ for ( let i = 0, l = this.length; i < l; i ++ ) {
718
+
719
+ this[ i ].updateFrom( materials[ i ], textures );
720
+
721
+ }
722
+
723
+ }
724
+
725
+ }
726
+
727
+ const prevColor = new three.Color();
728
+ class RenderTarget2DArray extends three.WebGLArrayRenderTarget {
729
+
730
+ constructor( ...args ) {
731
+
732
+ super( ...args );
733
+
734
+ const tex = this.texture;
735
+ tex.format = three.RGBAFormat;
736
+ tex.type = three.UnsignedByteType;
737
+ tex.minFilter = three.LinearFilter;
738
+ tex.magFilter = three.LinearFilter;
739
+ tex.wrapS = three.RepeatWrapping;
740
+ tex.wrapT = three.RepeatWrapping;
741
+ tex.setTextures = ( ...args ) => {
742
+
743
+ this.setTextures( ...args );
744
+
745
+ };
746
+
747
+ const fsQuad = new Pass_js.FullScreenQuad( new three.MeshBasicMaterial() );
748
+ this.fsQuad = fsQuad;
749
+
750
+ }
751
+
752
+ setTextures( renderer, width, height, textures ) {
753
+
754
+ // save previous renderer state
755
+ const prevRenderTarget = renderer.getRenderTarget();
756
+ const prevToneMapping = renderer.toneMapping;
757
+ const prevAlpha = renderer.getClearAlpha();
758
+ renderer.getClearColor( prevColor );
759
+
760
+ // resize the render target
761
+ const depth = textures.length;
762
+ this.setSize( width, height, depth );
763
+ renderer.setClearColor( 0, 0 );
764
+ renderer.toneMapping = three.NoToneMapping;
765
+
766
+ // render each texture into each layer of the target
767
+ const fsQuad = this.fsQuad;
768
+ for ( let i = 0, l = depth; i < l; i ++ ) {
769
+
770
+ const texture = textures[ i ];
771
+ fsQuad.material.map = texture;
772
+ fsQuad.material.transparent = true;
773
+
774
+ renderer.setRenderTarget( this, i );
775
+ fsQuad.render( renderer );
776
+
777
+ }
778
+
779
+ // reset the renderer
780
+ fsQuad.material.map = null;
781
+ renderer.setClearColor( prevColor, prevAlpha );
782
+ renderer.setRenderTarget( prevRenderTarget );
783
+ renderer.toneMapping = prevToneMapping;
784
+
785
+ }
786
+
787
+ dispose() {
788
+
789
+ super.dispose();
790
+ this.fsQuad.dispose();
791
+
792
+ }
793
+
794
+ }
795
+
796
+ class MaterialBase extends three.ShaderMaterial {
797
+
798
+ constructor( shader ) {
799
+
800
+ super( shader );
801
+
802
+ for ( const key in this.uniforms ) {
803
+
804
+ Object.defineProperty( this, key, {
805
+
806
+ get() {
807
+
808
+ return this.uniforms[ key ].value;
809
+
810
+ },
811
+
812
+ set( v ) {
813
+
814
+ this.uniforms[ key ].value = v;
815
+
816
+ }
817
+
818
+ } );
819
+
820
+ }
821
+
822
+ }
823
+
824
+ // sets the given named define value and sets "needsUpdate" to true if it's different
825
+ setDefine( name, value = undefined ) {
826
+
827
+ if ( value === undefined || value === null ) {
828
+
829
+ if ( name in this.defines ) {
830
+
831
+ delete this.defines[ name ];
832
+ this.needsUpdate = true;
833
+
834
+ }
835
+
836
+ } else {
837
+
838
+ if ( this.defines[ name ] !== value ) {
839
+
840
+ this.defines[ name ] = value;
841
+ this.needsUpdate = true;
842
+
843
+ }
844
+
845
+ }
846
+
847
+ }
848
+
849
+ }
850
+
851
+ const shaderMaterialStructs = /* glsl */ `
852
+
853
+ struct Material {
854
+
855
+ vec3 color;
856
+ int map;
857
+
858
+ float metalness;
859
+ int metalnessMap;
860
+
861
+ float roughness;
862
+ int roughnessMap;
863
+
864
+ float ior;
865
+ float transmission;
866
+ int transmissionMap;
867
+
868
+ vec3 emissive;
869
+ float emissiveIntensity;
870
+ int emissiveMap;
871
+
872
+ int normalMap;
873
+ vec2 normalScale;
874
+
875
+ float opacity;
876
+ float alphaTest;
877
+
878
+ };
879
+
880
+ `;
881
+
882
+ const shaderGGXFunctions = /* glsl */`
883
+ // The GGX functions provide sampling and distribution information for normals as output so
884
+ // in order to get probability of scatter direction the half vector must be computed and provided.
885
+ // [0] https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
886
+ // [1] https://hal.archives-ouvertes.fr/hal-01509746/document
887
+ // [2] http://jcgt.org/published/0007/04/01/
888
+ // [4] http://jcgt.org/published/0003/02/03/
889
+
890
+ // trowbridge-reitz === GGX === GTR
891
+
892
+ vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float random1, float random2 ) {
893
+
894
+ // TODO: try GGXVNDF implementation from reference [2], here. Needs to update ggxDistribution
895
+ // function below, as well
896
+
897
+ // Implementation from reference [1]
898
+ // stretch view
899
+ vec3 V = normalize( vec3( roughnessX * incidentDir.x, roughnessY * incidentDir.y, incidentDir.z ) );
900
+
901
+ // orthonormal basis
902
+ vec3 T1 = ( V.z < 0.9999 ) ? normalize( cross( V, vec3( 0.0, 0.0, 1.0 ) ) ) : vec3( 1.0, 0.0, 0.0 );
903
+ vec3 T2 = cross( T1, V );
904
+
905
+ // sample point with polar coordinates (r, phi)
906
+ float a = 1.0 / ( 1.0 + V.z );
907
+ float r = sqrt( random1 );
908
+ float phi = ( random2 < a ) ? random2 / a * PI : PI + ( random2 - a ) / ( 1.0 - a ) * PI;
909
+ float P1 = r * cos( phi );
910
+ float P2 = r * sin( phi ) * ( ( random2 < a ) ? 1.0 : V.z );
911
+
912
+ // compute normal
913
+ vec3 N = P1 * T1 + P2 * T2 + V * sqrt( max( 0.0, 1.0 - P1 * P1 - P2 * P2 ) );
914
+
915
+ // unstretch
916
+ N = normalize( vec3( roughnessX * N.x, roughnessY * N.y, max( 0.0, N.z ) ) );
917
+
918
+ return N;
919
+
920
+ }
921
+
922
+ // Below are PDF and related functions for use in a Monte Carlo path tracer
923
+ // as specified in Appendix B of the following paper
924
+ // See equation (2) from reference [2]
925
+ float ggxLamda( float theta, float roughness ) {
926
+
927
+ float tanTheta = tan( theta );
928
+ float tanTheta2 = tanTheta * tanTheta;
929
+ float alpha2 = roughness * roughness;
930
+
931
+ float numerator = - 1.0 + sqrt( 1.0 + alpha2 * tanTheta2 );
932
+ return numerator / 2.0;
933
+
934
+ }
935
+
936
+ // See equation (2) from reference [2]
937
+ float ggxShadowMaskG1( float theta, float roughness ) {
938
+
939
+ return 1.0 / ( 1.0 + ggxLamda( theta, roughness ) );
940
+
941
+ }
942
+
943
+ // See equation (125) from reference [4]
944
+ float ggxShadowMaskG2( vec3 wi, vec3 wo, float roughness ) {
945
+
946
+ float incidentTheta = acos( wi.z );
947
+ float scatterTheta = acos( wo.z );
948
+ return 1.0 / ( 1.0 + ggxLamda( incidentTheta, roughness ) + ggxLamda( scatterTheta, roughness ) );
949
+
950
+ }
951
+
952
+ float ggxDistribution( vec3 halfVector, float roughness ) {
953
+
954
+ // See equation (33) from reference [0]
955
+ float a2 = roughness * roughness;
956
+ float cosTheta = halfVector.z;
957
+ float cosTheta4 = pow( cosTheta, 4.0 );
958
+
959
+ if ( cosTheta == 0.0 ) return 0.0;
960
+
961
+ float theta = acos( halfVector.z );
962
+ float tanTheta = tan( theta );
963
+ float tanTheta2 = pow( tanTheta, 2.0 );
964
+
965
+ float denom = PI * cosTheta4 * pow( a2 + tanTheta2, 2.0 );
966
+ return a2 / denom;
967
+
968
+ // See equation (1) from reference [2]
969
+ // const { x, y, z } = halfVector;
970
+ // const a2 = roughness * roughness;
971
+ // const mult = x * x / a2 + y * y / a2 + z * z;
972
+ // const mult2 = mult * mult;
973
+
974
+ // return 1.0 / Math.PI * a2 * mult2;
975
+
976
+ }
977
+
978
+ // See equation (3) from reference [2]
979
+ float ggxPDF( vec3 wi, vec3 halfVector, float roughness ) {
980
+
981
+ float incidentTheta = acos( wi.z );
982
+ float D = ggxDistribution( halfVector, roughness );
983
+ float G1 = ggxShadowMaskG1( incidentTheta, roughness );
984
+
985
+ return D * G1 * max( 0.0, dot( wi, halfVector ) ) / wi.z;
986
+
987
+ }
988
+ `;
989
+
990
+ const shaderMaterialSampling = /* glsl */`
991
+
992
+ struct SurfaceRec {
993
+ vec3 normal;
994
+ vec3 faceNormal;
995
+ bool frontFace;
996
+ float roughness;
997
+ float filteredRoughness;
998
+ float metalness;
999
+ vec3 color;
1000
+ vec3 emission;
1001
+ float transmission;
1002
+ float ior;
1003
+ };
1004
+
1005
+ struct SampleRec {
1006
+ float pdf;
1007
+ vec3 direction;
1008
+ vec3 color;
1009
+ };
1010
+
1011
+ ${ shaderGGXFunctions }
1012
+
1013
+ // diffuse
1014
+ float diffusePDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
1015
+
1016
+ // https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html#lightscattering/thescatteringpdf
1017
+ float cosValue = wi.z;
1018
+ return cosValue / PI;
1019
+
1020
+ }
1021
+
1022
+ vec3 diffuseDirection( vec3 wo, SurfaceRec surf ) {
1023
+
1024
+ vec3 lightDirection = randDirection();
1025
+ lightDirection.z += 1.0;
1026
+ lightDirection = normalize( lightDirection );
1027
+
1028
+ return lightDirection;
1029
+
1030
+ }
1031
+
1032
+ vec3 diffuseColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
1033
+
1034
+ // TODO: scale by 1 - F here
1035
+ // note on division by PI
1036
+ // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
1037
+ float metalFactor = ( 1.0 - surf.metalness ) * wi.z / ( PI * PI );
1038
+ float transmissionFactor = 1.0 - surf.transmission;
1039
+ return surf.color * metalFactor * transmissionFactor;
1040
+
1041
+ }
1042
+
1043
+ // specular
1044
+ float specularPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
1045
+
1046
+ // See equation (17) in http://jcgt.org/published/0003/02/03/
1047
+ float filteredRoughness = surf.filteredRoughness;
1048
+ vec3 halfVector = getHalfVector( wi, wo );
1049
+ return ggxPDF( wi, halfVector, filteredRoughness ) / ( 4.0 * dot( wi, halfVector ) );
1050
+
1051
+ }
1052
+
1053
+ vec3 specularDirection( vec3 wo, SurfaceRec surf ) {
1054
+
1055
+ // sample ggx vndf distribution which gives a new normal
1056
+ float filteredRoughness = surf.filteredRoughness;
1057
+ vec3 halfVector = ggxDirection(
1058
+ wo,
1059
+ filteredRoughness,
1060
+ filteredRoughness,
1061
+ rand(),
1062
+ rand()
1063
+ );
1064
+
1065
+ // apply to new ray by reflecting off the new normal
1066
+ return - reflect( wo, halfVector );
1067
+
1068
+ }
1069
+
1070
+ vec3 specularColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
1071
+
1072
+ // if roughness is set to 0 then D === NaN which results in black pixels
1073
+ float metalness = surf.metalness;
1074
+ float ior = surf.ior;
1075
+ bool frontFace = surf.frontFace;
1076
+ float filteredRoughness = surf.filteredRoughness;
1077
+
1078
+ vec3 halfVector = getHalfVector( wo, wi );
1079
+ float iorRatio = frontFace ? 1.0 / ior : ior;
1080
+ float G = ggxShadowMaskG2( wi, wo, filteredRoughness );
1081
+ float D = ggxDistribution( halfVector, filteredRoughness );
1082
+
1083
+ float F = schlickFresnelFromIor( dot( wi, halfVector ), iorRatio );
1084
+ float cosTheta = min( wo.z, 1.0 );
1085
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
1086
+ bool cannotRefract = iorRatio * sinTheta > 1.0;
1087
+ if ( cannotRefract ) {
1088
+
1089
+ F = 1.0;
1090
+
1091
+ }
1092
+
1093
+ vec3 color = mix( vec3( 1.0 ), surf.color, metalness );
1094
+ color = mix( color, vec3( 1.0 ), F );
1095
+ color *= G * D / ( 4.0 * abs( wi.z * wo.z ) );
1096
+ color *= mix( F, 1.0, metalness );
1097
+ color *= wi.z; // scale the light by the direction the light is coming in from
1098
+
1099
+ return color;
1100
+
1101
+ }
1102
+
1103
+ /*
1104
+ // transmission
1105
+ function transmissionPDF( wo, wi, material, surf ) {
1106
+
1107
+ // See section 4.2 in https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
1108
+
1109
+ const { roughness, ior } = material;
1110
+ const { frontFace } = hit;
1111
+ const ratio = frontFace ? ior : 1 / ior;
1112
+ const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
1113
+
1114
+ halfVector.set( 0, 0, 0 ).addScaledVector( wi, ratio ).addScaledVector( wo, 1.0 ).normalize().multiplyScalar( - 1 );
1115
+
1116
+ const denom = Math.pow( ratio * halfVector.dot( wi ) + 1.0 * halfVector.dot( wo ), 2.0 );
1117
+ return ggxPDF( wo, halfVector, minRoughness ) / denom;
1118
+
1119
+ }
1120
+
1121
+ function transmissionDirection( wo, hit, material, lightDirection ) {
1122
+
1123
+ const { roughness, ior } = material;
1124
+ const { frontFace } = hit;
1125
+ const ratio = frontFace ? 1 / ior : ior;
1126
+ const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
1127
+
1128
+ // sample ggx vndf distribution which gives a new normal
1129
+ ggxDirection(
1130
+ wo,
1131
+ minRoughness,
1132
+ minRoughness,
1133
+ Math.random(),
1134
+ Math.random(),
1135
+ halfVector,
1136
+ );
1137
+
1138
+ // apply to new ray by reflecting off the new normal
1139
+ tempDir.copy( wo ).multiplyScalar( - 1 );
1140
+ refract( tempDir, halfVector, ratio, lightDirection );
1141
+
1142
+ }
1143
+
1144
+ function transmissionColor( wo, wi, material, hit, colorTarget ) {
1145
+
1146
+ const { metalness, transmission } = material;
1147
+ colorTarget
1148
+ .copy( material.color )
1149
+ .multiplyScalar( ( 1.0 - metalness ) * wo.z )
1150
+ .multiplyScalar( transmission );
1151
+
1152
+ }
1153
+ */
1154
+
1155
+ // TODO: This is just using a basic cosine-weighted specular distribution with an
1156
+ // incorrect PDF value at the moment. Update it to correctly use a GGX distribution
1157
+ float transmissionPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
1158
+
1159
+ float ior = surf.ior;
1160
+ bool frontFace = surf.frontFace;
1161
+
1162
+ float ratio = frontFace ? 1.0 / ior : ior;
1163
+ float cosTheta = min( wo.z, 1.0 );
1164
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
1165
+ float reflectance = schlickFresnelFromIor( cosTheta, ratio );
1166
+ bool cannotRefract = ratio * sinTheta > 1.0;
1167
+ if ( cannotRefract ) {
1168
+
1169
+ return 0.0;
1170
+
1171
+ }
1172
+
1173
+ return 1.0 / ( 1.0 - reflectance );
1174
+
1175
+ }
1176
+
1177
+ vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
1178
+
1179
+ float roughness = surf.roughness;
1180
+ float ior = surf.ior;
1181
+ bool frontFace = surf.frontFace;
1182
+ float ratio = frontFace ? 1.0 / ior : ior;
1183
+
1184
+ vec3 lightDirection = refract( - wo, vec3( 0.0, 0.0, 1.0 ), ratio );
1185
+ lightDirection += randDirection() * roughness;
1186
+ return normalize( lightDirection );
1187
+
1188
+ }
1189
+
1190
+ vec3 transmissionColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
1191
+
1192
+ float metalness = surf.metalness;
1193
+ float transmission = surf.transmission;
1194
+
1195
+ vec3 color = surf.color;
1196
+ color *= ( 1.0 - metalness );
1197
+ color *= transmission;
1198
+
1199
+ return color;
1200
+
1201
+ }
1202
+
1203
+ float bsdfPdf( vec3 wo, vec3 wi, SurfaceRec surf ) {
1204
+
1205
+ float ior = surf.ior;
1206
+ float metalness = surf.metalness;
1207
+ float transmission = surf.transmission;
1208
+ bool frontFace = surf.frontFace;
1209
+
1210
+ float ratio = frontFace ? 1.0 / ior : ior;
1211
+ float cosTheta = min( wo.z, 1.0 );
1212
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
1213
+ float reflectance = schlickFresnelFromIor( cosTheta, ratio );
1214
+ bool cannotRefract = ratio * sinTheta > 1.0;
1215
+ if ( cannotRefract ) {
1216
+
1217
+ reflectance = 1.0;
1218
+
1219
+ }
1220
+
1221
+ float spdf = 0.0;
1222
+ float dpdf = 0.0;
1223
+ float tpdf = 0.0;
1224
+
1225
+ if ( wi.z < 0.0 ) {
1226
+
1227
+ tpdf = transmissionPDF( wo, wi, surf );
1228
+
1229
+ } else {
1230
+
1231
+ spdf = specularPDF( wo, wi, surf );
1232
+ dpdf = diffusePDF( wo, wi, surf );
1233
+
1234
+ }
1235
+
1236
+ float transSpecularProb = mix( reflectance, 1.0, metalness );
1237
+ float diffSpecularProb = 0.5 + 0.5 * metalness;
1238
+ float pdf =
1239
+ spdf * transmission * transSpecularProb
1240
+ + tpdf * transmission * ( 1.0 - transSpecularProb )
1241
+ + spdf * ( 1.0 - transmission ) * diffSpecularProb
1242
+ + dpdf * ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb );
1243
+
1244
+ return pdf;
1245
+
1246
+ }
1247
+
1248
+ vec3 bsdfColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
1249
+
1250
+ vec3 color = vec3( 0.0 );
1251
+ if ( wi.z < 0.0 ) {
1252
+
1253
+ color = transmissionColor( wo, wi, surf );
1254
+
1255
+ } else {
1256
+
1257
+ color = diffuseColor( wo, wi, surf );
1258
+ color *= 1.0 - surf.transmission;
1259
+
1260
+ color += specularColor( wo, wi, surf );
1261
+
1262
+ }
1263
+
1264
+ return color;
1265
+
1266
+ }
1267
+
1268
+ SampleRec bsdfSample( vec3 wo, SurfaceRec surf ) {
1269
+
1270
+ float ior = surf.ior;
1271
+ float metalness = surf.metalness;
1272
+ float transmission = surf.transmission;
1273
+ bool frontFace = surf.frontFace;
1274
+
1275
+ float ratio = frontFace ? 1.0 / ior : ior;
1276
+ float cosTheta = min( wo.z, 1.0 );
1277
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
1278
+ float reflectance = schlickFresnelFromIor( cosTheta, ratio );
1279
+ bool cannotRefract = ratio * sinTheta > 1.0;
1280
+ if ( cannotRefract ) {
1281
+
1282
+ reflectance = 1.0;
1283
+
1284
+ }
1285
+
1286
+ SampleRec result;
1287
+ if ( rand() < transmission ) {
1288
+
1289
+ float specularProb = mix( reflectance, 1.0, metalness );
1290
+ if ( rand() < specularProb ) {
1291
+
1292
+ result.direction = specularDirection( wo, surf );
1293
+
1294
+ } else {
1295
+
1296
+ result.direction = transmissionDirection( wo, surf );
1297
+
1298
+ }
1299
+
1300
+ } else {
1301
+
1302
+ float specularProb = 0.5 + 0.5 * metalness;
1303
+ if ( rand() < specularProb ) {
1304
+
1305
+ result.direction = specularDirection( wo, surf );
1306
+
1307
+ } else {
1308
+
1309
+ result.direction = diffuseDirection( wo, surf );
1310
+
1311
+ }
1312
+
1313
+ }
1314
+
1315
+ result.pdf = bsdfPdf( wo, result.direction, surf );
1316
+ result.color = bsdfColor( wo, result.direction, surf );
1317
+ return result;
1318
+
1319
+ }
1320
+ `;
1321
+
1322
+ const shaderUtils = /* glsl */`
1323
+
1324
+ // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
1325
+ float schlickFresnel( float cosine, float f0 ) {
1326
+
1327
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
1328
+
1329
+ }
1330
+
1331
+ // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
1332
+ float schlickFresnelFromIor( float cosine, float iorRatio ) {
1333
+
1334
+ // Schlick approximation
1335
+ float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
1336
+ return schlickFresnel( cosine, r_0 );
1337
+
1338
+ }
1339
+
1340
+ // forms a basis with the normal vector as Z
1341
+ mat3 getBasisFromNormal( vec3 normal ) {
1342
+
1343
+ vec3 other;
1344
+ if ( abs( normal.x ) > 0.5 ) {
1345
+
1346
+ other = vec3( 0.0, 1.0, 0.0 );
1347
+
1348
+ } else {
1349
+
1350
+ other = vec3( 1.0, 0.0, 0.0 );
1351
+
1352
+ }
1353
+
1354
+ vec3 ortho = normalize( cross( normal, other ) );
1355
+ vec3 ortho2 = normalize( cross( normal, ortho ) );
1356
+ return mat3( ortho2, ortho, normal );
1357
+
1358
+ }
1359
+
1360
+ vec3 getHalfVector( vec3 a, vec3 b ) {
1361
+
1362
+ return normalize( a + b );
1363
+
1364
+ }
1365
+
1366
+ // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
1367
+ // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
1368
+ // we find a ray like that we ignore it to avoid artifacts.
1369
+ // This function returns if the direction is on the same side of both planes.
1370
+ bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
1371
+
1372
+ bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
1373
+ bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
1374
+ return aboveSurfaceNormal == aboveGeometryNormal;
1375
+
1376
+ }
1377
+
1378
+ vec3 getHemisphereSample( vec3 n, vec2 uv ) {
1379
+
1380
+ // https://www.rorydriscoll.com/2009/01/07/better-sampling/
1381
+ // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
1382
+ float sign = n.z == 0.0 ? 1.0 : sign( n.z );
1383
+ float a = - 1.0 / ( sign + n.z );
1384
+ float b = n.x * n.y * a;
1385
+ vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
1386
+ vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
1387
+
1388
+ float r = sqrt( uv.x );
1389
+ float theta = 2.0 * PI * uv.y;
1390
+ float x = r * cos( theta );
1391
+ float y = r * sin( theta );
1392
+ return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
1393
+
1394
+ }
1395
+
1396
+ // https://www.shadertoy.com/view/wltcRS
1397
+ uvec4 s0;
1398
+
1399
+ void rng_initialize(vec2 p, int frame) {
1400
+
1401
+ // white noise seed
1402
+ s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
1403
+
1404
+ }
1405
+
1406
+ // https://www.pcg-random.org/
1407
+ void pcg4d( inout uvec4 v ) {
1408
+
1409
+ v = v * 1664525u + 1013904223u;
1410
+ v.x += v.y * v.w;
1411
+ v.y += v.z * v.x;
1412
+ v.z += v.x * v.y;
1413
+ v.w += v.y * v.z;
1414
+ v = v ^ ( v >> 16u );
1415
+ v.x += v.y*v.w;
1416
+ v.y += v.z*v.x;
1417
+ v.z += v.x*v.y;
1418
+ v.w += v.y*v.z;
1419
+
1420
+ }
1421
+
1422
+ float rand() {
1423
+
1424
+ pcg4d(s0);
1425
+ return float( s0.x ) / float( 0xffffffffu );
1426
+
1427
+ }
1428
+
1429
+ vec2 rand2() {
1430
+
1431
+ pcg4d( s0 );
1432
+ return vec2( s0.xy ) / float(0xffffffffu);
1433
+
1434
+ }
1435
+
1436
+ vec3 rand3() {
1437
+
1438
+ pcg4d(s0);
1439
+ return vec3( s0.xyz ) / float( 0xffffffffu );
1440
+
1441
+ }
1442
+
1443
+ vec4 rand4() {
1444
+
1445
+ pcg4d(s0);
1446
+ return vec4(s0)/float(0xffffffffu);
1447
+
1448
+ }
1449
+
1450
+ // https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
1451
+ vec3 randDirection() {
1452
+
1453
+ vec2 r = rand2();
1454
+ float u = ( r.x - 0.5 ) * 2.0;
1455
+ float t = r.y * PI * 2.0;
1456
+ float f = sqrt( 1.0 - u * u );
1457
+
1458
+ return vec3( f * cos( t ), f * sin( t ), u );
1459
+
1460
+ }
1461
+ `;
1462
+
1463
+ class PhysicalPathTracingMaterial extends MaterialBase {
1464
+
1465
+ // three.js relies on this field to add env map functions and defines
1466
+ get envMap() {
1467
+
1468
+ return this.environmentMap;
1469
+
1470
+ }
1471
+
1472
+ constructor( parameters ) {
1473
+
1474
+ super( {
1475
+
1476
+ transparent: true,
1477
+ depthWrite: false,
1478
+
1479
+ defines: {
1480
+ BOUNCES: 3,
1481
+ TRANSPARENT_TRAVERSALS: 5,
1482
+ MATERIAL_LENGTH: 0,
1483
+ GRADIENT_BG: 0,
1484
+ },
1485
+
1486
+ uniforms: {
1487
+ bvh: { value: new threeMeshBvh.MeshBVHUniformStruct() },
1488
+ normalAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
1489
+ tangentAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
1490
+ uvAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
1491
+ materialIndexAttribute: { value: new threeMeshBvh.UIntVertexAttributeTexture() },
1492
+ materials: { value: new MaterialStructArrayUniform() },
1493
+ textures: { value: new RenderTarget2DArray().texture },
1494
+ cameraWorldMatrix: { value: new three.Matrix4() },
1495
+ invProjectionMatrix: { value: new three.Matrix4() },
1496
+ environmentBlur: { value: 0.2 },
1497
+ environmentIntensity: { value: 2.0 },
1498
+ environmentMap: { value: null },
1499
+ environmentRotation: { value: new three.Matrix3() },
1500
+ seed: { value: 0 },
1501
+ opacity: { value: 1 },
1502
+ filterGlossyFactor: { value: 0.0 },
1503
+
1504
+ gradientTop: { value: new three.Color( 0xbfd8ff ) },
1505
+ gradientBottom: { value: new three.Color( 0xffffff ) },
1506
+
1507
+ bgGradientTop: { value: new three.Color( 0x111111 ) },
1508
+ bgGradientBottom: { value: new three.Color( 0x000000 ) },
1509
+ },
1510
+
1511
+ vertexShader: /* glsl */`
1512
+
1513
+ varying vec2 vUv;
1514
+ void main() {
1515
+
1516
+ vec4 mvPosition = vec4( position, 1.0 );
1517
+ mvPosition = modelViewMatrix * mvPosition;
1518
+ gl_Position = projectionMatrix * mvPosition;
1519
+
1520
+ vUv = uv;
1521
+
1522
+ }
1523
+
1524
+ `,
1525
+
1526
+ fragmentShader: /* glsl */`
1527
+ #define RAY_OFFSET 1e-5
1528
+
1529
+ precision highp isampler2D;
1530
+ precision highp usampler2D;
1531
+ precision highp sampler2DArray;
1532
+ vec4 envMapTexelToLinear( vec4 a ) { return a; }
1533
+ #include <common>
1534
+ #include <cube_uv_reflection_fragment>
1535
+
1536
+ ${ threeMeshBvh.shaderStructs }
1537
+ ${ threeMeshBvh.shaderIntersectFunction }
1538
+ ${ shaderMaterialStructs }
1539
+
1540
+ ${ shaderUtils }
1541
+ ${ shaderMaterialSampling }
1542
+
1543
+ #ifdef USE_ENVMAP
1544
+
1545
+ uniform float environmentBlur;
1546
+ uniform sampler2D environmentMap;
1547
+ uniform mat3 environmentRotation;
1548
+
1549
+ #else
1550
+
1551
+ uniform vec3 gradientTop;
1552
+ uniform vec3 gradientBottom;
1553
+
1554
+ #endif
1555
+
1556
+ #if GRADIENT_BG
1557
+
1558
+ uniform vec3 bgGradientTop;
1559
+ uniform vec3 bgGradientBottom;
1560
+
1561
+ #endif
1562
+
1563
+ uniform mat4 cameraWorldMatrix;
1564
+ uniform mat4 invProjectionMatrix;
1565
+ uniform sampler2D normalAttribute;
1566
+ uniform sampler2D tangentAttribute;
1567
+ uniform sampler2D uvAttribute;
1568
+ uniform usampler2D materialIndexAttribute;
1569
+ uniform BVH bvh;
1570
+ uniform float environmentIntensity;
1571
+ uniform float filterGlossyFactor;
1572
+ uniform int seed;
1573
+ uniform float opacity;
1574
+ uniform Material materials[ MATERIAL_LENGTH ];
1575
+ uniform sampler2DArray textures;
1576
+ varying vec2 vUv;
1577
+
1578
+ void main() {
1579
+
1580
+ rng_initialize( gl_FragCoord.xy, seed );
1581
+
1582
+ // get [-1, 1] normalized device coordinates
1583
+ vec2 ndc = 2.0 * vUv - vec2( 1.0 );
1584
+ vec3 rayOrigin, rayDirection;
1585
+ ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
1586
+
1587
+ // Lambertian render
1588
+ gl_FragColor = vec4( 0.0 );
1589
+
1590
+ vec3 throughputColor = vec3( 1.0 );
1591
+
1592
+ // hit results
1593
+ uvec4 faceIndices = uvec4( 0u );
1594
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
1595
+ vec3 barycoord = vec3( 0.0 );
1596
+ float side = 1.0;
1597
+ float dist = 0.0;
1598
+ float accumulatedRoughness = 0.0;
1599
+ int i;
1600
+ int transparentTraversals = TRANSPARENT_TRAVERSALS;
1601
+ for ( i = 0; i < BOUNCES; i ++ ) {
1602
+
1603
+ if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
1604
+
1605
+ #if GRADIENT_BG
1606
+
1607
+ if ( i == 0 ) {
1608
+
1609
+ rayDirection = normalize( rayDirection + randDirection() * 0.05 );
1610
+ float value = ( rayDirection.y + 1.0 ) / 2.0;
1611
+
1612
+ value = pow( value, 2.0 );
1613
+
1614
+ gl_FragColor = vec4( mix( bgGradientBottom, bgGradientTop, value ), 1.0 );
1615
+ break;
1616
+
1617
+ }
1618
+
1619
+ #endif
1620
+
1621
+ #ifdef USE_ENVMAP
1622
+
1623
+ vec3 skyColor = textureCubeUV( environmentMap, environmentRotation * rayDirection, environmentBlur ).rgb;
1624
+
1625
+ #else
1626
+
1627
+ rayDirection = normalize( rayDirection );
1628
+ float value = ( rayDirection.y + 1.0 ) / 2.0;
1629
+ vec3 skyColor = mix( gradientBottom, gradientTop, value );
1630
+
1631
+ #endif
1632
+
1633
+ gl_FragColor += vec4( skyColor * throughputColor * environmentIntensity, 1.0 );
1634
+
1635
+ break;
1636
+
1637
+ }
1638
+
1639
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
1640
+ Material material = materials[ materialIndex ];
1641
+
1642
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
1643
+
1644
+ // albedo
1645
+ vec4 albedo = vec4( material.color, material.opacity );
1646
+ if ( material.map != - 1 ) {
1647
+
1648
+ albedo *= texture2D( textures, vec3( uv, material.map ) );
1649
+
1650
+ }
1651
+
1652
+ // possibly skip this sample if it's transparent or alpha test is enabled
1653
+ // alpha test is disabled when it === 0
1654
+ float alphaTest = material.alphaTest;
1655
+ bool useAlphaTest = alphaTest != 0.0;
1656
+ if (
1657
+ useAlphaTest && albedo.a < alphaTest
1658
+ || ! useAlphaTest && albedo.a < rand()
1659
+ ) {
1660
+
1661
+ vec3 point = rayOrigin + rayDirection * dist;
1662
+ rayOrigin += rayDirection * dist - faceNormal * RAY_OFFSET;
1663
+
1664
+ // only allow a limited number of transparency discards otherwise we could
1665
+ // crash the context with too long a loop.
1666
+ i -= sign( transparentTraversals );
1667
+ transparentTraversals -= sign( transparentTraversals );
1668
+ continue;
1669
+
1670
+ }
1671
+
1672
+ // fetch the interpolated smooth normal
1673
+ vec3 normal = normalize( textureSampleBarycoord(
1674
+ normalAttribute,
1675
+ barycoord,
1676
+ faceIndices.xyz
1677
+ ).xyz );
1678
+
1679
+ // roughness
1680
+ float roughness = material.roughness;
1681
+ if ( material.roughnessMap != - 1 ) {
1682
+
1683
+ roughness *= texture2D( textures, vec3( uv, material.roughnessMap ) ).g;
1684
+
1685
+ }
1686
+
1687
+ // metalness
1688
+ float metalness = material.metalness;
1689
+ if ( material.metalnessMap != - 1 ) {
1690
+
1691
+ metalness *= texture2D( textures, vec3( uv, material.metalnessMap ) ).b;
1692
+
1693
+ }
1694
+
1695
+ // emission
1696
+ vec3 emission = material.emissiveIntensity * material.emissive;
1697
+ if ( material.emissiveMap != - 1 ) {
1698
+
1699
+ emission *= texture2D( textures, vec3( uv, material.emissiveMap ) ).xyz;
1700
+
1701
+ }
1702
+
1703
+ // transmission
1704
+ float transmission = material.transmission;
1705
+ if ( material.transmissionMap != - 1 ) {
1706
+
1707
+ transmission *= texture2D( textures, vec3( uv, material.transmissionMap ) ).r;
1708
+
1709
+ }
1710
+
1711
+ // normal
1712
+ if ( material.normalMap != - 1 ) {
1713
+
1714
+ vec4 tangentSample = textureSampleBarycoord(
1715
+ tangentAttribute,
1716
+ barycoord,
1717
+ faceIndices.xyz
1718
+ );
1719
+
1720
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
1721
+ // resulting in NaNs and slow path tracing.
1722
+ if ( length( tangentSample.xyz ) > 0.0 ) {
1723
+
1724
+ vec3 tangent = normalize( tangentSample.xyz );
1725
+ vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
1726
+ mat3 vTBN = mat3( tangent, bitangent, normal );
1727
+
1728
+ vec3 texNormal = texture2D( textures, vec3( uv, material.normalMap ) ).xyz * 2.0 - 1.0;
1729
+ texNormal.xy *= material.normalScale;
1730
+ normal = vTBN * texNormal;
1731
+
1732
+ }
1733
+
1734
+ }
1735
+
1736
+ normal *= side;
1737
+
1738
+ SurfaceRec surfaceRec;
1739
+ surfaceRec.normal = normal;
1740
+ surfaceRec.faceNormal = faceNormal;
1741
+ surfaceRec.frontFace = side == 1.0;
1742
+ surfaceRec.transmission = transmission;
1743
+ surfaceRec.ior = material.ior;
1744
+ surfaceRec.emission = emission;
1745
+ surfaceRec.metalness = metalness;
1746
+ surfaceRec.color = albedo.rgb;
1747
+ surfaceRec.roughness = roughness;
1748
+
1749
+ // Compute the filtered roughness value to use during specular reflection computations. A minimum
1750
+ // value of 1e-6 is needed because the GGX functions do not work with a roughness value of 0 and
1751
+ // the accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
1752
+ // If we're exiting something transmissive then scale the factor down significantly so we can retain
1753
+ // sharp internal reflections
1754
+ surfaceRec.filteredRoughness = clamp(
1755
+ max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ),
1756
+ 1e-3,
1757
+ 1.0
1758
+ );
1759
+
1760
+ mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
1761
+ mat3 invBasis = inverse( normalBasis );
1762
+
1763
+ vec3 outgoing = - normalize( invBasis * rayDirection );
1764
+ SampleRec sampleRec = bsdfSample( outgoing, surfaceRec );
1765
+
1766
+ // adjust the hit point by the surface normal by a factor of some offset and the
1767
+ // maximum component-wise value of the current point to accommodate floating point
1768
+ // error as values increase.
1769
+ vec3 point = rayOrigin + rayDirection * dist;
1770
+ vec3 absPoint = abs( point );
1771
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
1772
+ rayDirection = normalize( normalBasis * sampleRec.direction );
1773
+
1774
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
1775
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
1776
+
1777
+ // accumulate a roughness value to offset diffuse, specular, diffuse rays that have high contribution
1778
+ // to a single pixel resulting in fireflies
1779
+ if ( ! isBelowSurface ) {
1780
+
1781
+ // determine if this is a rough normal or not by checking how far off straight up it is
1782
+ vec3 halfVector = normalize( outgoing + sampleRec.direction );
1783
+ accumulatedRoughness += sin( acos( halfVector.z ) );
1784
+
1785
+ }
1786
+
1787
+ // accumulate color
1788
+ gl_FragColor.rgb += ( emission * throughputColor );
1789
+
1790
+ // skip the sample if our PDF or ray is impossible
1791
+ if ( sampleRec.pdf <= 0.0 || ! isDirectionValid( rayDirection, normal, faceNormal) ) {
1792
+
1793
+ break;
1794
+
1795
+ }
1796
+
1797
+ throughputColor *= sampleRec.color / sampleRec.pdf;
1798
+
1799
+ // discard the sample if there are any NaNs
1800
+ if ( any( isnan( throughputColor ) ) || any( isinf( throughputColor ) ) ) {
1801
+
1802
+ break;
1803
+
1804
+ }
1805
+
1806
+ }
1807
+
1808
+ gl_FragColor.a = opacity;
1809
+
1810
+ }
1811
+
1812
+ `
1813
+
1814
+ } );
1815
+
1816
+ this.setValues( parameters );
1817
+
1818
+ }
1819
+
1820
+ }
1821
+
1822
+ // core
1823
+
1824
+ exports.MaterialBase = MaterialBase;
1825
+ exports.MaterialReducer = MaterialReducer;
1826
+ exports.MaterialStructArrayUniform = MaterialStructArrayUniform;
1827
+ exports.MaterialStructUniform = MaterialStructUniform;
1828
+ exports.PathTracingRenderer = PathTracingRenderer;
1829
+ exports.PathTracingSceneGenerator = PathTracingSceneGenerator;
1830
+ exports.PhysicalPathTracingMaterial = PhysicalPathTracingMaterial;
1831
+ exports.RenderTarget2DArray = RenderTarget2DArray;
1832
+ exports.mergeMeshes = mergeMeshes;
1833
+ exports.shaderMaterialSampling = shaderMaterialSampling;
1834
+ exports.shaderMaterialStructs = shaderMaterialStructs;
1835
+ exports.shaderUtils = shaderUtils;
1836
+
1837
+ Object.defineProperty(exports, '__esModule', { value: true });
1838
+
1839
+ }));
1840
+ //# sourceMappingURL=index.umd.cjs.map