three-gpu-pathtracer 0.0.11 → 0.0.13

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,4 +1,4 @@
1
- import { ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, NormalBlending, Color, BufferAttribute, Mesh, BufferGeometry, PerspectiveCamera, Camera, SpotLight, RectAreaLight, Spherical, DataTexture, EquirectangularReflectionMapping, RepeatWrapping, ClampToEdgeWrapping, LinearFilter, Vector3, DoubleSide, BackSide, FrontSide, WebGLArrayRenderTarget, UnsignedByteType, MeshBasicMaterial, NoToneMapping, Source, HalfFloatType, DataUtils, RedFormat, Matrix4, Quaternion, Loader, MathUtils, FileLoader, PMREMGenerator, Vector4, DataArrayTexture } from 'three';
1
+ import { ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, Vector4, NormalBlending, Color, Vector3, MathUtils, Matrix4, PerspectiveCamera, BufferAttribute, Mesh, BufferGeometry, Camera, SpotLight, RectAreaLight, Spherical, DataTexture, EquirectangularReflectionMapping, RepeatWrapping, ClampToEdgeWrapping, LinearFilter, DoubleSide, BackSide, FrontSide, WebGLArrayRenderTarget, UnsignedByteType, MeshBasicMaterial, NoToneMapping, Source, HalfFloatType, DataUtils, RedFormat, Quaternion, Loader, FileLoader, PMREMGenerator, DataArrayTexture } from 'three';
2
2
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
3
3
  import { StaticGeometryGenerator, SAH, MeshBVH, FloatVertexAttributeTexture, MeshBVHUniformStruct, UIntVertexAttributeTexture, shaderStructs, shaderIntersectFunction } from 'three-mesh-bvh';
4
4
  import { mergeVertices, mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
@@ -456,6 +456,9 @@ class SobolNumberMapGenerator {
456
456
 
457
457
  }
458
458
 
459
+ const _scissor = new Vector4();
460
+ const _viewport = new Vector4();
461
+
459
462
  function* renderTask() {
460
463
 
461
464
  const {
@@ -465,10 +468,13 @@ function* renderTask() {
465
468
  _primaryTarget,
466
469
  _blendTargets,
467
470
  _sobolTarget,
471
+ _subframe,
468
472
  alpha,
469
473
  camera,
470
474
  material,
471
475
  } = this;
476
+ const _ogScissor = new Vector4();
477
+ const _ogViewport = new Vector4();
472
478
 
473
479
  const blendMaterial = _blendQuad.material;
474
480
  let [ blendTarget1, blendTarget2 ] = _blendTargets;
@@ -477,20 +483,22 @@ function* renderTask() {
477
483
 
478
484
  if ( alpha ) {
479
485
 
480
- blendMaterial.opacity = 1 / ( this.samples + 1 );
486
+ blendMaterial.opacity = this._opacityFactor / ( this._samples + 1 );
481
487
  material.blending = NoBlending;
482
488
  material.opacity = 1;
483
489
 
484
490
  } else {
485
491
 
486
- material.opacity = 1 / ( this.samples + 1 );
492
+ material.opacity = this._opacityFactor / ( this._samples + 1 );
487
493
  material.blending = NormalBlending;
488
494
 
489
495
  }
490
496
 
497
+ const [ subX, subY, subW, subH ] = _subframe;
498
+
491
499
  const w = _primaryTarget.width;
492
500
  const h = _primaryTarget.height;
493
- material.resolution.set( w, h );
501
+ material.resolution.set( w * subW, h * subH );
494
502
  material.sobolTexture = _sobolTarget.texture;
495
503
  material.seed ++;
496
504
 
@@ -498,6 +506,7 @@ function* renderTask() {
498
506
  const tilesY = this.tiles.y || 1;
499
507
  const totalTiles = tilesX * tilesY;
500
508
  const dprInv = ( 1 / _renderer.getPixelRatio() );
509
+
501
510
  for ( let y = 0; y < tilesY; y ++ ) {
502
511
 
503
512
  for ( let x = 0; x < tilesX; x ++ ) {
@@ -526,8 +535,12 @@ function* renderTask() {
526
535
 
527
536
  material.setDefine( 'CAMERA_TYPE', cameraType );
528
537
 
538
+ // store og state
529
539
  const ogRenderTarget = _renderer.getRenderTarget();
530
540
  const ogAutoClear = _renderer.autoClear;
541
+ const ogScissorTest = _renderer.getScissorTest();
542
+ _renderer.getScissor( _ogScissor );
543
+ _renderer.getViewport( _ogViewport );
531
544
 
532
545
  let tx = x;
533
546
  let ty = y;
@@ -544,18 +557,48 @@ function* renderTask() {
544
557
  // three.js renderer takes values relative to the current pixel ratio
545
558
  _renderer.setRenderTarget( _primaryTarget );
546
559
  _renderer.setScissorTest( true );
547
- _renderer.setScissor(
548
- dprInv * Math.ceil( tx * w / tilesX ),
549
- dprInv * Math.ceil( ( tilesY - ty - 1 ) * h / tilesY ),
550
- dprInv * Math.ceil( w / tilesX ),
551
- dprInv * Math.ceil( h / tilesY ) );
560
+
561
+ // set the scissor window for a subtile
562
+ _scissor.x = tx * w / tilesX;
563
+ _scissor.y = ( tilesY - ty - 1 ) * h / tilesY;
564
+ _scissor.z = w / tilesX;
565
+ _scissor.w = h / tilesY;
566
+
567
+ // adjust for the subframe
568
+ _scissor.x = subX * w + subW * _scissor.x;
569
+ _scissor.y = subY * h + subH * _scissor.y;
570
+ _scissor.z = subW * _scissor.z;
571
+ _scissor.w = subH * _scissor.w;
572
+
573
+ // round for floating point cases
574
+ _scissor.x = _scissor.x;
575
+ _scissor.y = _scissor.y;
576
+ _scissor.z = _scissor.z;
577
+ _scissor.w = _scissor.w;
578
+
579
+ // multiply inverse of DPR in because threes multiplies it in
580
+ _scissor.multiplyScalar( dprInv ).ceil();
581
+
582
+ _viewport.x = subX * w;
583
+ _viewport.y = subY * h;
584
+ _viewport.z = subW * w;
585
+ _viewport.w = subH * h;
586
+ _viewport.multiplyScalar( dprInv ).ceil();
587
+
588
+ _renderer.setScissor( _scissor );
589
+ _renderer.setViewport( _viewport );
590
+
552
591
  _renderer.autoClear = false;
553
592
  _fsQuad.render( _renderer );
554
593
 
555
- _renderer.setScissorTest( false );
594
+ // reset original renderer state
595
+ _renderer.setViewport( _ogViewport );
596
+ _renderer.setScissor( _ogScissor );
597
+ _renderer.setScissorTest( ogScissorTest );
556
598
  _renderer.setRenderTarget( ogRenderTarget );
557
599
  _renderer.autoClear = ogAutoClear;
558
600
 
601
+ // swap and blend alpha targets
559
602
  if ( alpha ) {
560
603
 
561
604
  blendMaterial.target1 = blendTarget1.texture;
@@ -567,7 +610,14 @@ function* renderTask() {
567
610
 
568
611
  }
569
612
 
570
- this.samples += ( 1 / totalTiles );
613
+ this._samples += ( 1 / totalTiles );
614
+
615
+ // round the samples value if we've finished the tiles
616
+ if ( x === tilesX - 1 && y === tilesY - 1 ) {
617
+
618
+ this._samples = Math.round( this._samples );
619
+
620
+ }
571
621
 
572
622
  yield;
573
623
 
@@ -577,8 +627,6 @@ function* renderTask() {
577
627
 
578
628
  [ blendTarget1, blendTarget2 ] = [ blendTarget2, blendTarget1 ];
579
629
 
580
- this.samples = Math.round( this.samples );
581
-
582
630
  }
583
631
 
584
632
  }
@@ -606,6 +654,12 @@ class PathTracingRenderer {
606
654
 
607
655
  set alpha( v ) {
608
656
 
657
+ if ( this._alpha === v ) {
658
+
659
+ return;
660
+
661
+ }
662
+
609
663
  if ( ! v ) {
610
664
 
611
665
  this._blendTargets[ 0 ].dispose();
@@ -624,15 +678,23 @@ class PathTracingRenderer {
624
678
 
625
679
  }
626
680
 
681
+ get samples() {
682
+
683
+ return this._samples;
684
+
685
+ }
686
+
627
687
  constructor( renderer ) {
628
688
 
629
689
  this.camera = null;
630
690
  this.tiles = new Vector2( 1, 1 );
631
691
 
632
- this.samples = 0;
633
692
  this.stableNoise = false;
634
693
  this.stableTiles = true;
635
694
 
695
+ this._samples = 0;
696
+ this._subframe = new Vector4( 0, 0, 1, 1 );
697
+ this._opacityFactor = 1.0;
636
698
  this._renderer = renderer;
637
699
  this._alpha = false;
638
700
  this._fsQuad = new FullScreenQuad( null );
@@ -660,6 +722,15 @@ class PathTracingRenderer {
660
722
 
661
723
  setSize( w, h ) {
662
724
 
725
+ w = Math.ceil( w );
726
+ h = Math.ceil( h );
727
+
728
+ if ( this._primaryTarget.width === w && this._primaryTarget.height === h ) {
729
+
730
+ return;
731
+
732
+ }
733
+
663
734
  this._primaryTarget.setSize( w, h );
664
735
  this._blendTargets[ 0 ].setSize( w, h );
665
736
  this._blendTargets[ 1 ].setSize( w, h );
@@ -702,7 +773,7 @@ class PathTracingRenderer {
702
773
  _renderer.setClearColor( ogClearColor, ogClearAlpha );
703
774
  _renderer.setRenderTarget( ogRenderTarget );
704
775
 
705
- this.samples = 0;
776
+ this._samples = 0;
706
777
  this._task = null;
707
778
 
708
779
  if ( this.stableNoise ) {
@@ -727,6 +798,227 @@ class PathTracingRenderer {
727
798
 
728
799
  }
729
800
 
801
+ function* _task( cb ) {
802
+
803
+ const {
804
+ viewCount,
805
+ _camera,
806
+ _quiltUtility,
807
+ _subframe,
808
+ } = this;
809
+
810
+ const quiltViewInfo = {
811
+ subframe: _subframe,
812
+ projectionMatrix: _camera.projectionMatrix,
813
+ offsetDirection: new Vector3(),
814
+ };
815
+
816
+ while ( true ) {
817
+
818
+ for ( let i = 0; i < viewCount; i ++ ) {
819
+
820
+ // get the camera info for the current view index
821
+ _quiltUtility.near = this.camera.near;
822
+ _quiltUtility.far = this.camera.far;
823
+ _quiltUtility.getCameraViewInfo( i, quiltViewInfo );
824
+
825
+ // transform offset into world frame from camera frame
826
+ quiltViewInfo.offsetDirection.transformDirection( this.camera.matrixWorld );
827
+
828
+ // adjust the render camera with the view offset
829
+ this.camera.matrixWorld.decompose(
830
+ _camera.position,
831
+ _camera.quaternion,
832
+ _camera.scale,
833
+ );
834
+ _camera.position.addScaledVector( quiltViewInfo.offsetDirection, quiltViewInfo.offset );
835
+ _camera.updateMatrixWorld();
836
+
837
+ // get the inverse projection
838
+ _camera.projectionMatrixInverse
839
+ .copy( _camera.projectionMatrix )
840
+ .invert();
841
+
842
+ this._opacityFactor = Math.floor( this._samples + 1 ) / Math.floor( this._quiltSamples + 1 );
843
+
844
+ do {
845
+
846
+ const ogCamera = this.camera;
847
+ this.camera = _camera;
848
+ cb();
849
+ this.camera = ogCamera;
850
+ yield;
851
+
852
+ } while ( this._samples % 1 !== 0 );
853
+
854
+ this._quiltSamples += 1 / viewCount;
855
+
856
+ }
857
+
858
+ this._quiltSamples = Math.round( this._quiltSamples );
859
+
860
+ }
861
+
862
+ }
863
+
864
+ // Helper for extracting the camera projection, offset, and quilt subframe needed
865
+ // for rendering a quilt with the provided parameters.
866
+ class QuiltViewUtility {
867
+
868
+ constructor() {
869
+
870
+ this.viewCount = 48;
871
+ this.quiltDimensions = new Vector2( 8, 6 );
872
+ this.viewCone = 35 * MathUtils.DEG2RAD;
873
+ this.viewFoV = 14 * MathUtils.DEG2RAD;
874
+ this.displayDistance = 1;
875
+ this.displayAspect = 0.75;
876
+ this.near = 0.01;
877
+ this.far = 10;
878
+
879
+ }
880
+
881
+ getCameraViewInfo( i, target = {} ) {
882
+
883
+ const {
884
+ quiltDimensions,
885
+ viewCone,
886
+ displayDistance,
887
+ viewCount,
888
+ viewFoV,
889
+ displayAspect,
890
+ near,
891
+ far,
892
+ } = this;
893
+
894
+ // initialize defaults
895
+ target.subframe = target.subframe || new Vector4();
896
+ target.offsetDirection = target.offsetDirection || new Vector3();
897
+ target.projectionMatrix = target.projectionMatrix || new Matrix4();
898
+
899
+ // set camera offset
900
+ const halfWidth = Math.tan( 0.5 * viewCone ) * displayDistance;
901
+ const totalWidth = halfWidth * 2.0;
902
+ const stride = totalWidth / ( viewCount - 1 );
903
+ const offset = viewCount === 1 ? 0 : - halfWidth + stride * i;
904
+ target.offsetDirection.set( 1.0, 0, 0 );
905
+ target.offset = offset;
906
+
907
+ // set the projection matrix
908
+ const displayHalfHeight = Math.tan( viewFoV * 0.5 ) * displayDistance;
909
+ const displayHalfWidth = displayAspect * displayHalfHeight;
910
+ const nearScale = near / displayDistance;
911
+
912
+ target.projectionMatrix.makePerspective(
913
+ nearScale * ( - displayHalfWidth - offset ), nearScale * ( displayHalfWidth - offset ),
914
+ nearScale * displayHalfHeight, nearScale * - displayHalfHeight,
915
+ near, far,
916
+ );
917
+
918
+ // set the quilt subframe
919
+ const x = i % quiltDimensions.x;
920
+ const y = Math.floor( i / quiltDimensions.x );
921
+
922
+ const qw = 1 / quiltDimensions.x;
923
+ const qh = 1 / quiltDimensions.y;
924
+ target.subframe.set( x * qw, y * qh, qw, qh );
925
+
926
+ return target;
927
+
928
+ }
929
+
930
+ setFromDisplayView( viewerDistance, displayWidth, displayHeight ) {
931
+
932
+ this.displayAspect = displayWidth / displayHeight;
933
+ this.displayDistance = viewerDistance;
934
+ this.viewFoV = 2.0 * Math.atan( 0.5 * displayHeight / viewerDistance );
935
+
936
+ }
937
+
938
+ }
939
+
940
+ class QuiltPathTracingRenderer extends PathTracingRenderer {
941
+
942
+ get samples() {
943
+
944
+ return this._samples / this.viewCount;
945
+
946
+ }
947
+
948
+ constructor( ...args ) {
949
+
950
+ super( ...args );
951
+
952
+ [
953
+ 'quiltDimensions',
954
+ 'viewCount',
955
+ 'viewCone',
956
+ 'viewFoV',
957
+ 'displayDistance',
958
+ 'displayAspect',
959
+ ].forEach( member => {
960
+
961
+ Object.defineProperty( this, member, {
962
+
963
+ enumerable: true,
964
+
965
+ set: v => {
966
+
967
+ this._quiltUtility[ member ] = v;
968
+
969
+ },
970
+
971
+ get: () => {
972
+
973
+ return this._quiltUtility[ member ];
974
+
975
+ }
976
+
977
+ } );
978
+
979
+ } );
980
+
981
+
982
+ this._quiltUtility = new QuiltViewUtility();
983
+ this._quiltSamples = 0;
984
+ this._camera = new PerspectiveCamera();
985
+ this._quiltTask = null;
986
+
987
+ }
988
+
989
+ setFromDisplayView( ...args ) {
990
+
991
+ this._quiltUtility.setFromDisplayView( ...args );
992
+
993
+ }
994
+
995
+ update() {
996
+
997
+ this.alpha = false;
998
+ if ( ! this._quiltTask ) {
999
+
1000
+ this._quiltTask = _task.call( this, () => {
1001
+
1002
+ super.update();
1003
+
1004
+ } );
1005
+
1006
+ }
1007
+
1008
+ this._quiltTask.next();
1009
+
1010
+ }
1011
+
1012
+ reset() {
1013
+
1014
+ super.reset();
1015
+ this._quiltTask = null;
1016
+ this._quiltSamples = 0;
1017
+
1018
+ }
1019
+
1020
+ }
1021
+
730
1022
  function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
731
1023
 
732
1024
  const indexAttr = geometry.index;
@@ -971,7 +1263,7 @@ class PathTracingSceneGenerator {
971
1263
 
972
1264
  meshes.push( c );
973
1265
 
974
- } else if ( c.isRectAreaLight || c.isSpotLight ) {
1266
+ } else if ( c.isRectAreaLight || c.isSpotLight || c.isDirectionalLight || c.isPointLight ) {
975
1267
 
976
1268
  lights.push( c );
977
1269
 
@@ -1825,7 +2117,7 @@ class MaterialsTexture extends DataTexture {
1825
2117
  }
1826
2118
 
1827
2119
  index ++;
1828
- index ++;
2120
+ floatArray[ index ++ ] = getField( m, 'sheen', 0.0 );
1829
2121
 
1830
2122
  // sample 7
1831
2123
  // sheen
@@ -2170,31 +2462,45 @@ class EquirectHdrInfoUniform {
2170
2462
 
2171
2463
  constructor() {
2172
2464
 
2465
+ // Default to a white texture and associated weights so we don't
2466
+ // just render black initially.
2467
+ const whiteTex = new DataTexture( new Float32Array( [ 1, 1, 1, 1 ] ), 1, 1 );
2468
+ whiteTex.type = FloatType;
2469
+ whiteTex.format = RGBAFormat;
2470
+ whiteTex.minFilter = LinearFilter;
2471
+ whiteTex.magFilter = LinearFilter;
2472
+ whiteTex.wrapS = RepeatWrapping;
2473
+ whiteTex.wrapT = RepeatWrapping;
2474
+ whiteTex.generateMipmaps = false;
2475
+ whiteTex.needsUpdate = true;
2476
+
2173
2477
  // Stores a map of [0, 1] value -> cumulative importance row & pdf
2174
2478
  // used to sampling a random value to a relevant row to sample from
2175
- const marginalWeights = new DataTexture();
2479
+ const marginalWeights = new DataTexture( new Float32Array( [ 0, 1 ] ), 1, 2 );
2176
2480
  marginalWeights.type = FloatType;
2177
2481
  marginalWeights.format = RedFormat;
2178
2482
  marginalWeights.minFilter = LinearFilter;
2179
2483
  marginalWeights.magFilter = LinearFilter;
2180
2484
  marginalWeights.generateMipmaps = false;
2485
+ marginalWeights.needsUpdate = true;
2181
2486
 
2182
2487
  // Stores a map of [0, 1] value -> cumulative importance column & pdf
2183
2488
  // used to sampling a random value to a relevant pixel to sample from
2184
- const conditionalWeights = new DataTexture();
2489
+ const conditionalWeights = new DataTexture( new Float32Array( [ 0, 0, 1, 1 ] ), 2, 2 );
2185
2490
  conditionalWeights.type = FloatType;
2186
2491
  conditionalWeights.format = RedFormat;
2187
2492
  conditionalWeights.minFilter = LinearFilter;
2188
2493
  conditionalWeights.magFilter = LinearFilter;
2189
2494
  conditionalWeights.generateMipmaps = false;
2495
+ conditionalWeights.needsUpdate = true;
2190
2496
 
2497
+ this.map = whiteTex;
2191
2498
  this.marginalWeights = marginalWeights;
2192
2499
  this.conditionalWeights = conditionalWeights;
2193
- this.map = null;
2194
2500
 
2195
2501
  // the total sum value is separated into two values to work around low precision
2196
2502
  // storage of floating values in structs
2197
- this.totalSumWhole = 0;
2503
+ this.totalSumWhole = 1;
2198
2504
  this.totalSumDecimal = 0;
2199
2505
 
2200
2506
  }
@@ -2203,7 +2509,7 @@ class EquirectHdrInfoUniform {
2203
2509
 
2204
2510
  this.marginalWeights.dispose();
2205
2511
  this.conditionalWeights.dispose();
2206
- if ( this.map ) this.map.dispose();
2512
+ this.map.dispose();
2207
2513
 
2208
2514
  }
2209
2515
 
@@ -2542,7 +2848,10 @@ class LightsInfoUniformStruct {
2542
2848
 
2543
2849
  } else if ( l.isPointLight ) {
2544
2850
 
2545
- const worldPosition = l.getWorldPosition( u );
2851
+ const worldPosition = u.setFromMatrixPosition( l.matrixWorld );
2852
+
2853
+ // sample 3
2854
+ // u vector
2546
2855
  floatArray[ baseIndex + ( index ++ ) ] = worldPosition.x;
2547
2856
  floatArray[ baseIndex + ( index ++ ) ] = worldPosition.y;
2548
2857
  floatArray[ baseIndex + ( index ++ ) ] = worldPosition.z;
@@ -2559,10 +2868,12 @@ class LightsInfoUniformStruct {
2559
2868
 
2560
2869
  } else if ( l.isDirectionalLight ) {
2561
2870
 
2562
- const worldPosition = l.getWorldPosition( u );
2563
- const targetPosition = l.target.getWorldPosition( v );
2564
-
2871
+ const worldPosition = u.setFromMatrixPosition( l.matrixWorld );
2872
+ const targetPosition = v.setFromMatrixPosition( l.target.matrixWorld );
2565
2873
  target.subVectors( worldPosition, targetPosition ).normalize();
2874
+
2875
+ // sample 3
2876
+ // u vector
2566
2877
  floatArray[ baseIndex + ( index ++ ) ] = target.x;
2567
2878
  floatArray[ baseIndex + ( index ++ ) ] = target.y;
2568
2879
  floatArray[ baseIndex + ( index ++ ) ] = target.z;
@@ -2984,6 +3295,22 @@ class IESProfilesTexture extends WebGLArrayRenderTarget {
2984
3295
 
2985
3296
  const shaderUtils = /* glsl */`
2986
3297
 
3298
+ #ifndef RAY_OFFSET
3299
+ #define RAY_OFFSET 1e-4
3300
+ #endif
3301
+
3302
+ // adjust the hit point by the surface normal by a factor of some offset and the
3303
+ // maximum component-wise value of the current point to accommodate floating point
3304
+ // error as values increase.
3305
+ vec3 stepRayOrigin( vec3 rayOrigin, vec3 rayDirection, vec3 offset, float dist ) {
3306
+
3307
+ vec3 point = rayOrigin + rayDirection * dist;
3308
+ vec3 absPoint = abs( point );
3309
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
3310
+ return point + offset * ( maxPoint + 1.0 ) * RAY_OFFSET;
3311
+
3312
+ }
3313
+
2987
3314
  // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume/README.md#attenuation
2988
3315
  vec3 transmissionAttenuation( float dist, vec3 attColor, float attDist ) {
2989
3316
 
@@ -3915,6 +4242,7 @@ const shaderMaterialStructs = /* glsl */ `
3915
4242
  float side;
3916
4243
  bool matte;
3917
4244
 
4245
+ float sheen;
3918
4246
  vec3 sheenColor;
3919
4247
  int sheenColorMap;
3920
4248
  float sheenRoughness;
@@ -4004,6 +4332,7 @@ const shaderMaterialStructs = /* glsl */ `
4004
4332
  m.clearcoatNormalMap = int( round( s5.a ) );
4005
4333
  m.clearcoatNormalScale = s6.rg;
4006
4334
 
4335
+ m.sheen = s6.a;
4007
4336
  m.sheenColor = s7.rgb;
4008
4337
  m.sheenColorMap = int( round( s7.a ) );
4009
4338
  m.sheenRoughness = s8.r;
@@ -4443,6 +4772,7 @@ vec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinF
4443
4772
  phi12 = PI;
4444
4773
 
4445
4774
  }
4775
+
4446
4776
  float phi21 = PI - phi12;
4447
4777
 
4448
4778
  // Second interface
@@ -4455,11 +4785,13 @@ vec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinF
4455
4785
  phi23[ 0 ] = PI;
4456
4786
 
4457
4787
  }
4788
+
4458
4789
  if ( baseIOR[1] < iridescenceIor ) {
4459
4790
 
4460
4791
  phi23[ 1 ] = PI;
4461
4792
 
4462
4793
  }
4794
+
4463
4795
  if ( baseIOR[2] < iridescenceIor ) {
4464
4796
 
4465
4797
  phi23[ 2 ] = PI;
@@ -4481,15 +4813,17 @@ vec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinF
4481
4813
 
4482
4814
  // Reflectance term for m > 0 (pairs of diracs)
4483
4815
  vec3 Cm = Rs - T121;
4484
- for ( int m = 1; m <= 2; ++ m )
4485
- {
4816
+ for ( int m = 1; m <= 2; ++ m ) {
4817
+
4486
4818
  Cm *= r123;
4487
4819
  vec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );
4488
4820
  I += Cm * Sm;
4821
+
4489
4822
  }
4490
4823
 
4491
4824
  // Since out of gamut colors might be produced, negative color values are clamped to 0.
4492
4825
  return max( I, vec3( 0.0 ) );
4826
+
4493
4827
  }
4494
4828
 
4495
4829
  `;
@@ -4523,6 +4857,7 @@ struct SurfaceRec {
4523
4857
  float clearcoat;
4524
4858
  float clearcoatRoughness;
4525
4859
  float filteredClearcoatRoughness;
4860
+ float sheen;
4526
4861
  vec3 sheenColor;
4527
4862
  float sheenRoughness;
4528
4863
  float iridescence;
@@ -4598,12 +4933,12 @@ float specularEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf, out vec3 color )
4598
4933
 
4599
4934
  // if roughness is set to 0 then D === NaN which results in black pixels
4600
4935
  float metalness = surf.metalness;
4601
- float filteredRoughness = surf.filteredRoughness;
4936
+ float roughness = surf.filteredRoughness;
4602
4937
 
4603
4938
  float eta = surf.eta;
4604
4939
  float f0 = surf.f0;
4605
- float G = ggxShadowMaskG2( wi, wo, filteredRoughness );
4606
- float D = ggxDistribution( wh, filteredRoughness );
4940
+ float G = ggxShadowMaskG2( wi, wo, roughness );
4941
+ float D = ggxDistribution( wh, roughness );
4607
4942
  float FM = disneyFresnel( surf, wo, wi, wh );
4608
4943
  float cosTheta = min( wo.z, 1.0 );
4609
4944
  float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
@@ -4614,20 +4949,23 @@ float specularEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf, out vec3 color )
4614
4949
 
4615
4950
  }
4616
4951
 
4617
- vec3 metalColor = surf.color;
4618
- vec3 dielectricColor = f0 * surf.specularColor;
4619
- vec3 specColor = mix( dielectricColor, metalColor, surf.metalness );
4952
+ vec3 baseColor = mix( f0 * surf.specularColor * surf.specularIntensity, surf.color, surf.metalness );
4953
+ vec3 iridescenceFresnel = evalIridescence( 1.0, surf.iridescenceIor, dot( wi, wh ), surf.iridescenceThickness, baseColor );
4620
4954
 
4621
- vec3 iridescenceF = evalIridescence( 1.0, surf.iridescenceIor, dot( wi, wh ), surf.iridescenceThickness, vec3( f0 ) );
4622
- vec3 iridescenceMix = mix( vec3( FM ), iridescenceF, surf.iridescence );
4623
- vec3 F = mix( specColor, vec3( 1.0 ), iridescenceMix );
4955
+ vec3 metalMix = mix( surf.color, iridescenceFresnel, surf.iridescence );
4956
+ vec3 metalFresnel = mix( metalMix, vec3( 1.0 ), FM );
4624
4957
 
4625
- color = mix( surf.specularIntensity, 1.0, surf.metalness ) * wi.z * F * G * D / ( 4.0 * abs( wi.z * wo.z ) );
4958
+ vec3 dielectricIriMix = mix( iridescenceFresnel, vec3( 1.0 ), FM );
4959
+ vec3 dielectricMix = mix( f0 * surf.specularColor, vec3( 1.0 ), FM ) * surf.specularIntensity;
4960
+ vec3 dielectricFresnel = mix( dielectricMix, dielectricIriMix, surf.iridescence );
4961
+
4962
+ vec3 F = mix( dielectricFresnel, metalFresnel, surf.metalness );
4963
+ color = wi.z * F * G * D / ( 4.0 * abs( wi.z * wo.z ) );
4626
4964
 
4627
4965
  // PDF
4628
4966
  // See 14.1.1 Microfacet BxDFs in https://www.pbr-book.org/
4629
4967
  float incidentTheta = acos( wo.z );
4630
- float G1 = ggxShadowMaskG1( incidentTheta, filteredRoughness );
4968
+ float G1 = ggxShadowMaskG1( incidentTheta, roughness );
4631
4969
  float ggxPdf = D * G1 * max( 0.0, abs( dot( wo, wh ) ) ) / abs ( wo.z );
4632
4970
  return ggxPdf / ( 4.0 * dot( wo, wh ) );
4633
4971
 
@@ -4636,10 +4974,10 @@ float specularEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf, out vec3 color )
4636
4974
  vec3 specularDirection( vec3 wo, SurfaceRec surf ) {
4637
4975
 
4638
4976
  // sample ggx vndf distribution which gives a new normal
4639
- float filteredRoughness = surf.filteredRoughness;
4977
+ float roughness = surf.filteredRoughness;
4640
4978
  vec3 halfVector = ggxDirection(
4641
4979
  wo,
4642
- vec2( filteredRoughness ),
4980
+ vec2( roughness ),
4643
4981
  sobol2( 12 )
4644
4982
  );
4645
4983
 
@@ -4716,7 +5054,7 @@ float transmissionEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf, out vec3 col
4716
5054
 
4717
5055
  vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
4718
5056
 
4719
- float roughness = surf.roughness;
5057
+ float roughness = surf.filteredRoughness;
4720
5058
  float eta = surf.eta;
4721
5059
  vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( sobol2( 13 ) ) * roughness );
4722
5060
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
@@ -4736,11 +5074,11 @@ float clearcoatEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf, inout vec3 colo
4736
5074
  float ior = 1.5;
4737
5075
  float f0 = iorRatioToF0( ior );
4738
5076
  bool frontFace = surf.frontFace;
4739
- float filteredClearcoatRoughness = surf.filteredClearcoatRoughness;
5077
+ float roughness = surf.filteredClearcoatRoughness;
4740
5078
 
4741
5079
  float eta = frontFace ? 1.0 / ior : ior;
4742
- float G = ggxShadowMaskG2( wi, wo, filteredClearcoatRoughness );
4743
- float D = ggxDistribution( wh, filteredClearcoatRoughness );
5080
+ float G = ggxShadowMaskG2( wi, wo, roughness );
5081
+ float D = ggxDistribution( wh, roughness );
4744
5082
  float F = schlickFresnel( dot( wi, wh ), f0 );
4745
5083
  float cosTheta = min( wo.z, 1.0 );
4746
5084
  float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
@@ -4756,17 +5094,17 @@ float clearcoatEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf, inout vec3 colo
4756
5094
 
4757
5095
  // PDF
4758
5096
  // See equation (27) in http://jcgt.org/published/0003/02/03/
4759
- return ggxPDF( wo, wh, filteredClearcoatRoughness ) / ( 4.0 * dot( wi, wh ) );
5097
+ return ggxPDF( wo, wh, roughness ) / ( 4.0 * dot( wi, wh ) );
4760
5098
 
4761
5099
  }
4762
5100
 
4763
5101
  vec3 clearcoatDirection( vec3 wo, SurfaceRec surf ) {
4764
5102
 
4765
5103
  // sample ggx vndf distribution which gives a new normal
4766
- float filteredClearcoatRoughness = surf.filteredClearcoatRoughness;
5104
+ float roughness = surf.filteredClearcoatRoughness;
4767
5105
  vec3 halfVector = ggxDirection(
4768
5106
  wo,
4769
- vec2( filteredClearcoatRoughness ),
5107
+ vec2( roughness ),
4770
5108
  sobol2( 14 )
4771
5109
  );
4772
5110
 
@@ -4795,11 +5133,10 @@ vec3 sheenColor( vec3 wo, vec3 wi, vec3 wh, SurfaceRec surf ) {
4795
5133
  }
4796
5134
 
4797
5135
  // bsdf
4798
- #define DIFF_WEIGHT 0
4799
- #define SPEC_WEIGHT 1
4800
- #define TRANS_WEIGHT 2
4801
- #define CC_WEIGHT 3
4802
- void getLobeWeights( vec3 wo, vec3 wi, vec3 wh, vec3 clearcoatWo, SurfaceRec surf, out float[ 4 ] weights ) {
5136
+ void getLobeWeights(
5137
+ vec3 wo, vec3 wi, vec3 wh, vec3 clearcoatWo, SurfaceRec surf,
5138
+ out float diffuseWeight, out float specularWeight, out float transmissionWeight, out float clearcoatWeight
5139
+ ) {
4803
5140
 
4804
5141
  float metalness = surf.metalness;
4805
5142
  float transmission = surf.transmission;
@@ -4821,25 +5158,22 @@ void getLobeWeights( vec3 wo, vec3 wi, vec3 wh, vec3 clearcoatWo, SurfaceRec sur
4821
5158
  float transSpecularProb = mix( max( 0.25, reflectance ), 1.0, metalness );
4822
5159
  float diffSpecularProb = 0.5 + 0.5 * metalness;
4823
5160
 
4824
- float diffuseWeight = ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb );
4825
- float specularWeight = transmission * transSpecularProb + ( 1.0 - transmission ) * diffSpecularProb;
4826
- float transmissionWeight = transmission * ( 1.0 - transSpecularProb );
4827
- float clearcoatWeight = surf.clearcoat * schlickFresnel( clearcoatWo.z, 0.04 );
5161
+ diffuseWeight = ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb );
5162
+ specularWeight = transmission * transSpecularProb + ( 1.0 - transmission ) * diffSpecularProb;
5163
+ transmissionWeight = transmission * ( 1.0 - transSpecularProb );
5164
+ clearcoatWeight = surf.clearcoat * schlickFresnel( clearcoatWo.z, 0.04 );
4828
5165
 
4829
5166
  float totalWeight = diffuseWeight + specularWeight + transmissionWeight + clearcoatWeight;
4830
- weights[ DIFF_WEIGHT ] = diffuseWeight / totalWeight;
4831
- weights[ SPEC_WEIGHT ] = specularWeight / totalWeight;
4832
- weights[ TRANS_WEIGHT ] = transmissionWeight / totalWeight;
4833
- weights[ CC_WEIGHT ] = clearcoatWeight / totalWeight;
4834
-
5167
+ diffuseWeight /= totalWeight;
5168
+ specularWeight /= totalWeight;
5169
+ transmissionWeight /= totalWeight;
5170
+ clearcoatWeight /= totalWeight;
4835
5171
  }
4836
5172
 
4837
- float bsdfEval( vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRec surf, float[ 4 ] weights, out float specularPdf, out vec3 color ) {
4838
-
4839
- float diffuseWeight = weights[ DIFF_WEIGHT ];
4840
- float specularWeight = weights[ SPEC_WEIGHT ];
4841
- float transmissionWeight = weights[ TRANS_WEIGHT ];
4842
- float clearcoatWeight = weights[ CC_WEIGHT ];
5173
+ float bsdfEval(
5174
+ vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRec surf,
5175
+ float diffuseWeight, float specularWeight, float transmissionWeight, float clearcoatWeight, out float specularPdf, out vec3 color
5176
+ ) {
4843
5177
 
4844
5178
  float metalness = surf.metalness;
4845
5179
  float transmission = surf.transmission;
@@ -4889,8 +5223,8 @@ float bsdfEval( vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRec
4889
5223
  }
4890
5224
 
4891
5225
  // sheen
4892
- color *= sheenAlbedoScaling( wo, wi, surf );
4893
- color += sheenColor( wo, wi, halfVector, surf );
5226
+ color *= mix( 1.0, sheenAlbedoScaling( wo, wi, surf ), surf.sheen );
5227
+ color += sheenColor( wo, wi, halfVector, surf ) * surf.sheen;
4894
5228
 
4895
5229
  // clearcoat
4896
5230
  if ( clearcoatWi.z >= 0.0 && clearcoatWeight > 0.0 ) {
@@ -4915,20 +5249,32 @@ float bsdfEval( vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRec
4915
5249
 
4916
5250
  float bsdfResult( vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRec surf, out vec3 color ) {
4917
5251
 
4918
- float[ 4 ] pdf;
4919
5252
  vec3 wh = getHalfVector( wo, wi, surf.eta );
4920
- getLobeWeights( wo, wi, wh, clearcoatWo, surf, pdf );
5253
+ float diffuseWeight;
5254
+ float specularWeight;
5255
+ float transmissionWeight;
5256
+ float clearcoatWeight;
5257
+ getLobeWeights( wo, wi, wh, clearcoatWo, surf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight );
4921
5258
 
4922
5259
  float specularPdf;
4923
- return bsdfEval( wo, clearcoatWo, wi, clearcoatWi, surf, pdf, specularPdf, color );
5260
+ return bsdfEval( wo, clearcoatWo, wi, clearcoatWi, surf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight, specularPdf, color );
4924
5261
 
4925
5262
  }
4926
5263
 
4927
5264
  SampleRec bsdfSample( vec3 wo, vec3 clearcoatWo, mat3 normalBasis, mat3 invBasis, mat3 clearcoatNormalBasis, mat3 clearcoatInvBasis, SurfaceRec surf ) {
4928
5265
 
5266
+ float diffuseWeight;
5267
+ float specularWeight;
5268
+ float transmissionWeight;
5269
+ float clearcoatWeight;
4929
5270
  // using normal and basically-reflected ray since we don't have proper half vector here
5271
+ getLobeWeights( wo, wo, vec3( 0, 0, 1 ), clearcoatWo, surf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight );
5272
+
4930
5273
  float pdf[4];
4931
- getLobeWeights( wo, wo, vec3( 0, 0, 1 ), clearcoatWo, surf, pdf );
5274
+ pdf[0] = diffuseWeight;
5275
+ pdf[1] = specularWeight;
5276
+ pdf[2] = transmissionWeight;
5277
+ pdf[3] = clearcoatWeight;
4932
5278
 
4933
5279
  float cdf[4];
4934
5280
  cdf[0] = pdf[0];
@@ -4980,7 +5326,7 @@ SampleRec bsdfSample( vec3 wo, vec3 clearcoatWo, mat3 normalBasis, mat3 invBasis
4980
5326
  }
4981
5327
 
4982
5328
  SampleRec result;
4983
- result.pdf = bsdfEval( wo, clearcoatWo, wi, clearcoatWi, surf, pdf, result.specularPdf, result.color );
5329
+ result.pdf = bsdfEval( wo, clearcoatWo, wi, clearcoatWi, surf, diffuseWeight, specularWeight, transmissionWeight, clearcoatWeight, result.specularPdf, result.color );
4984
5330
  result.direction = wi;
4985
5331
  result.clearcoatDirection = clearcoatWi;
4986
5332
 
@@ -5580,9 +5926,9 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5580
5926
 
5581
5927
  defines: {
5582
5928
  FEATURE_MIS: 1,
5929
+ FEATURE_RUSSIAN_ROULETTE: 1,
5583
5930
  FEATURE_DOF: 1,
5584
5931
  FEATURE_BACKGROUND_MAP: 0,
5585
- TRANSPARENT_TRAVERSALS: 5,
5586
5932
  // 0 = Perspective
5587
5933
  // 1 = Orthographic
5588
5934
  // 2 = Equirectangular
@@ -5597,7 +5943,8 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5597
5943
  uniforms: {
5598
5944
  resolution: { value: new Vector2() },
5599
5945
 
5600
- bounces: { value: 3 },
5946
+ bounces: { value: 10 },
5947
+ transmissiveBounces: { value: 10 },
5601
5948
  physicalCamera: { value: new PhysicalCameraUniform() },
5602
5949
 
5603
5950
  bvh: { value: new MeshBVHUniformStruct() },
@@ -5640,6 +5987,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5640
5987
 
5641
5988
  fragmentShader: /* glsl */`
5642
5989
  #define RAY_OFFSET 1e-4
5990
+ #define INFINITY 1e20
5643
5991
 
5644
5992
  precision highp isampler2D;
5645
5993
  precision highp usampler2D;
@@ -5678,6 +6026,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5678
6026
 
5679
6027
  uniform vec2 resolution;
5680
6028
  uniform int bounces;
6029
+ uniform int transmissiveBounces;
5681
6030
  uniform mat4 cameraWorldMatrix;
5682
6031
  uniform mat4 invProjectionMatrix;
5683
6032
  uniform sampler2DArray attributesArray;
@@ -5727,7 +6076,10 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5727
6076
  }
5728
6077
 
5729
6078
  // step through multiple surface hits and accumulate color attenuation based on transmissive surfaces
5730
- bool attenuateHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, int traversals, bool isShadowRay, out vec3 color ) {
6079
+ bool attenuateHit(
6080
+ BVH bvh, vec3 rayOrigin, vec3 rayDirection, float rayDist,
6081
+ int traversals, int transparentTraversals, bool isShadowRay, out vec3 color
6082
+ ) {
5731
6083
 
5732
6084
  // hit results
5733
6085
  uvec4 faceIndices = uvec4( 0u );
@@ -5744,6 +6096,12 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5744
6096
 
5745
6097
  if ( bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
5746
6098
 
6099
+ if ( dist > rayDist ) {
6100
+
6101
+ return true;
6102
+
6103
+ }
6104
+
5747
6105
  // TODO: attenuate the contribution based on the PDF of the resulting ray including refraction values
5748
6106
  // Should be able to work using the material BSDF functions which will take into account specularity, etc.
5749
6107
  // TODO: should we account for emissive surfaces here?
@@ -5755,11 +6113,8 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5755
6113
  Material material = readMaterialInfo( materials, materialIndex );
5756
6114
 
5757
6115
  // adjust the ray to the new surface
5758
- bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
5759
- vec3 point = rayOrigin + rayDirection * dist;
5760
- vec3 absPoint = abs( point );
5761
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
5762
- rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
6116
+ bool isEntering = side == 1.0;
6117
+ rayOrigin = stepRayOrigin( rayOrigin, rayDirection, - faceNormal, dist );
5763
6118
 
5764
6119
  if ( ! material.castShadow && isShadowRay ) {
5765
6120
 
@@ -5829,7 +6184,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5829
6184
 
5830
6185
  }
5831
6186
 
5832
- if ( side == 1.0 && isBelowSurface ) {
6187
+ if ( side == 1.0 && isEntering ) {
5833
6188
 
5834
6189
  // only attenuate by surface color on the way in
5835
6190
  color *= mix( vec3( 1.0 ), albedo.rgb, transmissionFactor );
@@ -5841,6 +6196,15 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5841
6196
 
5842
6197
  }
5843
6198
 
6199
+ bool isTransmissiveRay = dot( rayDirection, faceNormal * side ) < 0.0;
6200
+ if ( ( isTransmissiveRay || isEntering ) && transparentTraversals > 0 ) {
6201
+
6202
+ transparentTraversals --;
6203
+ i --;
6204
+
6205
+ }
6206
+
6207
+
5844
6208
  } else {
5845
6209
 
5846
6210
  return false;
@@ -5853,19 +6217,6 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5853
6217
 
5854
6218
  }
5855
6219
 
5856
- // returns whether the ray hit anything before a certain distance, not just the first surface. Could be optimized to not check the full hierarchy.
5857
- bool anyCloserHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, float maxDist ) {
5858
-
5859
- uvec4 faceIndices = uvec4( 0u );
5860
- vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
5861
- vec3 barycoord = vec3( 0.0 );
5862
- float side = 1.0;
5863
- float dist = 0.0;
5864
- bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
5865
- return hit && dist < maxDist;
5866
-
5867
- }
5868
-
5869
6220
  vec3 ndcToRayOrigin( vec2 coord ) {
5870
6221
 
5871
6222
  vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( coord, - 1.0, 1.0 );
@@ -5974,7 +6325,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5974
6325
  float accumulatedRoughness = 0.0;
5975
6326
  float accumulatedClearcoatRoughness = 0.0;
5976
6327
  bool transmissiveRay = true;
5977
- int transparentTraversals = TRANSPARENT_TRAVERSALS;
6328
+ int transparentTraversals = transmissiveBounces;
5978
6329
  vec3 throughputColor = vec3( 1.0 );
5979
6330
  SampleRec sampleRec;
5980
6331
  int i;
@@ -5985,12 +6336,12 @@ class PhysicalPathTracingMaterial extends MaterialBase {
5985
6336
  sobolBounceIndex ++;
5986
6337
 
5987
6338
  bool hit = bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
5988
-
6339
+ bool firstRay = i == 0 && transparentTraversals == transmissiveBounces;
5989
6340
  LightSampleRec lightHit = lightsClosestHit( lights.tex, lights.count, rayOrigin, rayDirection );
5990
6341
 
5991
- if ( lightHit.hit && ( lightHit.dist < dist || !hit ) ) {
6342
+ if ( lightHit.hit && ( lightHit.dist < dist || ! hit ) ) {
5992
6343
 
5993
- if ( i == 0 || transmissiveRay ) {
6344
+ if ( firstRay || transmissiveRay ) {
5994
6345
 
5995
6346
  gl_FragColor.rgb += lightHit.emission * throughputColor;
5996
6347
 
@@ -6024,7 +6375,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6024
6375
 
6025
6376
  if ( ! hit ) {
6026
6377
 
6027
- if ( i == 0 || transmissiveRay ) {
6378
+ if ( firstRay || transmissiveRay ) {
6028
6379
 
6029
6380
  gl_FragColor.rgb += sampleBackground( envRotation3x3 * rayDirection, sobol2( 2 ) ) * throughputColor;
6030
6381
  gl_FragColor.a = backgroundAlpha;
@@ -6059,7 +6410,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6059
6410
  uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
6060
6411
  Material material = readMaterialInfo( materials, materialIndex );
6061
6412
 
6062
- if ( material.matte && i == 0 ) {
6413
+ if ( material.matte && firstRay ) {
6063
6414
 
6064
6415
  gl_FragColor = vec4( 0.0 );
6065
6416
  break;
@@ -6070,11 +6421,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6070
6421
  // then skip it
6071
6422
  if ( ! material.castShadow && isShadowRay ) {
6072
6423
 
6073
- vec3 point = rayOrigin + rayDirection * dist;
6074
- vec3 absPoint = abs( point );
6075
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
6076
- rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
6077
-
6424
+ rayOrigin = stepRayOrigin( rayOrigin, rayDirection, - faceNormal, dist );
6078
6425
  continue;
6079
6426
 
6080
6427
  }
@@ -6123,10 +6470,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6123
6470
  || material.transparent && ! useAlphaTest && albedo.a < sobol( 3 )
6124
6471
  ) {
6125
6472
 
6126
- vec3 point = rayOrigin + rayDirection * dist;
6127
- vec3 absPoint = abs( point );
6128
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
6129
- rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
6473
+ rayOrigin = stepRayOrigin( rayOrigin, rayDirection, - faceNormal, dist );
6130
6474
 
6131
6475
  // only allow a limited number of transparency discards otherwise we could
6132
6476
  // crash the context with too long a loop.
@@ -6333,6 +6677,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6333
6677
  surfaceRec.metalness = metalness;
6334
6678
  surfaceRec.color = albedo.rgb;
6335
6679
  surfaceRec.clearcoat = clearcoat;
6680
+ surfaceRec.sheen = material.sheen;
6336
6681
  surfaceRec.sheenColor = sheenColor;
6337
6682
  surfaceRec.iridescence = iridescence;
6338
6683
  surfaceRec.iridescenceIor = material.iridescenceIor;
@@ -6342,11 +6687,12 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6342
6687
  surfaceRec.attenuationColor = material.attenuationColor;
6343
6688
  surfaceRec.attenuationDistance = material.attenuationDistance;
6344
6689
 
6345
- // apply perceptual roughness factor from gltf
6690
+ // apply perceptual roughness factor from gltf. sheen perceptual roughness is
6691
+ // applied by its brdf function
6346
6692
  // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#microfacet-surfaces
6347
6693
  surfaceRec.roughness = roughness * roughness;
6348
6694
  surfaceRec.clearcoatRoughness = clearcoatRoughness * clearcoatRoughness;
6349
- surfaceRec.sheenRoughness = sheenRoughness * sheenRoughness;
6695
+ surfaceRec.sheenRoughness = sheenRoughness;
6350
6696
 
6351
6697
  // frontFace is used to determine transmissive properties and PDF. If no transmission is used
6352
6698
  // then we can just always assume this is a front face.
@@ -6372,18 +6718,14 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6372
6718
  vec3 clearcoatOutgoing = - normalize( clearcoatInvBasis * rayDirection );
6373
6719
  sampleRec = bsdfSample( outgoing, clearcoatOutgoing, normalBasis, invBasis, clearcoatNormalBasis, clearcoatInvBasis, surfaceRec );
6374
6720
 
6721
+ bool wasBelowSurface = dot( rayDirection, faceNormal ) > 0.0;
6375
6722
  isShadowRay = sampleRec.specularPdf < sobol( 4 );
6376
6723
 
6377
- // adjust the hit point by the surface normal by a factor of some offset and the
6378
- // maximum component-wise value of the current point to accommodate floating point
6379
- // error as values increase.
6380
- vec3 point = rayOrigin + rayDirection * dist;
6381
- vec3 absPoint = abs( point );
6382
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
6724
+ vec3 prevRayDirection = rayDirection;
6383
6725
  rayDirection = normalize( normalBasis * sampleRec.direction );
6384
6726
 
6385
6727
  bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
6386
- rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
6728
+ rayOrigin = stepRayOrigin( rayOrigin, prevRayDirection, isBelowSurface ? - faceNormal : faceNormal, dist );
6387
6729
 
6388
6730
  // direct env map sampling
6389
6731
  #if FEATURE_MIS
@@ -6402,10 +6744,11 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6402
6744
  }
6403
6745
 
6404
6746
  // check if a ray could even reach the light area
6747
+ vec3 attenuatedColor;
6405
6748
  if (
6406
6749
  lightSampleRec.pdf > 0.0 &&
6407
6750
  isDirectionValid( lightSampleRec.direction, normal, faceNormal ) &&
6408
- ! anyCloserHit( bvh, rayOrigin, lightSampleRec.direction, lightSampleRec.dist )
6751
+ ! attenuateHit( bvh, rayOrigin, lightSampleRec.direction, lightSampleRec.dist, bounces - i, transparentTraversals, isShadowRay, attenuatedColor )
6409
6752
  ) {
6410
6753
 
6411
6754
  // get the material pdf
@@ -6417,7 +6760,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6417
6760
  // weight the direct light contribution
6418
6761
  float lightPdf = lightSampleRec.pdf / float( lights.count + 1u );
6419
6762
  float misWeight = lightSampleRec.type == SPOT_LIGHT_TYPE || lightSampleRec.type == DIR_LIGHT_TYPE || lightSampleRec.type == POINT_LIGHT_TYPE ? 1.0 : misHeuristic( lightPdf, lightMaterialPdf );
6420
- gl_FragColor.rgb += lightSampleRec.emission * throughputColor * sampleColor * misWeight / lightPdf;
6763
+ gl_FragColor.rgb += attenuatedColor * lightSampleRec.emission * throughputColor * sampleColor * misWeight / lightPdf;
6421
6764
 
6422
6765
  }
6423
6766
 
@@ -6445,7 +6788,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6445
6788
  if (
6446
6789
  envPdf > 0.0 &&
6447
6790
  isDirectionValid( envDirection, normal, faceNormal ) &&
6448
- ! attenuateHit( bvh, rayOrigin, envDirection, bounces - i, isShadowRay, attenuatedColor )
6791
+ ! attenuateHit( bvh, rayOrigin, envDirection, INFINITY, bounces - i, transparentTraversals, isShadowRay, attenuatedColor )
6449
6792
  ) {
6450
6793
 
6451
6794
  // get the material pdf
@@ -6481,6 +6824,16 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6481
6824
 
6482
6825
  }
6483
6826
 
6827
+ // if we're bouncing around the inside a transmissive material then decrement
6828
+ // perform this separate from a bounce
6829
+ bool isTransmissiveRay = dot( rayDirection, faceNormal * side ) < 0.0;
6830
+ if ( ( isTransmissiveRay || isBelowSurface ) && transparentTraversals > 0 ) {
6831
+
6832
+ transparentTraversals --;
6833
+ i --;
6834
+
6835
+ }
6836
+
6484
6837
  // accumulate color
6485
6838
  gl_FragColor.rgb += ( emission * throughputColor );
6486
6839
 
@@ -6491,6 +6844,29 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6491
6844
 
6492
6845
  }
6493
6846
 
6847
+ #if FEATURE_RUSSIAN_ROULETTE
6848
+
6849
+ // russian roulette path termination
6850
+ // https://www.arnoldrenderer.com/research/physically_based_shader_design_in_arnold.pdf
6851
+ uint minBounces = 3u;
6852
+ float depthProb = float( sobolBounceIndex < minBounces );
6853
+
6854
+ float rrProb = luminance( throughputColor * sampleRec.color / sampleRec.pdf );
6855
+ rrProb /= luminance( throughputColor );
6856
+ rrProb = sqrt( rrProb );
6857
+ rrProb = max( rrProb, depthProb );
6858
+ rrProb = min( rrProb, 1.0 );
6859
+ if ( sobol( 8 ) > rrProb ) {
6860
+
6861
+ break;
6862
+
6863
+ }
6864
+
6865
+ // perform sample clamping here to avoid bright pixels
6866
+ throughputColor *= min( 1.0 / rrProb, 20.0 );
6867
+
6868
+ #endif
6869
+
6494
6870
  throughputColor *= sampleRec.color / sampleRec.pdf;
6495
6871
 
6496
6872
  // attenuate the throughput color by the medium color
@@ -6507,6 +6883,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6507
6883
 
6508
6884
  }
6509
6885
 
6886
+
6510
6887
  }
6511
6888
 
6512
6889
  gl_FragColor.a *= opacity;
@@ -6525,5 +6902,5 @@ class PhysicalPathTracingMaterial extends MaterialBase {
6525
6902
 
6526
6903
  // core
6527
6904
 
6528
- export { BlurredEnvMapGenerator, DenoiseMaterial, DynamicPathTracingSceneGenerator, EquirectCamera, EquirectHdrInfoUniform, GradientEquirectTexture, GraphMaterial, IESLoader, IESProfilesTexture, LightsInfoUniformStruct, MaterialBase, MaterialReducer, MaterialsTexture, PathTracingRenderer, PathTracingSceneGenerator, PhysicalCamera, PhysicalCameraUniform, PhysicalPathTracingMaterial, PhysicalSpotLight, ProceduralEquirectTexture, RenderTarget2DArray, ShapedAreaLight, getGroupMaterialIndicesAttribute, mergeMeshes, setCommonAttributes, shaderLightStruct, shaderMaterialSampling, shaderMaterialStructs, shaderUtils, trimToAttributes };
6905
+ export { BlurredEnvMapGenerator, DenoiseMaterial, DynamicPathTracingSceneGenerator, EquirectCamera, EquirectHdrInfoUniform, GradientEquirectTexture, GraphMaterial, IESLoader, IESProfilesTexture, LightsInfoUniformStruct, MaterialBase, MaterialReducer, MaterialsTexture, PathTracingRenderer, PathTracingSceneGenerator, PhysicalCamera, PhysicalCameraUniform, PhysicalPathTracingMaterial, PhysicalSpotLight, ProceduralEquirectTexture, QuiltPathTracingRenderer, RenderTarget2DArray, ShapedAreaLight, getGroupMaterialIndicesAttribute, mergeMeshes, setCommonAttributes, shaderLightStruct, shaderMaterialSampling, shaderMaterialStructs, shaderUtils, trimToAttributes };
6529
6906
  //# sourceMappingURL=index.module.js.map