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
@@ -94,15 +94,17 @@ export const bsdfSamplingGLSL = /* glsl */`
94
94
 
95
95
  // TODO: subsurface approx?
96
96
 
97
- float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
97
+ // float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
98
+ float F = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
98
99
  color = ( 1.0 - F ) * transFactor * metalFactor * wi.z * surf.color * ( retro + lambert ) / PI;
100
+
99
101
  return wi.z / PI;
100
102
 
101
103
  }
102
104
 
103
105
  vec3 diffuseDirection( vec3 wo, SurfaceRecord surf ) {
104
106
 
105
- vec3 lightDirection = sampleSphere( sobol2( 11 ) );
107
+ vec3 lightDirection = sampleSphere( rand2( 11 ) );
106
108
  lightDirection.z += 1.0;
107
109
  lightDirection = normalize( lightDirection );
108
110
 
@@ -147,7 +149,7 @@ export const bsdfSamplingGLSL = /* glsl */`
147
149
  vec3 halfVector = ggxDirection(
148
150
  wo,
149
151
  vec2( roughness ),
150
- sobol2( 12 )
152
+ rand2( 12 )
151
153
  );
152
154
 
153
155
  // apply to new ray by reflecting off the new normal
@@ -184,7 +186,7 @@ export const bsdfSamplingGLSL = /* glsl */`
184
186
  vec3 halfVector = ggxDirection(
185
187
  wo,
186
188
  vec2( filteredRoughness ),
187
- sobol2( 13 )
189
+ rand2( 13 )
188
190
  );
189
191
 
190
192
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
@@ -206,7 +208,8 @@ export const bsdfSamplingGLSL = /* glsl */`
206
208
  color = surf.transmission * surf.color;
207
209
 
208
210
  // PDF
209
- float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
211
+ // float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
212
+ float F = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
210
213
  if ( F >= 1.0 ) {
211
214
 
212
215
  return 0.0;
@@ -221,7 +224,7 @@ export const bsdfSamplingGLSL = /* glsl */`
221
224
 
222
225
  float roughness = surf.filteredRoughness;
223
226
  float eta = surf.eta;
224
- vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( sobol2( 13 ) ) * roughness );
227
+ vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( rand2( 13 ) ) * roughness );
225
228
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
226
229
 
227
230
  if ( surf.thinFilm ) {
@@ -262,7 +265,7 @@ export const bsdfSamplingGLSL = /* glsl */`
262
265
  vec3 halfVector = ggxDirection(
263
266
  wo,
264
267
  vec2( roughness ),
265
- sobol2( 14 )
268
+ rand2( 14 )
266
269
  );
267
270
 
268
271
  // apply to new ray by reflecting off the new normal
@@ -297,7 +300,8 @@ export const bsdfSamplingGLSL = /* glsl */`
297
300
 
298
301
  float metalness = surf.metalness;
299
302
  float transmission = surf.transmission;
300
- float fEstimate = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
303
+ // float fEstimate = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
304
+ float fEstimate = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
301
305
 
302
306
  float transSpecularProb = mix( max( 0.25, fEstimate ), 1.0, metalness );
303
307
  float diffSpecularProb = 0.5 + 0.5 * metalness;
@@ -413,7 +417,7 @@ export const bsdfSamplingGLSL = /* glsl */`
413
417
  ScatterRecord sampleRec;
414
418
  sampleRec.specularPdf = 0.0;
415
419
  sampleRec.pdf = 1.0 / ( 4.0 * PI );
416
- sampleRec.direction = sampleSphere( sobol2( 16 ) );
420
+ sampleRec.direction = sampleSphere( rand2( 16 ) );
417
421
  sampleRec.color = surf.color / ( 4.0 * PI );
418
422
  return sampleRec;
419
423
 
@@ -465,7 +469,7 @@ export const bsdfSamplingGLSL = /* glsl */`
465
469
  vec3 wi;
466
470
  vec3 clearcoatWi;
467
471
 
468
- float r = sobol( 15 );
472
+ float r = rand( 15 );
469
473
  if ( r <= cdf[0] ) { // diffuse
470
474
 
471
475
  wi = diffuseDirection( wo, surf );
@@ -69,30 +69,36 @@ export const fresnelGLSL = /* glsl */`
69
69
 
70
70
  }
71
71
 
72
- float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
72
+ // TODO: disney fresnel was removed and replaced with this fresnel function to better align with
73
+ // the glTF but is causing blown out pixels. Should be revisited
74
+ // float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
73
75
 
74
- if ( totalInternalReflection( cosTheta, eta ) ) {
76
+ // if ( totalInternalReflection( cosTheta, eta ) ) {
75
77
 
76
- return 1.0;
78
+ // return 1.0;
77
79
 
78
- }
80
+ // }
79
81
 
80
- return schlickFresnel( cosTheta, f0 );
82
+ // return schlickFresnel( cosTheta, f0 );
81
83
 
82
- }
84
+ // }
83
85
 
84
- /*
85
86
  // https://schuttejoe.github.io/post/disneybsdf/
86
87
  float disneyFresnel( vec3 wo, vec3 wi, vec3 wh, float f0, float eta, float metalness ) {
87
88
 
88
89
  float dotHV = dot( wo, wh );
89
- float dotHL = dot( wi, wh );
90
+ if ( totalInternalReflection( dotHV, eta ) ) {
90
91
 
92
+ return 1.0;
93
+
94
+ }
95
+
96
+ float dotHL = dot( wi, wh );
91
97
  float dielectricFresnel = dielectricFresnel( abs( dotHV ), eta );
92
98
  float metallicFresnel = schlickFresnel( dotHL, f0 );
93
99
 
94
100
  return mix( dielectricFresnel, metallicFresnel, metalness );
95
101
 
96
102
  }
97
- */
103
+
98
104
  `;
@@ -27,28 +27,28 @@ export const pcgGLSL = /* glsl */`
27
27
  }
28
28
 
29
29
  // returns [ 0, 1 ]
30
- float rand() {
30
+ float pcgRand() {
31
31
 
32
32
  pcg4d( WHITE_NOISE_SEED );
33
33
  return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
34
34
 
35
35
  }
36
36
 
37
- vec2 rand2() {
37
+ vec2 pcgRand2() {
38
38
 
39
39
  pcg4d( WHITE_NOISE_SEED );
40
40
  return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
41
41
 
42
42
  }
43
43
 
44
- vec3 rand3() {
44
+ vec3 pcgRand3() {
45
45
 
46
46
  pcg4d( WHITE_NOISE_SEED );
47
47
  return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
48
48
 
49
49
  }
50
50
 
51
- vec4 rand4() {
51
+ vec4 pcgRand4() {
52
52
 
53
53
  pcg4d( WHITE_NOISE_SEED );
54
54
  return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
@@ -0,0 +1,45 @@
1
+ export const stratifiedTextureGLSL = /* glsl */`
2
+
3
+ uniform sampler2D stratifiedTexture;
4
+ uniform sampler2D stratifiedOffsetTexture;
5
+
6
+ uint sobolPixelIndex = 0u;
7
+ uint sobolPathIndex = 0u;
8
+ uint sobolBounceIndex = 0u;
9
+ vec4 pixelSeed = vec4( 0 );
10
+
11
+ vec4 rand4( int v ) {
12
+
13
+ ivec2 uv = ivec2( v, sobolBounceIndex );
14
+ vec4 stratifiedSample = texelFetch( stratifiedTexture, uv, 0 );
15
+ return fract( stratifiedSample + pixelSeed.r ); // blue noise + stratified samples
16
+
17
+ }
18
+
19
+ vec3 rand3( int v ) {
20
+
21
+ return rand4( v ).xyz;
22
+
23
+ }
24
+
25
+ vec2 rand2( int v ) {
26
+
27
+ return rand4( v ).xy;
28
+
29
+ }
30
+
31
+ float rand( int v ) {
32
+
33
+ return rand4( v ).x;
34
+
35
+ }
36
+
37
+ void rng_initialize( vec2 screenCoord, int frame ) {
38
+
39
+ // tile the small noise texture across the entire screen
40
+ ivec2 noiseSize = ivec2( textureSize( stratifiedOffsetTexture, 0 ) );
41
+ pixelSeed = texelFetch( stratifiedOffsetTexture, ivec2( screenCoord.xy ) % noiseSize, 0 );
42
+
43
+ }
44
+
45
+ `;
@@ -26,10 +26,17 @@ export const equirectSamplingGLSL = /* glsl */`
26
26
  // samples the color given env map with CDF and returns the pdf of the direction
27
27
  float sampleEquirect( vec3 direction, inout vec3 color ) {
28
28
 
29
+ float totalSum = envMapInfo.totalSum;
30
+ if ( totalSum == 0.0 ) {
31
+
32
+ color = vec3( 0.0 );
33
+ return 1.0;
34
+
35
+ }
36
+
29
37
  vec2 uv = equirectDirectionToUv( direction );
30
38
  color = texture2D( envMapInfo.map, uv ).rgb;
31
39
 
32
- float totalSum = envMapInfo.totalSum;
33
40
  float lum = luminance( color );
34
41
  ivec2 resolution = textureSize( envMapInfo.map, 0 );
35
42
  float pdf = lum / totalSum;
@@ -61,18 +61,16 @@ export const lightsStructGLSL = /* glsl */`
61
61
  vec4 s4 = texelFetch1D( tex, i + 4u );
62
62
  vec4 s5 = texelFetch1D( tex, i + 5u );
63
63
  l.radius = s4.r;
64
- l.near = s4.g;
65
- l.decay = s4.b;
66
- l.distance = s4.a;
64
+ l.decay = s4.g;
65
+ l.distance = s4.b;
66
+ l.coneCos = s4.a;
67
67
 
68
- l.coneCos = s5.r;
69
- l.penumbraCos = s5.g;
70
- l.iesProfile = int( round( s5.b ) );
68
+ l.penumbraCos = s5.r;
69
+ l.iesProfile = int( round( s5.g ) );
71
70
 
72
71
  } else {
73
72
 
74
73
  l.radius = 0.0;
75
- l.near = 0.0;
76
74
  l.decay = 0.0;
77
75
  l.distance = 0.0;
78
76
 
@@ -0,0 +1,87 @@
1
+ import { DataTexture, FloatType, NearestFilter, RGBAFormat, RGFormat, RedFormat } from 'three';
2
+ import { BlueNoiseGenerator } from './blueNoise/BlueNoiseGenerator.js';
3
+
4
+ function getStride( channels ) {
5
+
6
+ if ( channels >= 3 ) {
7
+
8
+ return 4;
9
+
10
+ } else {
11
+
12
+ return channels;
13
+
14
+ }
15
+
16
+ }
17
+
18
+ function getFormat( channels ) {
19
+
20
+ switch ( channels ) {
21
+
22
+ case 1:
23
+ return RedFormat;
24
+ case 2:
25
+ return RGFormat;
26
+ default:
27
+ return RGBAFormat;
28
+
29
+ }
30
+
31
+ }
32
+
33
+ export class BlueNoiseTexture extends DataTexture {
34
+
35
+ constructor( size = 64, channels = 1 ) {
36
+
37
+ super( new Float32Array( 4 ), 1, 1, RGBAFormat, FloatType );
38
+ this.minFilter = NearestFilter;
39
+ this.magFilter = NearestFilter;
40
+
41
+ this.size = size;
42
+ this.channels = channels;
43
+ this.update();
44
+
45
+ }
46
+
47
+ update() {
48
+
49
+ const channels = this.channels;
50
+ const size = this.size;
51
+ const generator = new BlueNoiseGenerator();
52
+ generator.channels = channels;
53
+ generator.size = size;
54
+
55
+ const stride = getStride( channels );
56
+ const format = getFormat( stride );
57
+ if ( this.image.width !== size || format !== this.format ) {
58
+
59
+ this.image.width = size;
60
+ this.image.height = size;
61
+ this.image.data = new Float32Array( ( size ** 2 ) * stride );
62
+ this.format = format;
63
+ this.dispose();
64
+
65
+ }
66
+
67
+ const data = this.image.data;
68
+ for ( let i = 0, l = channels; i < l; i ++ ) {
69
+
70
+ const result = generator.generate();
71
+ const bin = result.data;
72
+ const maxValue = result.maxValue;
73
+
74
+ for ( let j = 0, l2 = bin.length; j < l2; j ++ ) {
75
+
76
+ const value = bin[ j ] / maxValue;
77
+ data[ j * stride + i ] = value;
78
+
79
+ }
80
+
81
+ }
82
+
83
+ this.needsUpdate = true;
84
+
85
+ }
86
+
87
+ }
@@ -2,14 +2,13 @@ import {
2
2
  ClampToEdgeWrapping,
3
3
  Color,
4
4
  DataTexture,
5
- DataUtils,
6
5
  EquirectangularReflectionMapping,
7
- HalfFloatType,
8
6
  LinearFilter,
9
7
  RepeatWrapping,
10
8
  RGBAFormat,
11
9
  Spherical,
12
10
  Vector2,
11
+ FloatType
13
12
  } from 'three';
14
13
 
15
14
  const _uv = new Vector2();
@@ -21,8 +20,8 @@ export class ProceduralEquirectTexture extends DataTexture {
21
20
  constructor( width = 512, height = 512 ) {
22
21
 
23
22
  super(
24
- new Uint16Array( width * height * 4 ),
25
- width, height, RGBAFormat, HalfFloatType, EquirectangularReflectionMapping,
23
+ new Float32Array( width * height * 4 ),
24
+ width, height, RGBAFormat, FloatType, EquirectangularReflectionMapping,
26
25
  RepeatWrapping, ClampToEdgeWrapping, LinearFilter, LinearFilter,
27
26
  );
28
27
 
@@ -54,10 +53,10 @@ export class ProceduralEquirectTexture extends DataTexture {
54
53
 
55
54
  const i = y * width + x;
56
55
  const i4 = 4 * i;
57
- data[ i4 + 0 ] = DataUtils.toHalfFloat( _color.r );
58
- data[ i4 + 1 ] = DataUtils.toHalfFloat( _color.g );
59
- data[ i4 + 2 ] = DataUtils.toHalfFloat( _color.b );
60
- data[ i4 + 3 ] = DataUtils.toHalfFloat( 1.0 );
56
+ data[ i4 + 0 ] = ( _color.r );
57
+ data[ i4 + 1 ] = ( _color.g );
58
+ data[ i4 + 2 ] = ( _color.b );
59
+ data[ i4 + 3 ] = ( 1.0 );
61
60
 
62
61
  }
63
62
 
@@ -0,0 +1,115 @@
1
+ import { shuffleArray, fillWithOnes } from './utils.js';
2
+ import { BlueNoiseSamples } from './BlueNoiseSamples.js';
3
+
4
+ export class BlueNoiseGenerator {
5
+
6
+ constructor() {
7
+
8
+ this.random = Math.random;
9
+ this.sigma = 1.5;
10
+ this.size = 64;
11
+ this.majorityPointsRatio = 0.1;
12
+
13
+ this.samples = new BlueNoiseSamples( 1 );
14
+ this.savedSamples = new BlueNoiseSamples( 1 );
15
+
16
+ }
17
+
18
+ generate() {
19
+
20
+ // http://cv.ulichney.com/papers/1993-void-cluster.pdf
21
+
22
+ const {
23
+ samples,
24
+ savedSamples,
25
+ sigma,
26
+ majorityPointsRatio,
27
+ size,
28
+ } = this;
29
+
30
+ samples.resize( size );
31
+ samples.setSigma( sigma );
32
+
33
+ // 1. Randomly place the minority points.
34
+ const pointCount = Math.floor( size * size * majorityPointsRatio );
35
+ const initialSamples = samples.binaryPattern;
36
+
37
+ fillWithOnes( initialSamples, pointCount );
38
+ shuffleArray( initialSamples, this.random );
39
+
40
+ for ( let i = 0, l = initialSamples.length; i < l; i ++ ) {
41
+
42
+ if ( initialSamples[ i ] === 1 ) {
43
+
44
+ samples.addPointIndex( i );
45
+
46
+ }
47
+
48
+ }
49
+
50
+ // 2. Remove minority point that is in densest cluster and place it in the largest void.
51
+ while ( true ) {
52
+
53
+ const clusterIndex = samples.findCluster();
54
+ samples.removePointIndex( clusterIndex );
55
+
56
+ const voidIndex = samples.findVoid();
57
+ if ( clusterIndex === voidIndex ) {
58
+
59
+ samples.addPointIndex( clusterIndex );
60
+ break;
61
+
62
+ }
63
+
64
+ samples.addPointIndex( voidIndex );
65
+
66
+ }
67
+
68
+ // 3. PHASE I: Assign a rank to each progressively less dense cluster point and put it
69
+ // in the dither array.
70
+ const ditherArray = new Uint32Array( size * size );
71
+ savedSamples.copy( samples );
72
+
73
+ let rank;
74
+ rank = samples.count - 1;
75
+ while ( rank >= 0 ) {
76
+
77
+ const clusterIndex = samples.findCluster();
78
+ samples.removePointIndex( clusterIndex );
79
+
80
+ ditherArray[ clusterIndex ] = rank;
81
+ rank --;
82
+
83
+ }
84
+
85
+ // 4. PHASE II: Do the same thing for the largest voids up to half of the total pixels using
86
+ // the initial binary pattern.
87
+ const totalSize = size * size;
88
+ rank = savedSamples.count;
89
+ while ( rank < totalSize / 2 ) {
90
+
91
+ const voidIndex = savedSamples.findVoid();
92
+ savedSamples.addPointIndex( voidIndex );
93
+ ditherArray[ voidIndex ] = rank;
94
+ rank ++;
95
+
96
+ }
97
+
98
+ // 5. PHASE III: Invert the pattern and finish out by assigning a rank to the remaining
99
+ // and iteratively removing them.
100
+ savedSamples.invert();
101
+
102
+ while ( rank < totalSize ) {
103
+
104
+ const clusterIndex = savedSamples.findCluster();
105
+ savedSamples.removePointIndex( clusterIndex );
106
+ ditherArray[ clusterIndex ] = rank;
107
+ rank ++;
108
+
109
+ }
110
+
111
+ return { data: ditherArray, maxValue: totalSize };
112
+
113
+ }
114
+
115
+ }