three-gpu-pathtracer 0.0.21 → 0.0.23

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-gpu-pathtracer",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
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",
@@ -159,6 +159,9 @@ export class PathTracingRenderer {
159
159
 
160
160
  set material( v ) {
161
161
 
162
+ this._fsQuad.material.removeEventListener( 'recompilation', this._compileFunction );
163
+ v.addEventListener( 'recompilation', this._compileFunction );
164
+
162
165
  this._fsQuad.material = v;
163
166
 
164
167
  }
@@ -195,6 +198,12 @@ export class PathTracingRenderer {
195
198
 
196
199
  }
197
200
 
201
+ get isCompiling() {
202
+
203
+ return Boolean( this._compilePromise );
204
+
205
+ }
206
+
198
207
  constructor( renderer ) {
199
208
 
200
209
  this.camera = null;
@@ -212,6 +221,7 @@ export class PathTracingRenderer {
212
221
  this._blendQuad = new FullScreenQuad( new BlendMaterial() );
213
222
  this._task = null;
214
223
  this._currentTile = 0;
224
+ this._compilePromise = null;
215
225
 
216
226
  this._sobolTarget = new SobolNumberMapGenerator().generate( renderer );
217
227
 
@@ -236,6 +246,33 @@ export class PathTracingRenderer {
236
246
  } ),
237
247
  ];
238
248
 
249
+ // function for listening to for triggered compilation so we can wait for compilation to finish
250
+ // before starting to render
251
+ this._compileFunction = () => {
252
+
253
+ const promise = this.compileMaterial( this._fsQuad._mesh );
254
+ promise.then( () => {
255
+
256
+ if ( this._compilePromise === promise ) {
257
+
258
+ this._compilePromise = null;
259
+
260
+ }
261
+
262
+ } );
263
+
264
+ this._compilePromise = promise;
265
+
266
+ };
267
+
268
+ this.material.addEventListener( 'recompilation', this._compileFunction );
269
+
270
+ }
271
+
272
+ compileMaterial() {
273
+
274
+ return this._renderer.compileAsync( this._fsQuad._mesh );
275
+
239
276
  }
240
277
 
241
278
  setCamera( camera ) {
@@ -267,7 +304,6 @@ export class PathTracingRenderer {
267
304
  material.setDefine( 'CAMERA_TYPE', cameraType );
268
305
 
269
306
  this.camera = camera;
270
- // this.reset();
271
307
 
272
308
  }
273
309
 
@@ -334,9 +370,11 @@ export class PathTracingRenderer {
334
370
  this.samples = 0;
335
371
  this._task = null;
336
372
 
373
+ this.material.stratifiedTexture.stableNoise = this.stableNoise;
337
374
  if ( this.stableNoise ) {
338
375
 
339
376
  this.material.seed = 0;
377
+ this.material.stratifiedTexture.reset();
340
378
 
341
379
  }
342
380
 
@@ -344,6 +382,15 @@ export class PathTracingRenderer {
344
382
 
345
383
  update() {
346
384
 
385
+ // ensure we've updated our defines before rendering so we can ensure we
386
+ // can wait for compilation to finish
387
+ this.material.onBeforeRender();
388
+ if ( this.isCompiling ) {
389
+
390
+ return;
391
+
392
+ }
393
+
347
394
  if ( ! this._task ) {
348
395
 
349
396
  this._task = renderTask.call( this );
@@ -5,6 +5,7 @@ import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
5
5
  import { GradientEquirectTexture } from '../textures/GradientEquirectTexture.js';
6
6
  import { getIesTextures, getLights, getTextures } from './utils/sceneUpdateUtils.js';
7
7
  import { ClampedInterpolationMaterial } from '../materials/fullscreen/ClampedInterpolationMaterial.js';
8
+ import { CubeToEquirectGenerator } from '../utils/CubeToEquirectGenerator.js';
8
9
 
9
10
  function supportsFloatBlending( renderer ) {
10
11
 
@@ -81,6 +82,24 @@ export class WebGLPathTracer {
81
82
 
82
83
  }
83
84
 
85
+ get stableNoise() {
86
+
87
+ return this._pathTracer.stableNoise;
88
+
89
+ }
90
+
91
+ set stableNoise( v ) {
92
+
93
+ this._pathTracer.stableNoise = v;
94
+
95
+ }
96
+
97
+ get isCompiling() {
98
+
99
+ return Boolean( this._pathTracer.isCompiling );
100
+
101
+ }
102
+
84
103
  constructor( renderer ) {
85
104
 
86
105
  // members
@@ -89,6 +108,7 @@ export class WebGLPathTracer {
89
108
  this._pathTracer = new PathTracingRenderer( renderer );
90
109
  this._queueReset = false;
91
110
  this._clock = new Clock();
111
+ this._compilePromise = null;
92
112
 
93
113
  this._lowResPathTracer = new PathTracingRenderer( renderer );
94
114
  this._lowResPathTracer.tiles.set( 1, 1 );
@@ -101,6 +121,10 @@ export class WebGLPathTracer {
101
121
  } ) );
102
122
  this._materials = null;
103
123
 
124
+ this._previousEnvironment = null;
125
+ this._previousBackground = null;
126
+ this._internalBackground = null;
127
+
104
128
  // options
105
129
  this.renderDelay = 100;
106
130
  this.minSamples = 5;
@@ -229,6 +253,13 @@ export class WebGLPathTracer {
229
253
  const scene = this.scene;
230
254
  const material = this._pathTracer.material;
231
255
 
256
+ if ( this._internalBackground ) {
257
+
258
+ this._internalBackground.dispose();
259
+ this._internalBackground = null;
260
+
261
+ }
262
+
232
263
  // update scene background
233
264
  material.backgroundBlur = scene.backgroundBlurriness;
234
265
  material.backgroundIntensity = scene.backgroundIntensity ?? 1;
@@ -256,6 +287,17 @@ export class WebGLPathTracer {
256
287
  material.backgroundMap = colorBackground;
257
288
  material.backgroundAlpha = 1;
258
289
 
290
+ } else if ( scene.background.isCubeTexture ) {
291
+
292
+ if ( scene.background !== this._previousBackground ) {
293
+
294
+ const background = new CubeToEquirectGenerator( this._renderer ).generate( scene.background );
295
+ this._internalBackground = background;
296
+ material.backgroundMap = background;
297
+ material.backgroundAlpha = 1;
298
+
299
+ }
300
+
259
301
  } else {
260
302
 
261
303
  material.backgroundMap = scene.background;
@@ -268,12 +310,21 @@ export class WebGLPathTracer {
268
310
  material.environmentRotation.makeRotationFromEuler( scene.environmentRotation ).invert();
269
311
  if ( this._previousEnvironment !== scene.environment ) {
270
312
 
271
- if ( scene.environment ) {
313
+ if ( scene.environment !== null ) {
314
+
315
+ if ( scene.environment.isCubeTexture ) {
272
316
 
273
- // TODO: Consider setting this to the highest supported bit depth by checking for
274
- // OES_texture_float_linear or OES_texture_half_float_linear. Requires changes to
275
- // the equirect uniform
276
- material.envMapInfo.updateFrom( scene.environment );
317
+ const environment = new CubeToEquirectGenerator( this._renderer ).generate( scene.environment );
318
+ material.envMapInfo.updateFrom( environment );
319
+
320
+ } else {
321
+
322
+ // TODO: Consider setting this to the highest supported bit depth by checking for
323
+ // OES_texture_float_linear or OES_texture_half_float_linear. Requires changes to
324
+ // the equirect uniform
325
+ material.envMapInfo.updateFrom( scene.environment );
326
+
327
+ }
277
328
 
278
329
  } else {
279
330
 
@@ -284,6 +335,7 @@ export class WebGLPathTracer {
284
335
  }
285
336
 
286
337
  this._previousEnvironment = scene.environment;
338
+ this._previousBackground = scene.background;
287
339
  this.reset();
288
340
 
289
341
  }
@@ -354,7 +406,7 @@ export class WebGLPathTracer {
354
406
  // render the path tracing sample after enough time has passed
355
407
  const delta = clock.getDelta() * 1e3;
356
408
  const elapsedTime = clock.getElapsedTime() * 1e3;
357
- if ( ! this.pausePathTracing && this.enablePathTracing && this.renderDelay <= elapsedTime ) {
409
+ if ( ! this.pausePathTracing && this.enablePathTracing && this.renderDelay <= elapsedTime && ! this.isCompiling ) {
358
410
 
359
411
  pathTracer.update();
360
412
 
@@ -387,7 +439,7 @@ export class WebGLPathTracer {
387
439
  // render the fallback if we haven't rendered enough samples, are paused, or are occluded
388
440
  if ( ! this.enablePathTracing || this.samples < minSamples || quad.material.opacity < 1 ) {
389
441
 
390
- if ( this.dynamicLowRes ) {
442
+ if ( this.dynamicLowRes && ! this.isCompiling ) {
391
443
 
392
444
  if ( lowResPathTracer.samples < 1 ) {
393
445
 
@@ -402,7 +454,9 @@ export class WebGLPathTracer {
402
454
  quad.render( renderer );
403
455
  quad.material.opacity = currentOpacity;
404
456
 
405
- } else if ( this.rasterizeScene ) {
457
+ }
458
+
459
+ if ( ! this.dynamicLowRes && this.rasterizeScene || this.dynamicLowRes && this.isCompiling ) {
406
460
 
407
461
  this.rasterizeSceneCallback( this.scene, this.camera );
408
462
 
@@ -1,6 +1,7 @@
1
1
  import { BufferGeometry } from 'three';
2
2
  import { MeshDiff } from './MeshDiff.js';
3
3
  import { convertToStaticGeometry } from './convertToStaticGeometry.js';
4
+ import { validateAttributes } from './BufferAttributeUtils.js';
4
5
 
5
6
  export class BakedGeometry extends BufferGeometry {
6
7
 
@@ -13,6 +14,28 @@ export class BakedGeometry extends BufferGeometry {
13
14
 
14
15
  }
15
16
 
17
+ // returns whether the passed mesh is compatible with this baked geometry
18
+ // such that it can be updated without resizing attributes
19
+ isCompatible( mesh, attributes ) {
20
+
21
+ const geometry = mesh.geometry;
22
+ for ( let i = 0; i < attributes.length; i ++ ) {
23
+
24
+ const key = attributes[ i ];
25
+ const attr1 = geometry.attributes[ key ];
26
+ const attr2 = this.attributes[ key ];
27
+ if ( attr1 && ! validateAttributes( attr1, attr2 ) ) {
28
+
29
+ return false;
30
+
31
+ }
32
+
33
+ }
34
+
35
+ return true;
36
+
37
+ }
38
+
16
39
  updateFrom( mesh, options ) {
17
40
 
18
41
  const diff = this._diff;
@@ -41,12 +41,18 @@ export function createAttributeClone( attr, countOverride = null ) {
41
41
 
42
42
  }
43
43
 
44
- // Confirms that the two provided attributes are compatible
44
+ // Confirms that the two provided attributes are compatible. Returns false if they are not.
45
45
  export function validateAttributes( attr1, attr2 ) {
46
46
 
47
47
  if ( ! attr1 && ! attr2 ) {
48
48
 
49
- return;
49
+ return true;
50
+
51
+ }
52
+
53
+ if ( Boolean( attr1 ) !== Boolean( attr2 ) ) {
54
+
55
+ return false;
50
56
 
51
57
  }
52
58
 
@@ -57,8 +63,10 @@ export function validateAttributes( attr1, attr2 ) {
57
63
 
58
64
  if ( ! sameCount || ! sameNormalized || ! sameType || ! sameItemSize ) {
59
65
 
60
- throw new Error();
66
+ return false;
61
67
 
62
68
  }
63
69
 
70
+ return true;
71
+
64
72
  }
@@ -1,29 +1,22 @@
1
1
  import { Matrix4 } from 'three';
2
2
  import { bufferToHash } from '../../utils/bufferToHash.js';
3
3
 
4
- function attributeSort( a, b ) {
5
-
6
- if ( a.uuid > b.uuid ) return 1;
7
- if ( a.uuid < b.uuid ) return - 1;
8
- return 0;
9
-
10
- }
11
-
12
4
  function getGeometryHash( geometry ) {
13
5
 
14
- let hash = '';
6
+ let hash = geometry.uuid;
15
7
  const attributes = Object.values( geometry.attributes );
16
8
  if ( geometry.index ) {
17
9
 
18
10
  attributes.push( geometry.index );
11
+ hash += `index|${ geometry.index.version }`;
19
12
 
20
13
  }
21
14
 
22
- attributes.sort( attributeSort );
23
-
24
- for ( const attr of attributes ) {
15
+ const keys = Object.keys( attributes ).sort();
16
+ for ( const key of keys ) {
25
17
 
26
- hash += `${ attr.uuid }_${ attr.version }|`;
18
+ const attr = attributes[ key ];
19
+ hash += `${ key }_${ attr.version }|`;
27
20
 
28
21
  }
29
22
 
@@ -13,7 +13,7 @@ function flatTraverseMeshes( objects, cb ) {
13
13
  for ( let i = 0, l = objects.length; i < l; i ++ ) {
14
14
 
15
15
  const object = objects[ i ];
16
- object.traverse( o => {
16
+ object.traverseVisible( o => {
17
17
 
18
18
  if ( o.isMesh ) {
19
19
 
@@ -171,15 +171,24 @@ export class StaticGeometryGenerator {
171
171
  unusedMeshKeys.delete( meshKey );
172
172
 
173
173
  // initialize the intermediate geometry
174
- if ( ! _intermediateGeometry.has( meshKey ) ) {
174
+ // if the mesh and source geometry have changed in such a way that they are no longer
175
+ // compatible then regenerate the baked geometry from scratch
176
+ let geom = _intermediateGeometry.get( meshKey );
177
+ if ( ! geom || ! geom.isCompatible( mesh, this.attributes ) ) {
175
178
 
176
- _intermediateGeometry.set( meshKey, new BakedGeometry() );
179
+ if ( geom ) {
180
+
181
+ geom.dispose();
182
+
183
+ }
184
+
185
+ geom = new BakedGeometry();
186
+ _intermediateGeometry.set( meshKey, geom );
177
187
 
178
188
  }
179
189
 
180
190
  // transform the geometry into the intermediate buffer geometry, saving whether
181
191
  // or not it changed.
182
- const geom = _intermediateGeometry.get( meshKey );
183
192
  if ( geom.updateFrom( mesh, convertOptions ) ) {
184
193
 
185
194
  // TODO: provide option for only generating the set of attributes that are present
@@ -231,6 +240,12 @@ export class StaticGeometryGenerator {
231
240
 
232
241
  // get the list of geometries to merge
233
242
  let forceUpdate = false;
243
+ if ( meshes.length !== previousMergeInfo.length ) {
244
+
245
+ forceUpdate = true;
246
+
247
+ }
248
+
234
249
  for ( let i = 0, l = meshes.length; i < l; i ++ ) {
235
250
 
236
251
  const mesh = meshes[ i ];
@@ -2,6 +2,17 @@ import { ShaderMaterial } from 'three';
2
2
 
3
3
  export class MaterialBase extends ShaderMaterial {
4
4
 
5
+ set needsUpdate( v ) {
6
+
7
+ super.needsUpdate = true;
8
+ this.dispatchEvent( {
9
+
10
+ type: 'recompilation',
11
+
12
+ } );
13
+
14
+ }
15
+
5
16
  constructor( shader ) {
6
17
 
7
18
  super( shader );
@@ -1,6 +1,36 @@
1
1
  import { DataTexture, FloatType, NearestFilter, RGBAFormat } from 'three';
2
2
  import { StratifiedSamplerCombined } from './stratified/StratifiedSamplerCombined.js';
3
3
 
4
+ // https://stackoverflow.com/questions/424292/seedable-javascript-random-number-generator
5
+ class RandomGenerator {
6
+
7
+ constructor( seed = 0 ) {
8
+
9
+ // LCG using GCC's constants
10
+ this.m = 0x80000000; // 2**31;
11
+ this.a = 1103515245;
12
+ this.c = 12345;
13
+
14
+ this.seed = seed;
15
+
16
+ }
17
+
18
+ nextInt() {
19
+
20
+ this.seed = ( this.a * this.seed + this.c ) % this.m;
21
+ return this.seed;
22
+
23
+ }
24
+
25
+ nextFloat() {
26
+
27
+ // returns in range [0,1]
28
+ return this.nextInt() / ( this.m - 1 );
29
+
30
+ }
31
+
32
+ }
33
+
4
34
  export class StratifiedSamplesTexture extends DataTexture {
5
35
 
6
36
  constructor( count = 1, depth = 1, strata = 8 ) {
@@ -11,22 +41,37 @@ export class StratifiedSamplesTexture extends DataTexture {
11
41
 
12
42
  this.strata = strata;
13
43
  this.sampler = null;
44
+ this.generator = new RandomGenerator();
45
+ this.stableNoise = false;
46
+ this.random = () => {
47
+
48
+ if ( this.stableNoise ) {
49
+
50
+ return this.generator.nextFloat();
51
+
52
+ } else {
53
+
54
+ return Math.random();
55
+
56
+ }
57
+
58
+ };
14
59
 
15
60
  this.init( count, depth, strata );
16
61
 
17
62
  }
18
63
 
19
- init( count, depth, strata = this.strata ) {
64
+ init( count = this.image.height, depth = this.image.width, strata = this.strata ) {
20
65
 
21
66
  const { image } = this;
22
- if ( image.width === depth && image.height === count ) {
67
+ if ( image.width === depth && image.height === count && this.sampler !== null ) {
23
68
 
24
69
  return;
25
70
 
26
71
  }
27
72
 
28
73
  const dimensions = new Array( count * depth ).fill( 4 );
29
- const sampler = new StratifiedSamplerCombined( strata, dimensions );
74
+ const sampler = new StratifiedSamplerCombined( strata, dimensions, this.random );
30
75
 
31
76
  image.width = depth;
32
77
  image.height = count;
@@ -46,4 +91,11 @@ export class StratifiedSamplesTexture extends DataTexture {
46
91
 
47
92
  }
48
93
 
94
+ reset() {
95
+
96
+ this.sampler.reset();
97
+ this.generator.seed = 0;
98
+
99
+ }
100
+
49
101
  }
@@ -2,11 +2,11 @@
2
2
  // - https://github.com/hoverinc/ray-tracing-renderer
3
3
  // - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
4
4
 
5
- export function shuffle( arr ) {
5
+ export function shuffle( arr, random = Math.random() ) {
6
6
 
7
7
  for ( let i = arr.length - 1; i > 0; i -- ) {
8
8
 
9
- const j = Math.floor( Math.random() * ( i + 1 ) );
9
+ const j = Math.floor( random() * ( i + 1 ) );
10
10
  const x = arr[ i ];
11
11
  arr[ i ] = arr[ j ];
12
12
  arr[ j ] = x;
@@ -21,7 +21,7 @@ export function shuffle( arr ) {
21
21
  // dimensions : The number of dimensions to generate stratified values for
22
22
  export class StratifiedSampler {
23
23
 
24
- constructor( strataCount, dimensions ) {
24
+ constructor( strataCount, dimensions, random = Math.random ) {
25
25
 
26
26
  const l = strataCount ** dimensions;
27
27
  const strata = new Uint16Array( l );
@@ -38,7 +38,19 @@ export class StratifiedSampler {
38
38
 
39
39
  this.strataCount = strataCount;
40
40
 
41
- this.restart = function () {
41
+ this.reset = function () {
42
+
43
+ for ( let i = 0; i < l; i ++ ) {
44
+
45
+ strata[ i ] = i;
46
+
47
+ }
48
+
49
+ index = 0;
50
+
51
+ };
52
+
53
+ this.reshuffle = function () {
42
54
 
43
55
  index = 0;
44
56
 
@@ -50,8 +62,8 @@ export class StratifiedSampler {
50
62
 
51
63
  if ( index >= strata.length ) {
52
64
 
53
- shuffle( strata );
54
- this.restart();
65
+ shuffle( strata, random );
66
+ this.reshuffle();
55
67
 
56
68
  }
57
69
 
@@ -59,7 +71,7 @@ export class StratifiedSampler {
59
71
 
60
72
  for ( let i = 0; i < dimensions; i ++ ) {
61
73
 
62
- samples[ i ] = ( stratum % strataCount + Math.random() ) / strataCount;
74
+ samples[ i ] = ( stratum % strataCount + random() ) / strataCount;
63
75
  stratum = Math.floor( stratum / strataCount );
64
76
 
65
77
  }
@@ -7,7 +7,7 @@ import { StratifiedSampler } from './StratifiedSampler.js';
7
7
  // Stratified set of data with each tuple stratified separately and combined
8
8
  export class StratifiedSamplerCombined {
9
9
 
10
- constructor( strataCount, listOfDimensions ) {
10
+ constructor( strataCount, listOfDimensions, random = Math.random ) {
11
11
 
12
12
  let totalDim = 0;
13
13
  for ( const dim of listOfDimensions ) {
@@ -21,7 +21,7 @@ export class StratifiedSamplerCombined {
21
21
  let offset = 0;
22
22
  for ( const dim of listOfDimensions ) {
23
23
 
24
- const sampler = new StratifiedSampler( strataCount, dim );
24
+ const sampler = new StratifiedSampler( strataCount, dim, random );
25
25
  sampler.samples = new Float32Array( combined.buffer, offset, sampler.samples.length );
26
26
  offset += sampler.samples.length * 4;
27
27
  strataObjs.push( sampler );
@@ -44,11 +44,21 @@ export class StratifiedSamplerCombined {
44
44
 
45
45
  };
46
46
 
47
- this.restart = function () {
47
+ this.reshuffle = function () {
48
48
 
49
49
  for ( const strata of strataObjs ) {
50
50
 
51
- strata.restart();
51
+ strata.reshuffle();
52
+
53
+ }
54
+
55
+ };
56
+
57
+ this.reset = function () {
58
+
59
+ for ( const strata of strataObjs ) {
60
+
61
+ strata.reset();
52
62
 
53
63
  }
54
64