three-gpu-pathtracer 0.0.17 → 0.0.18

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 (34) hide show
  1. package/build/index.module.js +1000 -319
  2. package/build/index.module.js.map +1 -1
  3. package/build/index.umd.cjs +996 -316
  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 +33 -11
  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 +3 -3
  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/structs/lightsStruct.glsl.js +5 -7
  21. package/src/textures/BlueNoiseTexture.js +87 -0
  22. package/src/textures/ProceduralEquirectTexture.js +7 -8
  23. package/src/textures/blueNoise/BlueNoiseGenerator.js +115 -0
  24. package/src/textures/blueNoise/BlueNoiseSamples.js +214 -0
  25. package/src/textures/blueNoise/utils.js +24 -0
  26. package/src/uniforms/EquirectHdrInfoUniform.js +45 -8
  27. package/src/uniforms/LightsInfoUniformStruct.js +11 -7
  28. package/src/uniforms/RenderTarget2DArray.js +50 -3
  29. package/src/uniforms/StratifiedSamplesTexture.js +49 -0
  30. package/src/uniforms/stratified/StratifiedSampler.js +73 -0
  31. package/src/uniforms/stratified/StratifiedSamplerCombined.js +59 -0
  32. package/src/uniforms/utils.js +1 -1
  33. package/src/utils/GeometryPreparationUtils.js +8 -101
  34. package/src/workers/PathTracingSceneWorker.js +18 -8
@@ -0,0 +1,24 @@
1
+ export function shuffleArray( array, random = Math.random ) {
2
+
3
+ for ( let i = array.length - 1; i > 0; i -- ) {
4
+
5
+ const replaceIndex = ~ ~ ( ( random() - 1e-6 ) * i );
6
+ const tmp = array[ i ];
7
+ array[ i ] = array[ replaceIndex ];
8
+ array[ replaceIndex ] = tmp;
9
+
10
+ }
11
+
12
+ }
13
+
14
+ export function fillWithOnes( array, count ) {
15
+
16
+ array.fill( 0 );
17
+
18
+ for ( let i = 0; i < count; i ++ ) {
19
+
20
+ array[ i ] = 1;
21
+
22
+ }
23
+
24
+ }
@@ -1,4 +1,4 @@
1
- import { DataTexture, RedFormat, LinearFilter, DataUtils, HalfFloatType, Source, RepeatWrapping, RGBAFormat } from 'three';
1
+ import { DataTexture, RedFormat, LinearFilter, DataUtils, HalfFloatType, Source, RepeatWrapping, RGBAFormat, FloatType, ClampToEdgeWrapping } from 'three';
2
2
  import { toHalfFloatArray } from '../utils/TextureUtils.js';
3
3
 
4
4
  function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
@@ -39,7 +39,7 @@ function colorToLuminance( r, g, b ) {
39
39
  }
40
40
 
41
41
  // ensures the data is all floating point values and flipY is false
42
- function preprocessEnvMap( envMap ) {
42
+ function preprocessEnvMap( envMap, targetType = HalfFloatType ) {
43
43
 
44
44
  const map = envMap.clone();
45
45
  map.source = new Source( { ...map.image } );
@@ -48,17 +48,54 @@ function preprocessEnvMap( envMap ) {
48
48
  // TODO: is there a simple way to avoid cloning and adjusting the env map data here?
49
49
  // convert the data from half float uint 16 arrays to float arrays for cdf computation
50
50
  let newData = data;
51
- if ( map.type === HalfFloatType ) {
51
+ if ( map.type !== targetType ) {
52
52
 
53
- newData = new Uint16Array( data.length );
54
- for ( const i in data ) {
53
+ if ( targetType === HalfFloatType ) {
55
54
 
56
- newData[ i ] = data[ i ];
55
+ newData = new Uint16Array( data.length );
56
+
57
+ } else {
58
+
59
+ newData = new Float32Array( data.length );
60
+
61
+ }
62
+
63
+ let maxIntValue;
64
+ if ( data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array ) {
65
+
66
+ maxIntValue = 2 ** ( 8 * data.BYTES_PER_ELEMENT - 1 ) - 1;
67
+
68
+ } else {
69
+
70
+ maxIntValue = 2 ** ( 8 * data.BYTES_PER_ELEMENT ) - 1;
71
+
72
+ }
73
+
74
+ for ( let i = 0, l = data.length; i < l; i ++ ) {
75
+
76
+ let v = data[ i ];
77
+ if ( map.type === HalfFloatType ) {
78
+
79
+ v = DataUtils.fromHalfFloat( data[ i ] );
80
+
81
+ }
82
+
83
+ if ( map.type !== FloatType && map.type !== HalfFloatType ) {
84
+
85
+ v /= maxIntValue;
86
+
87
+ }
88
+
89
+ if ( targetType === HalfFloatType ) {
90
+
91
+ newData[ i ] = DataUtils.toHalfFloat( v );
92
+
93
+ }
57
94
 
58
95
  }
59
96
 
60
97
  map.image.data = newData;
61
- map.type = HalfFloatType;
98
+ map.type = targetType;
62
99
 
63
100
  }
64
101
 
@@ -150,7 +187,7 @@ export class EquirectHdrInfoUniform {
150
187
  // https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
151
188
  const map = preprocessEnvMap( hdr );
152
189
  map.wrapS = RepeatWrapping;
153
- map.wrapT = RepeatWrapping;
190
+ map.wrapT = ClampToEdgeWrapping;
154
191
 
155
192
  const { width, height, data } = map.image;
156
193
 
@@ -57,6 +57,13 @@ export class LightsInfoUniformStruct {
57
57
  const baseIndex = i * LIGHT_PIXELS * 4;
58
58
  let index = 0;
59
59
 
60
+ // initialize to 0
61
+ for ( let p = 0; p < LIGHT_PIXELS; p ++ ) {
62
+
63
+ floatArray[ baseIndex + p ] = 0;
64
+
65
+ }
66
+
60
67
  // sample 1
61
68
  // position
62
69
  l.getWorldPosition( v );
@@ -121,7 +128,7 @@ export class LightsInfoUniformStruct {
121
128
 
122
129
  } else if ( l.isSpotLight ) {
123
130
 
124
- const radius = l.radius;
131
+ const radius = l.radius || 0;
125
132
  eye.setFromMatrixPosition( l.matrixWorld );
126
133
  target.setFromMatrixPosition( l.target.matrixWorld );
127
134
  m.lookAt( eye, target, up );
@@ -151,24 +158,21 @@ export class LightsInfoUniformStruct {
151
158
  // radius
152
159
  floatArray[ baseIndex + ( index ++ ) ] = radius;
153
160
 
154
- // near
155
- floatArray[ baseIndex + ( index ++ ) ] = l.shadow.camera.near;
156
-
157
161
  // decay
158
162
  floatArray[ baseIndex + ( index ++ ) ] = l.decay;
159
163
 
160
164
  // distance
161
165
  floatArray[ baseIndex + ( index ++ ) ] = l.distance;
162
166
 
163
- // sample 6
164
167
  // coneCos
165
168
  floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle );
166
169
 
170
+ // sample 6
167
171
  // penumbraCos
168
172
  floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle * ( 1 - l.penumbra ) );
169
173
 
170
174
  // iesProfile
171
- floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
175
+ floatArray[ baseIndex + ( index ++ ) ] = l.iesTexture ? iesTextures.indexOf( l.iesTexture ) : - 1;
172
176
 
173
177
  } else if ( l.isPointLight ) {
174
178
 
@@ -185,7 +189,7 @@ export class LightsInfoUniformStruct {
185
189
  index += 4;
186
190
 
187
191
  // sample 5
188
- index += 2;
192
+ index += 1;
189
193
 
190
194
  floatArray[ baseIndex + ( index ++ ) ] = l.decay;
191
195
  floatArray[ baseIndex + ( index ++ ) ] = l.distance;
@@ -2,11 +2,11 @@ import {
2
2
  WebGLArrayRenderTarget,
3
3
  RGBAFormat,
4
4
  UnsignedByteType,
5
- MeshBasicMaterial,
6
5
  Color,
7
6
  RepeatWrapping,
8
7
  LinearFilter,
9
8
  NoToneMapping,
9
+ ShaderMaterial,
10
10
  } from 'three';
11
11
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
12
12
  import { reduceTexturesToUniqueSources } from './utils.js';
@@ -31,7 +31,7 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
31
31
 
32
32
  };
33
33
 
34
- const fsQuad = new FullScreenQuad( new MeshBasicMaterial() );
34
+ const fsQuad = new FullScreenQuad( new CopyMaterial() );
35
35
  this.fsQuad = fsQuad;
36
36
 
37
37
  }
@@ -66,7 +66,6 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
66
66
  texture.matrix.identity();
67
67
 
68
68
  fsQuad.material.map = texture;
69
- fsQuad.material.transparent = true;
70
69
 
71
70
  renderer.setRenderTarget( this, i );
72
71
  fsQuad.render( renderer );
@@ -95,3 +94,51 @@ export class RenderTarget2DArray extends WebGLArrayRenderTarget {
95
94
  }
96
95
 
97
96
  }
97
+
98
+ class CopyMaterial extends ShaderMaterial {
99
+
100
+ get map() {
101
+
102
+ return this.uniforms.map.value;
103
+
104
+ }
105
+ set map( v ) {
106
+
107
+ this.uniforms.map.value = v;
108
+
109
+ }
110
+
111
+ constructor() {
112
+
113
+ super( {
114
+ uniforms: {
115
+
116
+ map: { value: null },
117
+
118
+ },
119
+
120
+ vertexShader: /* glsl */`
121
+ varying vec2 vUv;
122
+ void main() {
123
+
124
+ vUv = uv;
125
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
126
+
127
+ }
128
+ `,
129
+
130
+ fragmentShader: /* glsl */`
131
+ uniform sampler2D map;
132
+ varying vec2 vUv;
133
+ void main() {
134
+
135
+ gl_FragColor = texture2D( map, vUv );
136
+
137
+ }
138
+ `
139
+ } );
140
+
141
+ }
142
+
143
+
144
+ }
@@ -0,0 +1,49 @@
1
+ import { DataTexture, FloatType, NearestFilter, RGBAFormat } from 'three';
2
+ import { StratifiedSamplerCombined } from './stratified/StratifiedSamplerCombined.js';
3
+
4
+ export class StratifiedSamplesTexture extends DataTexture {
5
+
6
+ constructor( count = 1, depth = 1, strata = 8 ) {
7
+
8
+ super( new Float32Array( 1 ), 1, 1, RGBAFormat, FloatType );
9
+ this.minFilter = NearestFilter;
10
+ this.magFilter = NearestFilter;
11
+
12
+ this.strata = strata;
13
+ this.sampler = null;
14
+
15
+ this.init( count, depth, strata );
16
+
17
+ }
18
+
19
+ init( count, depth, strata = this.strata ) {
20
+
21
+ const { image } = this;
22
+ if ( image.width === depth && image.height === count ) {
23
+
24
+ return;
25
+
26
+ }
27
+
28
+ const dimensions = new Array( count * depth ).fill( 4 );
29
+ const sampler = new StratifiedSamplerCombined( strata, dimensions );
30
+
31
+ image.width = depth;
32
+ image.height = count;
33
+ image.data = sampler.samples;
34
+
35
+ this.sampler = sampler;
36
+
37
+ this.dispose();
38
+ this.next();
39
+
40
+ }
41
+
42
+ next() {
43
+
44
+ this.sampler.next();
45
+ this.needsUpdate = true;
46
+
47
+ }
48
+
49
+ }
@@ -0,0 +1,73 @@
1
+ // Stratified Sampling based on implementation from hoverinc pathtracer
2
+ // - https://github.com/hoverinc/ray-tracing-renderer
3
+ // - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
4
+
5
+ export function shuffle( arr ) {
6
+
7
+ for ( let i = arr.length - 1; i > 0; i -- ) {
8
+
9
+ const j = Math.floor( Math.random() * ( i + 1 ) );
10
+ const x = arr[ i ];
11
+ arr[ i ] = arr[ j ];
12
+ arr[ j ] = x;
13
+
14
+ }
15
+
16
+ return arr;
17
+
18
+ }
19
+
20
+ // strataCount : The number of bins per dimension
21
+ // dimensions : The number of dimensions to generate stratified values for
22
+ export class StratifiedSampler {
23
+
24
+ constructor( strataCount, dimensions ) {
25
+
26
+ const l = strataCount ** dimensions;
27
+ const strata = new Uint16Array( l );
28
+ let index = l;
29
+
30
+ // each integer represents a statum bin
31
+ for ( let i = 0; i < l; i ++ ) {
32
+
33
+ strata[ i ] = i;
34
+
35
+ }
36
+
37
+ this.samples = new Float32Array( dimensions );
38
+
39
+ this.strataCount = strataCount;
40
+
41
+ this.restart = function () {
42
+
43
+ index = 0;
44
+
45
+ };
46
+
47
+ this.next = function () {
48
+
49
+ const { samples } = this;
50
+
51
+ if ( index >= strata.length ) {
52
+
53
+ shuffle( strata );
54
+ this.restart();
55
+
56
+ }
57
+
58
+ let stratum = strata[ index ++ ];
59
+
60
+ for ( let i = 0; i < dimensions; i ++ ) {
61
+
62
+ samples[ i ] = ( stratum % strataCount + Math.random() ) / strataCount;
63
+ stratum = Math.floor( stratum / strataCount );
64
+
65
+ }
66
+
67
+ return samples;
68
+
69
+ };
70
+
71
+ }
72
+
73
+ }
@@ -0,0 +1,59 @@
1
+ // Stratified Sampling based on implementation from hoverinc pathtracer
2
+ // - https://github.com/hoverinc/ray-tracing-renderer
3
+ // - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
4
+
5
+ import { StratifiedSampler } from './StratifiedSampler.js';
6
+
7
+ // Stratified set of data with each tuple stratified separately and combined
8
+ export class StratifiedSamplerCombined {
9
+
10
+ constructor( strataCount, listOfDimensions ) {
11
+
12
+ let totalDim = 0;
13
+ for ( const dim of listOfDimensions ) {
14
+
15
+ totalDim += dim;
16
+
17
+ }
18
+
19
+ const combined = new Float32Array( totalDim );
20
+ const strataObjs = [];
21
+ let offset = 0;
22
+ for ( const dim of listOfDimensions ) {
23
+
24
+ const sampler = new StratifiedSampler( strataCount, dim );
25
+ sampler.samples = new Float32Array( combined.buffer, offset, sampler.samples.length );
26
+ offset += sampler.samples.length * 4;
27
+ strataObjs.push( sampler );
28
+
29
+ }
30
+
31
+ this.samples = combined;
32
+
33
+ this.strataCount = strataCount;
34
+
35
+ this.next = function () {
36
+
37
+ for ( const strata of strataObjs ) {
38
+
39
+ strata.next();
40
+
41
+ }
42
+
43
+ return combined;
44
+
45
+ };
46
+
47
+ this.restart = function () {
48
+
49
+ for ( const strata of strataObjs ) {
50
+
51
+ strata.restart();
52
+
53
+ }
54
+
55
+ };
56
+
57
+ }
58
+
59
+ }
@@ -2,7 +2,7 @@
2
2
  // when rendering each texture to the texture array they must have a consistent color space.
3
3
  export function getTextureHash( t ) {
4
4
 
5
- return `${ t.source.uuid }:${ t.encoding }`;
5
+ return `${ t.source.uuid }:${ t.colorSpace }`;
6
6
 
7
7
  }
8
8
 
@@ -1,5 +1,5 @@
1
1
  import { BufferAttribute } from 'three';
2
- import { mergeGeometries, mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
2
+ import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
3
3
  export function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
4
4
 
5
5
  const indexAttr = geometry.index;
@@ -54,25 +54,6 @@ export function getGroupMaterialIndicesAttribute( geometry, materials, allMateri
54
54
 
55
55
  }
56
56
 
57
- export function trimToAttributes( geometry, attributes ) {
58
-
59
- // trim any unneeded attributes
60
- if ( attributes ) {
61
-
62
- for ( const key in geometry.attributes ) {
63
-
64
- if ( ! attributes.includes( key ) ) {
65
-
66
- geometry.deleteAttribute( key );
67
-
68
- }
69
-
70
- }
71
-
72
- }
73
-
74
- }
75
-
76
57
  export function setCommonAttributes( geometry, options ) {
77
58
 
78
59
  const { attributes = [], normalMapRequired = false } = options;
@@ -90,6 +71,13 @@ export function setCommonAttributes( geometry, options ) {
90
71
 
91
72
  }
92
73
 
74
+ if ( ! geometry.attributes.uv2 && ( attributes && attributes.includes( 'uv2' ) ) ) {
75
+
76
+ const vertCount = geometry.attributes.position.count;
77
+ geometry.setAttribute( 'uv2', new BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
78
+
79
+ }
80
+
93
81
  if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
94
82
 
95
83
  if ( normalMapRequired ) {
@@ -137,84 +125,3 @@ export function setCommonAttributes( geometry, options ) {
137
125
  }
138
126
 
139
127
  }
140
-
141
- export function mergeMeshes( meshes, options = {} ) {
142
-
143
- options = { attributes: null, cloneGeometry: true, ...options };
144
-
145
- const transformedGeometry = [];
146
- const materialSet = new Set();
147
- for ( let i = 0, l = meshes.length; i < l; i ++ ) {
148
-
149
- // save any materials
150
- const mesh = meshes[ i ];
151
- if ( mesh.visible === false ) continue;
152
-
153
- if ( Array.isArray( mesh.material ) ) {
154
-
155
- mesh.material.forEach( m => materialSet.add( m ) );
156
-
157
- } else {
158
-
159
- materialSet.add( mesh.material );
160
-
161
- }
162
-
163
- }
164
-
165
- const materials = Array.from( materialSet );
166
- for ( let i = 0, l = meshes.length; i < l; i ++ ) {
167
-
168
- // ensure the matrix world is up to date
169
- const mesh = meshes[ i ];
170
- if ( mesh.visible === false ) continue;
171
-
172
- mesh.updateMatrixWorld();
173
-
174
- // apply the matrix world to the geometry
175
- const originalGeometry = meshes[ i ].geometry;
176
- const geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
177
- geometry.applyMatrix4( mesh.matrixWorld );
178
-
179
- if ( mesh.matrixWorld.determinant() < 0 ) {
180
-
181
- geometry.index.array.reverse();
182
-
183
- }
184
-
185
- // ensure our geometry has common attributes
186
- setCommonAttributes( geometry, {
187
- attributes: options.attributes,
188
- normalMapRequired: ! ! mesh.material.normalMap,
189
- } );
190
- trimToAttributes( geometry, options.attributes );
191
-
192
- // create the material index attribute
193
- const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
194
- geometry.setAttribute( 'materialIndex', materialIndexAttribute );
195
-
196
- transformedGeometry.push( geometry );
197
-
198
- }
199
-
200
- const textureSet = new Set();
201
- materials.forEach( material => {
202
-
203
- for ( const key in material ) {
204
-
205
- const value = material[ key ];
206
- if ( value && value.isTexture ) {
207
-
208
- textureSet.add( value );
209
-
210
- }
211
-
212
- }
213
-
214
- } );
215
-
216
- const geometry = mergeGeometries( transformedGeometry, false );
217
- const textures = Array.from( textureSet );
218
- return { geometry, materials, textures };
219
-
220
- }
@@ -1,23 +1,34 @@
1
- import { PathTracingSceneGenerator } from '../core/PathTracingSceneGenerator.js';
2
- import { SAH } from 'three-mesh-bvh';
3
1
  import { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
2
+ import { DynamicPathTracingSceneGenerator } from '../core/DynamicPathTracingSceneGenerator.js';
4
3
 
5
- export class PathTracingSceneWorker extends PathTracingSceneGenerator {
4
+ export class PathTracingSceneWorker {
6
5
 
7
6
  constructor() {
8
7
 
9
- super();
10
8
  this.bvhGenerator = new GenerateMeshBVHWorker();
11
9
 
12
10
  }
13
11
 
14
12
  generate( scene, options = {} ) {
15
13
 
14
+ // ensure scene transforms are up to date
15
+ // TODO: remove this?
16
+ if ( Array.isArray( scene ) ) {
17
+
18
+ scene.forEach( s => s.updateMatrixWorld( true ) );
19
+
20
+ } else {
21
+
22
+ scene.updateMatrixWorld( true );
23
+
24
+ }
25
+
16
26
  const { bvhGenerator } = this;
17
- const { geometry, materials, textures, lights, spotLights } = this.prepScene( scene );
27
+ const sceneGenerator = new DynamicPathTracingSceneGenerator( scene );
28
+ sceneGenerator.prepScene();
18
29
 
19
- const bvhOptions = { strategy: SAH, ...options, maxLeafTris: 1 };
20
- const bvhPromise = bvhGenerator.generate( geometry, bvhOptions );
30
+ const { geometry, materials, textures, lights } = sceneGenerator;
31
+ const bvhPromise = bvhGenerator.generate( geometry, options );
21
32
  return bvhPromise.then( bvh => {
22
33
 
23
34
  return {
@@ -25,7 +36,6 @@ export class PathTracingSceneWorker extends PathTracingSceneGenerator {
25
36
  materials,
26
37
  textures,
27
38
  lights,
28
- spotLights,
29
39
  bvh,
30
40
  };
31
41