three-gpu-pathtracer 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/build/index.module.js +1013 -322
  2. package/build/index.module.js.map +1 -1
  3. package/build/index.umd.cjs +1010 -320
  4. package/build/index.umd.cjs.map +1 -1
  5. package/package.json +2 -2
  6. package/src/core/DynamicPathTracingSceneGenerator.js +80 -40
  7. package/src/core/PathTracingRenderer.js +28 -34
  8. package/src/core/PathTracingSceneGenerator.js +11 -60
  9. package/src/materials/pathtracing/LambertPathTracingMaterial.js +1 -1
  10. package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +37 -12
  11. package/src/materials/pathtracing/glsl/attenuateHit.glsl.js +19 -9
  12. package/src/materials/pathtracing/glsl/cameraUtils.glsl.js +2 -2
  13. package/src/materials/pathtracing/glsl/directLightContribution.glsl.js +4 -4
  14. package/src/materials/pathtracing/glsl/getSurfaceRecord.glsl.js +2 -1
  15. package/src/materials/pathtracing/glsl/traceScene.glsl.js +1 -1
  16. package/src/shader/bsdf/bsdfSampling.glsl.js +14 -10
  17. package/src/shader/common/fresnel.glsl.js +15 -9
  18. package/src/shader/rand/pcg.glsl.js +4 -4
  19. package/src/shader/rand/stratifiedTexture.glsl.js +45 -0
  20. package/src/shader/sampling/equirectSampling.glsl.js +8 -1
  21. package/src/shader/structs/lightsStruct.glsl.js +5 -7
  22. package/src/textures/BlueNoiseTexture.js +87 -0
  23. package/src/textures/ProceduralEquirectTexture.js +7 -8
  24. package/src/textures/blueNoise/BlueNoiseGenerator.js +115 -0
  25. package/src/textures/blueNoise/BlueNoiseSamples.js +214 -0
  26. package/src/textures/blueNoise/utils.js +24 -0
  27. package/src/uniforms/EquirectHdrInfoUniform.js +45 -8
  28. package/src/uniforms/LightsInfoUniformStruct.js +11 -7
  29. package/src/uniforms/MaterialsTexture.js +1 -1
  30. package/src/uniforms/RenderTarget2DArray.js +50 -3
  31. package/src/uniforms/StratifiedSamplesTexture.js +49 -0
  32. package/src/uniforms/stratified/StratifiedSampler.js +73 -0
  33. package/src/uniforms/stratified/StratifiedSamplerCombined.js +59 -0
  34. package/src/uniforms/utils.js +1 -1
  35. package/src/utils/GeometryPreparationUtils.js +8 -101
  36. package/src/workers/PathTracingSceneWorker.js +18 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-gpu-pathtracer",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Path tracing renderer and utilities for three.js built on top of three-mesh-bvh.",
5
5
  "module": "src/index.js",
6
6
  "main": "build/index.umd.cjs",
@@ -42,7 +42,7 @@
42
42
  "rollup": "^2.70.0",
43
43
  "simple-git": "^3.10.0",
44
44
  "three": "^0.160.0",
45
- "three-mesh-bvh": "0.7.0",
45
+ "three-mesh-bvh": "0.7.3",
46
46
  "yargs": "^17.5.1"
47
47
  },
48
48
  "peerDependencies": {
@@ -1,7 +1,16 @@
1
- import { BufferGeometry } from 'three';
2
- import { StaticGeometryGenerator, MeshBVH } from 'three-mesh-bvh';
1
+ import { BufferGeometry, MeshBasicMaterial, BufferAttribute, Mesh } from 'three';
2
+ import { StaticGeometryGenerator, MeshBVH, SAH } from 'three-mesh-bvh';
3
3
  import { setCommonAttributes, getGroupMaterialIndicesAttribute } from '../utils/GeometryPreparationUtils.js';
4
4
 
5
+ const dummyMaterial = new MeshBasicMaterial();
6
+ export function getDummyMesh() {
7
+
8
+ const emptyGeometry = new BufferGeometry();
9
+ emptyGeometry.setAttribute( 'position', new BufferAttribute( new Float32Array( 9 ), 3 ) );
10
+ return new Mesh( emptyGeometry, dummyMaterial );
11
+
12
+ }
13
+
5
14
  export class DynamicPathTracingSceneGenerator {
6
15
 
7
16
  get initialized() {
@@ -10,9 +19,29 @@ export class DynamicPathTracingSceneGenerator {
10
19
 
11
20
  }
12
21
 
13
- constructor( scene ) {
22
+ constructor( objects ) {
23
+
24
+ // ensure the objects is an array
25
+ if ( ! Array.isArray( objects ) ) {
26
+
27
+ objects = [ objects ];
28
+
29
+ }
30
+
31
+ // use a dummy object for a fallback
32
+ const finalObjects = [ ...objects ];
33
+ if ( finalObjects.length === 0 ) {
34
+
35
+ finalObjects.push( getDummyMesh() );
36
+
37
+ }
38
+
39
+ // options
40
+ this.bvhOptions = {};
41
+ this.attributes = [ 'position', 'normal', 'tangent', 'color', 'uv', 'uv2' ];
14
42
 
15
- this.objects = Array.isArray( scene ) ? scene : [ scene ];
43
+ // state
44
+ this.objects = finalObjects;
16
45
  this.bvh = null;
17
46
  this.geometry = new BufferGeometry();
18
47
  this.materials = null;
@@ -36,64 +65,75 @@ export class DynamicPathTracingSceneGenerator {
36
65
 
37
66
  dispose() {}
38
67
 
39
- generate() {
68
+ prepScene() {
40
69
 
41
- const { objects, staticGeometryGenerator, geometry, lights } = this;
42
- if ( this.bvh === null ) {
70
+ if ( this.bvh !== null ) {
43
71
 
44
- const attributes = [ 'position', 'normal', 'tangent', 'uv', 'color' ];
72
+ return;
45
73
 
46
- for ( let i = 0, l = objects.length; i < l; i ++ ) {
74
+ }
47
75
 
48
- objects[ i ].traverse( c => {
76
+ const { objects, staticGeometryGenerator, geometry, lights, attributes } = this;
77
+ for ( let i = 0, l = objects.length; i < l; i ++ ) {
49
78
 
50
- if ( c.isMesh ) {
79
+ objects[ i ].traverse( c => {
51
80
 
52
- const normalMapRequired = ! ! c.material.normalMap;
53
- setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
81
+ if ( c.isMesh ) {
54
82
 
55
- } else if (
56
- c.isRectAreaLight ||
57
- c.isSpotLight ||
58
- c.isPointLight ||
59
- c.isDirectionalLight
60
- ) {
83
+ const normalMapRequired = ! ! c.material.normalMap;
84
+ setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
61
85
 
62
- lights.push( c );
86
+ } else if (
87
+ c.isRectAreaLight ||
88
+ c.isSpotLight ||
89
+ c.isPointLight ||
90
+ c.isDirectionalLight
91
+ ) {
63
92
 
64
- }
93
+ lights.push( c );
65
94
 
66
- } );
95
+ }
67
96
 
68
- }
97
+ } );
69
98
 
70
- const textureSet = new Set();
71
- const materials = staticGeometryGenerator.getMaterials();
72
- materials.forEach( material => {
99
+ }
73
100
 
74
- for ( const key in material ) {
101
+ const textureSet = new Set();
102
+ const materials = staticGeometryGenerator.getMaterials();
103
+ materials.forEach( material => {
75
104
 
76
- const value = material[ key ];
77
- if ( value && value.isTexture ) {
105
+ for ( const key in material ) {
78
106
 
79
- textureSet.add( value );
107
+ const value = material[ key ];
108
+ if ( value && value.isTexture ) {
80
109
 
81
- }
110
+ textureSet.add( value );
82
111
 
83
112
  }
84
113
 
85
- } );
114
+ }
86
115
 
87
- staticGeometryGenerator.attributes = attributes;
88
- staticGeometryGenerator.generate( geometry );
116
+ } );
117
+
118
+ staticGeometryGenerator.attributes = attributes;
119
+ staticGeometryGenerator.generate( geometry );
89
120
 
90
- const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
91
- geometry.setAttribute( 'materialIndex', materialIndexAttribute );
92
- geometry.clearGroups();
121
+ const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
122
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
123
+ geometry.clearGroups();
124
+
125
+ this.materials = materials;
126
+ this.textures = Array.from( textureSet );
127
+
128
+ }
129
+
130
+ generate() {
131
+
132
+ const { objects, staticGeometryGenerator, geometry, bvhOptions } = this;
133
+ if ( this.bvh === null ) {
93
134
 
94
- this.bvh = new MeshBVH( geometry );
95
- this.materials = materials;
96
- this.textures = Array.from( textureSet );
135
+ this.prepScene();
136
+ this.bvh = new MeshBVH( geometry, { strategy: SAH, maxLeafTris: 1, ...bvhOptions } );
97
137
 
98
138
  return {
99
139
  lights: this.lights,
@@ -3,9 +3,6 @@ import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
3
3
  import { BlendMaterial } from '../materials/fullscreen/BlendMaterial.js';
4
4
  import { SobolNumberMapGenerator } from '../utils/SobolNumberMapGenerator.js';
5
5
 
6
- const _scissor = new Vector4();
7
- const _viewport = new Vector4();
8
-
9
6
  function* renderTask() {
10
7
 
11
8
  const {
@@ -47,12 +44,21 @@ function* renderTask() {
47
44
  const h = _primaryTarget.height;
48
45
  material.resolution.set( w * subW, h * subH );
49
46
  material.sobolTexture = _sobolTarget.texture;
47
+ material.stratifiedTexture.init( 20, material.bounces + material.transmissiveBounces + 5 );
48
+ material.stratifiedTexture.next();
50
49
  material.seed ++;
51
50
 
52
51
  const tilesX = this.tiles.x || 1;
53
52
  const tilesY = this.tiles.y || 1;
54
53
  const totalTiles = tilesX * tilesY;
55
- const dprInv = ( 1 / _renderer.getPixelRatio() );
54
+
55
+ const pxSubW = Math.ceil( w * subW );
56
+ const pxSubH = Math.ceil( h * subH );
57
+ const pxSubX = Math.floor( subX * w );
58
+ const pxSubY = Math.floor( subY * h );
59
+
60
+ const pxTileW = Math.ceil( pxSubW / tilesX );
61
+ const pxTileH = Math.ceil( pxSubH / tilesY );
56
62
 
57
63
  for ( let y = 0; y < tilesY; y ++ ) {
58
64
 
@@ -101,40 +107,28 @@ function* renderTask() {
101
107
 
102
108
  }
103
109
 
110
+ // set the scissor and the viewport on the render target
111
+ // note that when using the webgl renderer set viewport the device pixel ratio
112
+ // is multiplied into the field causing some pixels to not be rendered
113
+ const reverseTy = tilesY - ty - 1;
114
+ _primaryTarget.scissor.set(
115
+ pxSubX + tx * pxTileW,
116
+ pxSubY + reverseTy * pxTileH,
117
+ Math.min( pxTileW, pxSubW - tx * pxTileW ),
118
+ Math.min( pxTileH, pxSubH - reverseTy * pxTileH ),
119
+ );
120
+
121
+ _primaryTarget.viewport.set(
122
+ pxSubX,
123
+ pxSubY,
124
+ pxSubW,
125
+ pxSubH,
126
+ );
127
+
104
128
  // three.js renderer takes values relative to the current pixel ratio
105
129
  _renderer.setRenderTarget( _primaryTarget );
106
130
  _renderer.setScissorTest( true );
107
131
 
108
- // set the scissor window for a subtile
109
- _scissor.x = tx * w / tilesX;
110
- _scissor.y = ( tilesY - ty - 1 ) * h / tilesY;
111
- _scissor.z = w / tilesX;
112
- _scissor.w = h / tilesY;
113
-
114
- // adjust for the subframe
115
- _scissor.x = subX * w + subW * _scissor.x;
116
- _scissor.y = subY * h + subH * _scissor.y;
117
- _scissor.z = subW * _scissor.z;
118
- _scissor.w = subH * _scissor.w;
119
-
120
- // round for floating point cases
121
- _scissor.x = _scissor.x;
122
- _scissor.y = _scissor.y;
123
- _scissor.z = _scissor.z;
124
- _scissor.w = _scissor.w;
125
-
126
- // multiply inverse of DPR in because threes multiplies it in
127
- _scissor.multiplyScalar( dprInv ).ceil();
128
-
129
- _viewport.x = subX * w;
130
- _viewport.y = subY * h;
131
- _viewport.z = subW * w;
132
- _viewport.w = subH * h;
133
- _viewport.multiplyScalar( dprInv ).ceil();
134
-
135
- _renderer.setScissor( _scissor );
136
- _renderer.setViewport( _viewport );
137
-
138
132
  _renderer.autoClear = false;
139
133
  _fsQuad.render( _renderer );
140
134
 
@@ -1,73 +1,24 @@
1
- import { Mesh } from 'three';
2
- import { SAH, MeshBVH, StaticGeometryGenerator } from 'three-mesh-bvh';
3
- import { mergeMeshes } from '../utils/GeometryPreparationUtils.js';
1
+ import { DynamicPathTracingSceneGenerator } from './DynamicPathTracingSceneGenerator.js';
4
2
 
5
3
  export class PathTracingSceneGenerator {
6
4
 
7
- prepScene( scene ) {
8
-
9
- scene = Array.isArray( scene ) ? scene : [ scene ];
10
-
11
- const meshes = [];
12
- const lights = [];
13
-
14
- for ( let i = 0, l = scene.length; i < l; i ++ ) {
15
-
16
- scene[ i ].traverseVisible( c => {
17
-
18
- if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
19
-
20
- const generator = new StaticGeometryGenerator( c );
21
- generator.attributes = [ 'position', 'color', 'normal', 'tangent', 'uv', 'uv2' ];
22
- generator.applyWorldTransforms = false;
23
- const mesh = new Mesh(
24
- generator.generate(),
25
- c.material,
26
- );
27
- mesh.matrixWorld.copy( c.matrixWorld );
28
- mesh.matrix.copy( c.matrixWorld );
29
- mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
30
- meshes.push( mesh );
31
-
32
- } else if ( c.isMesh ) {
33
-
34
- meshes.push( c );
5
+ generate( scene, options = {} ) {
35
6
 
36
- } else if (
37
- c.isRectAreaLight ||
38
- c.isSpotLight ||
39
- c.isPointLight ||
40
- c.isDirectionalLight
41
- ) {
7
+ // ensure scene transforms are up to date
8
+ // TODO: remove this?
9
+ if ( Array.isArray( scene ) ) {
42
10
 
43
- lights.push( c );
11
+ scene.forEach( s => s.updateMatrixWorld( true ) );
44
12
 
45
- }
13
+ } else {
46
14
 
47
- } );
15
+ scene.updateMatrixWorld( true );
48
16
 
49
17
  }
50
18
 
51
- return {
52
- ...mergeMeshes( meshes, {
53
- attributes: [ 'position', 'normal', 'tangent', 'uv', 'color' ],
54
- } ),
55
- lights,
56
- };
57
-
58
- }
59
-
60
- generate( scene, options = {} ) {
61
-
62
- const { materials, textures, geometry, lights } = this.prepScene( scene );
63
- const bvhOptions = { strategy: SAH, ...options, maxLeafTris: 1 };
64
- return {
65
- scene,
66
- materials,
67
- textures,
68
- lights,
69
- bvh: new MeshBVH( geometry, bvhOptions ),
70
- };
19
+ const generator = new DynamicPathTracingSceneGenerator( scene );
20
+ generator.bvhOptions = options;
21
+ return generator.generate();
71
22
 
72
23
  }
73
24
 
@@ -194,7 +194,7 @@ export class LambertPathTracingMaterial extends MaterialBase {
194
194
  uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
195
195
  Material material = materials[ materialIndex ];
196
196
 
197
- if ( material.opacity < rand() ) {
197
+ if ( material.opacity < pcgRand() ) {
198
198
 
199
199
  vec3 point = rayOrigin + rayDirection * dist;
200
200
  rayOrigin += rayDirection * dist - faceNormal * RAY_OFFSET;
@@ -48,6 +48,9 @@ import { attenuateHitGLSL } from './glsl/attenuateHit.glsl.js';
48
48
  import { traceSceneGLSL } from './glsl/traceScene.glsl.js';
49
49
  import { getSurfaceRecordGLSL } from './glsl/getSurfaceRecord.glsl.js';
50
50
  import { directLightContributionGLSL } from './glsl/directLightContribution.glsl.js';
51
+ import { stratifiedTextureGLSL } from '../../shader/rand/stratifiedTexture.glsl.js';
52
+ import { StratifiedSamplesTexture } from '../../uniforms/StratifiedSamplesTexture.js';
53
+ import { BlueNoiseTexture } from '../../textures/BlueNoiseTexture.js';
51
54
 
52
55
  export class PhysicalPathTracingMaterial extends MaterialBase {
53
56
 
@@ -72,7 +75,12 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
72
75
  FEATURE_DOF: 1,
73
76
  FEATURE_BACKGROUND_MAP: 0,
74
77
  FEATURE_FOG: 1,
75
- FEATURE_SOBOL: 0,
78
+
79
+ // 0 = PCG
80
+ // 1 = Sobol
81
+ // 2 = Stratified List
82
+ RANDOM_TYPE: 2,
83
+
76
84
  // 0 = Perspective
77
85
  // 1 = Orthographic
78
86
  // 2 = Equirectangular
@@ -114,6 +122,8 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
114
122
 
115
123
  backgroundAlpha: { value: 1.0 },
116
124
  sobolTexture: { value: null },
125
+ stratifiedTexture: { value: new StratifiedSamplesTexture() },
126
+ stratifiedOffsetTexture: { value: new BlueNoiseTexture( 64, 1 ) },
117
127
  },
118
128
 
119
129
  vertexShader: /* glsl */`
@@ -153,23 +163,35 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
153
163
  ${ materialStructGLSL }
154
164
 
155
165
  // random
156
- ${ pcgGLSL }
157
- #if FEATURE_SOBOL
166
+ #if RANDOM_TYPE == 2 // Stratified List
167
+
168
+ ${ stratifiedTextureGLSL }
169
+
170
+ #elif RANDOM_TYPE == 1 // Sobol
158
171
 
172
+ ${ pcgGLSL }
159
173
  ${ sobolCommonGLSL }
160
174
  ${ sobolSamplingGLSL }
161
175
 
162
- #else
176
+ #define rand(v) sobol(v)
177
+ #define rand2(v) sobol2(v)
178
+ #define rand3(v) sobol3(v)
179
+ #define rand4(v) sobol4(v)
180
+
181
+ #else // PCG
182
+
183
+ ${ pcgGLSL }
163
184
 
164
185
  // Using the sobol functions seems to break the the compiler on MacOS
165
186
  // - specifically the "sobolReverseBits" function.
166
187
  uint sobolPixelIndex = 0u;
167
188
  uint sobolPathIndex = 0u;
168
189
  uint sobolBounceIndex = 0u;
169
- float sobol( int v ) { return rand(); }
170
- vec2 sobol2( int v ) { return rand2(); }
171
- vec3 sobol3( int v ) { return rand3(); }
172
- vec4 sobol4( int v ) { return rand4(); }
190
+
191
+ #define rand(v) pcgRand()
192
+ #define rand2(v) pcgRand2()
193
+ #define rand3(v) pcgRand3()
194
+ #define rand4(v) pcgRand4()
173
195
 
174
196
  #endif
175
197
 
@@ -287,7 +309,10 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
287
309
  // inverse environment rotation
288
310
  envRotation3x3 = mat3( environmentRotation );
289
311
  invEnvRotation3x3 = inverse( envRotation3x3 );
290
- lightsDenom = environmentIntensity == 0.0 && lights.count != 0u ? float( lights.count ) : float( lights.count + 1u );
312
+ lightsDenom =
313
+ ( environmentIntensity == 0.0 || envMapInfo.totalSum == 0.0 ) && lights.count != 0u ?
314
+ float( lights.count ) :
315
+ float( lights.count + 1u );
291
316
 
292
317
  // final color
293
318
  gl_FragColor = vec4( 0, 0, 0, 1 );
@@ -356,7 +381,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
356
381
 
357
382
  if ( state.firstRay || state.transmissiveRay ) {
358
383
 
359
- gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, sobol2( 2 ) ) * state.throughputColor;
384
+ gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, rand2( 2 ) ) * state.throughputColor;
360
385
  gl_FragColor.a = backgroundAlpha;
361
386
 
362
387
  } else {
@@ -447,7 +472,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
447
472
  }
448
473
 
449
474
  scatterRec = bsdfSample( - ray.direction, surf );
450
- state.isShadowRay = scatterRec.specularPdf < sobol( 4 );
475
+ state.isShadowRay = scatterRec.specularPdf < rand( 4 );
451
476
 
452
477
  bool isBelowSurface = ! surf.volumeParticle && dot( scatterRec.direction, surf.faceNormal ) < 0.0;
453
478
  vec3 hitPoint = stepRayOrigin( ray.origin, ray.direction, isBelowSurface ? - surf.faceNormal : surf.faceNormal, surfaceHit.dist );
@@ -517,7 +542,7 @@ export class PhysicalPathTracingMaterial extends MaterialBase {
517
542
  rrProb = sqrt( rrProb );
518
543
  rrProb = max( rrProb, depthProb );
519
544
  rrProb = min( rrProb, 1.0 );
520
- if ( sobol( 8 ) > rrProb ) {
545
+ if ( rand( 8 ) > rrProb ) {
521
546
 
522
547
  break;
523
548
 
@@ -8,6 +8,9 @@ export const attenuateHitGLSL = /* glsl */`
8
8
  out vec3 color
9
9
  ) {
10
10
 
11
+ // store the original bounce index so we can reset it after
12
+ uint originalBounceIndex = sobolBounceIndex;
13
+
11
14
  int traversals = state.traversals;
12
15
  int transmissiveTraversals = state.transmissiveTraversals;
13
16
  bool isShadowRay = state.isShadowRay;
@@ -20,22 +23,25 @@ export const attenuateHitGLSL = /* glsl */`
20
23
 
21
24
  color = vec3( 1.0 );
22
25
 
23
- // TODO: we should be using sobol sampling here instead of rand but the sobol bounce and path indices need to be incremented
24
- // and then reset.
26
+ bool result = true;
25
27
  for ( int i = 0; i < traversals; i ++ ) {
26
28
 
29
+ sobolBounceIndex ++;
30
+
27
31
  int hitType = traceScene( ray, fogMaterial, surfaceHit );
28
32
 
29
33
  if ( hitType == FOG_HIT ) {
30
34
 
31
- return true;
35
+ result = true;
36
+ break;
32
37
 
33
38
  } else if ( hitType == SURFACE_HIT ) {
34
39
 
35
40
  float totalDist = distance( startPoint, ray.origin + ray.direction * surfaceHit.dist );
36
41
  if ( totalDist > rayDist ) {
37
42
 
38
- return false;
43
+ result = false;
44
+ break;
39
45
 
40
46
  }
41
47
 
@@ -117,7 +123,7 @@ export const attenuateHitGLSL = /* glsl */`
117
123
  bool useAlphaTest = alphaTest != 0.0;
118
124
  float transmissionFactor = ( 1.0 - metalness ) * transmission;
119
125
  if (
120
- transmissionFactor < rand() && ! (
126
+ transmissionFactor < rand( 9 ) && ! (
121
127
  // material sidedness
122
128
  material.side != 0.0 && surfaceHit.side == material.side
123
129
 
@@ -125,11 +131,12 @@ export const attenuateHitGLSL = /* glsl */`
125
131
  || useAlphaTest && albedo.a < alphaTest
126
132
 
127
133
  // opacity
128
- || material.transparent && ! useAlphaTest && albedo.a < rand()
134
+ || material.transparent && ! useAlphaTest && albedo.a < rand( 10 )
129
135
  )
130
136
  ) {
131
137
 
132
- return true;
138
+ result = true;
139
+ break;
133
140
 
134
141
  }
135
142
 
@@ -155,13 +162,16 @@ export const attenuateHitGLSL = /* glsl */`
155
162
 
156
163
  } else {
157
164
 
158
- return false;
165
+ result = false;
166
+ break;
159
167
 
160
168
  }
161
169
 
162
170
  }
163
171
 
164
- return true;
172
+ // reset the bounce index
173
+ sobolBounceIndex = originalBounceIndex;
174
+ return result;
165
175
 
166
176
  }
167
177
 
@@ -12,7 +12,7 @@ export const cameraUtilsGLSL = /* glsl */`
12
12
 
13
13
  // Jitter the camera ray by finding a uv coordinate at a random sample
14
14
  // around this pixel's UV coordinate for AA
15
- vec2 ruv = sobol2( 0 );
15
+ vec2 ruv = rand2( 0 );
16
16
  vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
17
17
  Ray ray;
18
18
 
@@ -57,7 +57,7 @@ export const cameraUtilsGLSL = /* glsl */`
57
57
 
58
58
  // get the aperture sample
59
59
  // if blades === 0 then we assume a circle
60
- vec3 shapeUVW= sobol3( 1 );
60
+ vec3 shapeUVW= rand3( 1 );
61
61
  int blades = physicalCamera.apertureBlades;
62
62
  float anamorphicRatio = physicalCamera.anamorphicRatio;
63
63
  vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularPolygon( blades, shapeUVW );
@@ -5,10 +5,10 @@ export const directLightContributionGLSL = /*glsl*/`
5
5
  vec3 result = vec3( 0.0 );
6
6
 
7
7
  // uniformly pick a light or environment map
8
- if( lightsDenom != 0.0 && sobol( 5 ) < float( lights.count ) / lightsDenom ) {
8
+ if( lightsDenom != 0.0 && rand( 5 ) < float( lights.count ) / lightsDenom ) {
9
9
 
10
10
  // sample a light or environment
11
- LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, sobol3( 6 ) );
11
+ LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, rand3( 6 ) );
12
12
 
13
13
  bool isSampleBelowSurface = ! surf.volumeParticle && dot( surf.faceNormal, lightRec.direction ) < 0.0;
14
14
  if ( isSampleBelowSurface ) {
@@ -43,11 +43,11 @@ export const directLightContributionGLSL = /*glsl*/`
43
43
 
44
44
  }
45
45
 
46
- } else {
46
+ } else if ( envMapInfo.totalSum != 0.0 && environmentIntensity != 0.0 ) {
47
47
 
48
48
  // find a sample in the environment map to include in the contribution
49
49
  vec3 envColor, envDirection;
50
- float envPdf = sampleEquirectProbability( sobol2( 7 ), envColor, envDirection );
50
+ float envPdf = sampleEquirectProbability( rand2( 7 ), envColor, envDirection );
51
51
  envDirection = invEnvRotation3x3 * envDirection;
52
52
 
53
53
  // this env sampling is not set up for transmissive sampling and yields overly bright
@@ -36,6 +36,7 @@ export const getSurfaceRecordGLSL = /* glsl */`
36
36
 
37
37
  vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
38
38
  albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
39
+
39
40
  }
40
41
 
41
42
  if ( material.vertexColors ) {
@@ -67,7 +68,7 @@ export const getSurfaceRecordGLSL = /* glsl */`
67
68
  || useAlphaTest && albedo.a < alphaTest
68
69
 
69
70
  // opacity
70
- || material.transparent && ! useAlphaTest && albedo.a < sobol( 3 )
71
+ || material.transparent && ! useAlphaTest && albedo.a < rand( 3 )
71
72
  ) {
72
73
 
73
74
  return SKIP_SURFACE;
@@ -23,7 +23,7 @@ export const traceSceneGLSL = /* glsl */`
23
23
 
24
24
  // offset the distance so we don't run into issues with particles on the same surface
25
25
  // as other objects
26
- float particleDist = intersectFogVolume( fogMaterial, sobol( 1 ) );
26
+ float particleDist = intersectFogVolume( fogMaterial, rand( 1 ) );
27
27
  if ( particleDist + RAY_OFFSET < surfaceHit.dist ) {
28
28
 
29
29
  surfaceHit.side = 1.0;