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/build/index.module.js +411 -44
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +410 -43
- package/build/index.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/core/PathTracingRenderer.js +48 -1
- package/src/core/WebGLPathTracer.js +62 -8
- package/src/core/utils/BakedGeometry.js +23 -0
- package/src/core/utils/BufferAttributeUtils.js +11 -3
- package/src/core/utils/MeshDiff.js +6 -13
- package/src/core/utils/StaticGeometryGenerator.js +19 -4
- package/src/materials/MaterialBase.js +11 -0
- package/src/uniforms/StratifiedSamplesTexture.js +55 -3
- package/src/uniforms/stratified/StratifiedSampler.js +19 -7
- package/src/uniforms/stratified/StratifiedSamplerCombined.js +14 -4
- package/src/utils/CubeToEquirectGenerator.js +159 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
for ( const attr of attributes ) {
|
|
15
|
+
const keys = Object.keys( attributes ).sort();
|
|
16
|
+
for ( const key of keys ) {
|
|
25
17
|
|
|
26
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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 +
|
|
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.
|
|
47
|
+
this.reshuffle = function () {
|
|
48
48
|
|
|
49
49
|
for ( const strata of strataObjs ) {
|
|
50
50
|
|
|
51
|
-
strata.
|
|
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
|
|