three-gpu-pathtracer 0.0.11 → 0.0.12

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 (53) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +886 -886
  3. package/build/index.module.js +6478 -6470
  4. package/build/index.module.js.map +1 -1
  5. package/build/index.umd.cjs +6473 -6465
  6. package/build/index.umd.cjs.map +1 -1
  7. package/package.json +72 -69
  8. package/src/core/DynamicPathTracingSceneGenerator.js +119 -119
  9. package/src/core/MaterialReducer.js +256 -256
  10. package/src/core/PathTracingRenderer.js +275 -275
  11. package/src/core/PathTracingSceneGenerator.js +69 -69
  12. package/src/index.js +39 -39
  13. package/src/materials/AlphaDisplayMaterial.js +48 -48
  14. package/src/materials/AmbientOcclusionMaterial.js +199 -199
  15. package/src/materials/BlendMaterial.js +67 -67
  16. package/src/materials/DenoiseMaterial.js +142 -142
  17. package/src/materials/GraphMaterial.js +243 -243
  18. package/src/materials/LambertPathTracingMaterial.js +285 -285
  19. package/src/materials/MaterialBase.js +56 -56
  20. package/src/materials/PhysicalPathTracingMaterial.js +982 -982
  21. package/src/objects/EquirectCamera.js +13 -13
  22. package/src/objects/PhysicalCamera.js +28 -28
  23. package/src/objects/PhysicalSpotLight.js +14 -14
  24. package/src/objects/ShapedAreaLight.js +12 -12
  25. package/src/shader/shaderBvhAnyHit.js +76 -0
  26. package/src/shader/shaderEnvMapSampling.js +58 -58
  27. package/src/shader/shaderGGXFunctions.js +100 -100
  28. package/src/shader/shaderIridescenceFunctions.js +130 -130
  29. package/src/shader/shaderLayerTexelFetchFunctions.js +25 -25
  30. package/src/shader/shaderLightSampling.js +229 -229
  31. package/src/shader/shaderMaterialSampling.js +506 -498
  32. package/src/shader/shaderRandFunctions.js +57 -57
  33. package/src/shader/shaderSheenFunctions.js +98 -98
  34. package/src/shader/shaderSobolSampling.js +256 -256
  35. package/src/shader/shaderStructs.js +325 -325
  36. package/src/shader/shaderUtils.js +361 -361
  37. package/src/textures/GradientEquirectTexture.js +35 -35
  38. package/src/textures/ProceduralEquirectTexture.js +75 -75
  39. package/src/uniforms/AttributesTextureArray.js +35 -35
  40. package/src/uniforms/EquirectHdrInfoUniform.js +259 -259
  41. package/src/uniforms/FloatAttributeTextureArray.js +169 -169
  42. package/src/uniforms/IESProfilesTexture.js +100 -100
  43. package/src/uniforms/LightsInfoUniformStruct.js +207 -207
  44. package/src/uniforms/MaterialsTexture.js +426 -426
  45. package/src/uniforms/PhysicalCameraUniform.js +36 -36
  46. package/src/uniforms/RenderTarget2DArray.js +97 -97
  47. package/src/uniforms/utils.js +30 -30
  48. package/src/utils/BlurredEnvMapGenerator.js +116 -116
  49. package/src/utils/GeometryPreparationUtils.js +214 -214
  50. package/src/utils/IESLoader.js +325 -325
  51. package/src/utils/SobolNumberMapGenerator.js +80 -80
  52. package/src/utils/UVUnwrapper.js +101 -101
  53. package/src/workers/PathTracingSceneWorker.js +42 -42
@@ -1,259 +1,259 @@
1
- import { DataTexture, FloatType, RedFormat, LinearFilter, DataUtils, HalfFloatType, Source, RepeatWrapping } from 'three';
2
-
3
- function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
4
-
5
- let lower = 0;
6
- let upper = count - 1;
7
- while ( lower < upper ) {
8
-
9
- const mid = ~ ~ ( 0.5 * upper + 0.5 * lower );
10
-
11
- // check if the middle array value is above or below the target and shift
12
- // which half of the array we're looking at
13
- if ( array[ offset + mid ] < targetValue ) {
14
-
15
- lower = mid + 1;
16
-
17
- } else {
18
-
19
- upper = mid;
20
-
21
- }
22
-
23
- }
24
-
25
- return lower;
26
-
27
- }
28
-
29
- function colorToLuminance( r, g, b ) {
30
-
31
- // https://en.wikipedia.org/wiki/Relative_luminance
32
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
33
-
34
- }
35
-
36
- // ensures the data is all floating point values and flipY is false
37
- function preprocessEnvMap( envMap ) {
38
-
39
- const map = envMap.clone();
40
- map.source = new Source( { ...map.image } );
41
- const { width, height, data } = map.image;
42
-
43
- // TODO: is there a simple way to avoid cloning and adjusting the env map data here?
44
- // convert the data from half float uint 16 arrays to float arrays for cdf computation
45
- let newData = data;
46
- if ( map.type === HalfFloatType ) {
47
-
48
- newData = new Float32Array( data.length );
49
- for ( const i in data ) {
50
-
51
- newData[ i ] = DataUtils.fromHalfFloat( data[ i ] );
52
-
53
- }
54
-
55
- map.image.data = newData;
56
- map.type = FloatType;
57
-
58
- }
59
-
60
- // remove any y flipping for cdf computation
61
- if ( map.flipY ) {
62
-
63
- const ogData = newData;
64
- newData = newData.slice();
65
- for ( let y = 0; y < height; y ++ ) {
66
-
67
- for ( let x = 0; x < width; x ++ ) {
68
-
69
- const newY = height - y - 1;
70
- const ogIndex = 4 * ( y * width + x );
71
- const newIndex = 4 * ( newY * width + x );
72
-
73
- newData[ newIndex + 0 ] = ogData[ ogIndex + 0 ];
74
- newData[ newIndex + 1 ] = ogData[ ogIndex + 1 ];
75
- newData[ newIndex + 2 ] = ogData[ ogIndex + 2 ];
76
- newData[ newIndex + 3 ] = ogData[ ogIndex + 3 ];
77
-
78
- }
79
-
80
- }
81
-
82
- map.flipY = false;
83
- map.image.data = newData;
84
-
85
- }
86
-
87
- return map;
88
-
89
- }
90
-
91
- export class EquirectHdrInfoUniform {
92
-
93
- constructor() {
94
-
95
- // Stores a map of [0, 1] value -> cumulative importance row & pdf
96
- // used to sampling a random value to a relevant row to sample from
97
- const marginalWeights = new DataTexture();
98
- marginalWeights.type = FloatType;
99
- marginalWeights.format = RedFormat;
100
- marginalWeights.minFilter = LinearFilter;
101
- marginalWeights.magFilter = LinearFilter;
102
- marginalWeights.generateMipmaps = false;
103
-
104
- // Stores a map of [0, 1] value -> cumulative importance column & pdf
105
- // used to sampling a random value to a relevant pixel to sample from
106
- const conditionalWeights = new DataTexture();
107
- conditionalWeights.type = FloatType;
108
- conditionalWeights.format = RedFormat;
109
- conditionalWeights.minFilter = LinearFilter;
110
- conditionalWeights.magFilter = LinearFilter;
111
- conditionalWeights.generateMipmaps = false;
112
-
113
- this.marginalWeights = marginalWeights;
114
- this.conditionalWeights = conditionalWeights;
115
- this.map = null;
116
-
117
- // the total sum value is separated into two values to work around low precision
118
- // storage of floating values in structs
119
- this.totalSumWhole = 0;
120
- this.totalSumDecimal = 0;
121
-
122
- }
123
-
124
- dispose() {
125
-
126
- this.marginalWeights.dispose();
127
- this.conditionalWeights.dispose();
128
- if ( this.map ) this.map.dispose();
129
-
130
- }
131
-
132
- updateFrom( hdr ) {
133
-
134
- // https://github.com/knightcrawler25/GLSL-PathTracer/blob/3c6fd9b6b3da47cd50c527eeb45845eef06c55c3/src/loaders/hdrloader.cpp
135
- // https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
136
- const map = preprocessEnvMap( hdr );
137
- map.wrapS = RepeatWrapping;
138
- map.wrapT = RepeatWrapping;
139
-
140
- const { width, height, data } = map.image;
141
-
142
- // "conditional" = "pixel relative to row pixels sum"
143
- // "marginal" = "row relative to row sum"
144
-
145
- // track the importance of any given pixel in the image by tracking its weight relative to other pixels in the image
146
- const pdfConditional = new Float32Array( width * height );
147
- const cdfConditional = new Float32Array( width * height );
148
-
149
- const pdfMarginal = new Float32Array( height );
150
- const cdfMarginal = new Float32Array( height );
151
-
152
- let totalSumValue = 0.0;
153
- let cumulativeWeightMarginal = 0.0;
154
- for ( let y = 0; y < height; y ++ ) {
155
-
156
- let cumulativeRowWeight = 0.0;
157
- for ( let x = 0; x < width; x ++ ) {
158
-
159
- const i = y * width + x;
160
- const r = data[ 4 * i + 0 ];
161
- const g = data[ 4 * i + 1 ];
162
- const b = data[ 4 * i + 2 ];
163
-
164
- // the probability of the pixel being selected in this row is the
165
- // scale of the luminance relative to the rest of the pixels.
166
- // TODO: this should also account for the solid angle of the pixel when sampling
167
- const weight = colorToLuminance( r, g, b );
168
- cumulativeRowWeight += weight;
169
- totalSumValue += weight;
170
-
171
- pdfConditional[ i ] = weight;
172
- cdfConditional[ i ] = cumulativeRowWeight;
173
-
174
- }
175
-
176
- // can happen if the row is all black
177
- if ( cumulativeRowWeight !== 0 ) {
178
-
179
- // scale the pdf and cdf to [0.0, 1.0]
180
- for ( let i = y * width, l = y * width + width; i < l; i ++ ) {
181
-
182
- pdfConditional[ i ] /= cumulativeRowWeight;
183
- cdfConditional[ i ] /= cumulativeRowWeight;
184
-
185
- }
186
-
187
- }
188
-
189
- cumulativeWeightMarginal += cumulativeRowWeight;
190
-
191
- // compute the marginal pdf and cdf along the height of the map.
192
- pdfMarginal[ y ] = cumulativeRowWeight;
193
- cdfMarginal[ y ] = cumulativeWeightMarginal;
194
-
195
- }
196
-
197
- // can happen if the texture is all black
198
- if ( cumulativeWeightMarginal !== 0 ) {
199
-
200
- // scale the marginal pdf and cdf to [0.0, 1.0]
201
- for ( let i = 0, l = pdfMarginal.length; i < l; i ++ ) {
202
-
203
- pdfMarginal[ i ] /= cumulativeWeightMarginal;
204
- cdfMarginal[ i ] /= cumulativeWeightMarginal;
205
-
206
- }
207
-
208
- }
209
-
210
- // compute a sorted index of distributions and the probabilities along them for both
211
- // the marginal and conditional data. These will be used to sample with a random number
212
- // to retrieve a uv value to sample in the environment map.
213
- // These values continually increase so it's okay to interpolate between them.
214
- const marginalDataArray = new Float32Array( height );
215
- const conditionalDataArray = new Float32Array( width * height );
216
-
217
- // we add a half texel offset so we're sampling the center of the pixel
218
- for ( let i = 0; i < height; i ++ ) {
219
-
220
- const dist = ( i + 1 ) / height;
221
- const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
222
-
223
- marginalDataArray[ i ] = ( row + 0.5 ) / height;
224
-
225
- }
226
-
227
- for ( let y = 0; y < height; y ++ ) {
228
-
229
- for ( let x = 0; x < width; x ++ ) {
230
-
231
- const i = y * width + x;
232
- const dist = ( x + 1 ) / width;
233
- const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
234
-
235
- conditionalDataArray[ i ] = ( col + 0.5 ) / width;
236
-
237
- }
238
-
239
- }
240
-
241
- this.dispose();
242
-
243
- const { marginalWeights, conditionalWeights } = this;
244
- marginalWeights.image = { width: height, height: 1, data: marginalDataArray };
245
- marginalWeights.needsUpdate = true;
246
-
247
- conditionalWeights.image = { width, height, data: conditionalDataArray };
248
- conditionalWeights.needsUpdate = true;
249
-
250
- const totalSumWhole = ~ ~ totalSumValue;
251
- const totalSumDecimal = ( totalSumValue - totalSumWhole );
252
- this.totalSumWhole = totalSumWhole;
253
- this.totalSumDecimal = totalSumDecimal;
254
-
255
- this.map = map;
256
-
257
- }
258
-
259
- }
1
+ import { DataTexture, FloatType, RedFormat, LinearFilter, DataUtils, HalfFloatType, Source, RepeatWrapping } from 'three';
2
+
3
+ function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
4
+
5
+ let lower = 0;
6
+ let upper = count - 1;
7
+ while ( lower < upper ) {
8
+
9
+ const mid = ~ ~ ( 0.5 * upper + 0.5 * lower );
10
+
11
+ // check if the middle array value is above or below the target and shift
12
+ // which half of the array we're looking at
13
+ if ( array[ offset + mid ] < targetValue ) {
14
+
15
+ lower = mid + 1;
16
+
17
+ } else {
18
+
19
+ upper = mid;
20
+
21
+ }
22
+
23
+ }
24
+
25
+ return lower;
26
+
27
+ }
28
+
29
+ function colorToLuminance( r, g, b ) {
30
+
31
+ // https://en.wikipedia.org/wiki/Relative_luminance
32
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
33
+
34
+ }
35
+
36
+ // ensures the data is all floating point values and flipY is false
37
+ function preprocessEnvMap( envMap ) {
38
+
39
+ const map = envMap.clone();
40
+ map.source = new Source( { ...map.image } );
41
+ const { width, height, data } = map.image;
42
+
43
+ // TODO: is there a simple way to avoid cloning and adjusting the env map data here?
44
+ // convert the data from half float uint 16 arrays to float arrays for cdf computation
45
+ let newData = data;
46
+ if ( map.type === HalfFloatType ) {
47
+
48
+ newData = new Float32Array( data.length );
49
+ for ( const i in data ) {
50
+
51
+ newData[ i ] = DataUtils.fromHalfFloat( data[ i ] );
52
+
53
+ }
54
+
55
+ map.image.data = newData;
56
+ map.type = FloatType;
57
+
58
+ }
59
+
60
+ // remove any y flipping for cdf computation
61
+ if ( map.flipY ) {
62
+
63
+ const ogData = newData;
64
+ newData = newData.slice();
65
+ for ( let y = 0; y < height; y ++ ) {
66
+
67
+ for ( let x = 0; x < width; x ++ ) {
68
+
69
+ const newY = height - y - 1;
70
+ const ogIndex = 4 * ( y * width + x );
71
+ const newIndex = 4 * ( newY * width + x );
72
+
73
+ newData[ newIndex + 0 ] = ogData[ ogIndex + 0 ];
74
+ newData[ newIndex + 1 ] = ogData[ ogIndex + 1 ];
75
+ newData[ newIndex + 2 ] = ogData[ ogIndex + 2 ];
76
+ newData[ newIndex + 3 ] = ogData[ ogIndex + 3 ];
77
+
78
+ }
79
+
80
+ }
81
+
82
+ map.flipY = false;
83
+ map.image.data = newData;
84
+
85
+ }
86
+
87
+ return map;
88
+
89
+ }
90
+
91
+ export class EquirectHdrInfoUniform {
92
+
93
+ constructor() {
94
+
95
+ // Stores a map of [0, 1] value -> cumulative importance row & pdf
96
+ // used to sampling a random value to a relevant row to sample from
97
+ const marginalWeights = new DataTexture();
98
+ marginalWeights.type = FloatType;
99
+ marginalWeights.format = RedFormat;
100
+ marginalWeights.minFilter = LinearFilter;
101
+ marginalWeights.magFilter = LinearFilter;
102
+ marginalWeights.generateMipmaps = false;
103
+
104
+ // Stores a map of [0, 1] value -> cumulative importance column & pdf
105
+ // used to sampling a random value to a relevant pixel to sample from
106
+ const conditionalWeights = new DataTexture();
107
+ conditionalWeights.type = FloatType;
108
+ conditionalWeights.format = RedFormat;
109
+ conditionalWeights.minFilter = LinearFilter;
110
+ conditionalWeights.magFilter = LinearFilter;
111
+ conditionalWeights.generateMipmaps = false;
112
+
113
+ this.marginalWeights = marginalWeights;
114
+ this.conditionalWeights = conditionalWeights;
115
+ this.map = null;
116
+
117
+ // the total sum value is separated into two values to work around low precision
118
+ // storage of floating values in structs
119
+ this.totalSumWhole = 0;
120
+ this.totalSumDecimal = 0;
121
+
122
+ }
123
+
124
+ dispose() {
125
+
126
+ this.marginalWeights.dispose();
127
+ this.conditionalWeights.dispose();
128
+ if ( this.map ) this.map.dispose();
129
+
130
+ }
131
+
132
+ updateFrom( hdr ) {
133
+
134
+ // https://github.com/knightcrawler25/GLSL-PathTracer/blob/3c6fd9b6b3da47cd50c527eeb45845eef06c55c3/src/loaders/hdrloader.cpp
135
+ // https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
136
+ const map = preprocessEnvMap( hdr );
137
+ map.wrapS = RepeatWrapping;
138
+ map.wrapT = RepeatWrapping;
139
+
140
+ const { width, height, data } = map.image;
141
+
142
+ // "conditional" = "pixel relative to row pixels sum"
143
+ // "marginal" = "row relative to row sum"
144
+
145
+ // track the importance of any given pixel in the image by tracking its weight relative to other pixels in the image
146
+ const pdfConditional = new Float32Array( width * height );
147
+ const cdfConditional = new Float32Array( width * height );
148
+
149
+ const pdfMarginal = new Float32Array( height );
150
+ const cdfMarginal = new Float32Array( height );
151
+
152
+ let totalSumValue = 0.0;
153
+ let cumulativeWeightMarginal = 0.0;
154
+ for ( let y = 0; y < height; y ++ ) {
155
+
156
+ let cumulativeRowWeight = 0.0;
157
+ for ( let x = 0; x < width; x ++ ) {
158
+
159
+ const i = y * width + x;
160
+ const r = data[ 4 * i + 0 ];
161
+ const g = data[ 4 * i + 1 ];
162
+ const b = data[ 4 * i + 2 ];
163
+
164
+ // the probability of the pixel being selected in this row is the
165
+ // scale of the luminance relative to the rest of the pixels.
166
+ // TODO: this should also account for the solid angle of the pixel when sampling
167
+ const weight = colorToLuminance( r, g, b );
168
+ cumulativeRowWeight += weight;
169
+ totalSumValue += weight;
170
+
171
+ pdfConditional[ i ] = weight;
172
+ cdfConditional[ i ] = cumulativeRowWeight;
173
+
174
+ }
175
+
176
+ // can happen if the row is all black
177
+ if ( cumulativeRowWeight !== 0 ) {
178
+
179
+ // scale the pdf and cdf to [0.0, 1.0]
180
+ for ( let i = y * width, l = y * width + width; i < l; i ++ ) {
181
+
182
+ pdfConditional[ i ] /= cumulativeRowWeight;
183
+ cdfConditional[ i ] /= cumulativeRowWeight;
184
+
185
+ }
186
+
187
+ }
188
+
189
+ cumulativeWeightMarginal += cumulativeRowWeight;
190
+
191
+ // compute the marginal pdf and cdf along the height of the map.
192
+ pdfMarginal[ y ] = cumulativeRowWeight;
193
+ cdfMarginal[ y ] = cumulativeWeightMarginal;
194
+
195
+ }
196
+
197
+ // can happen if the texture is all black
198
+ if ( cumulativeWeightMarginal !== 0 ) {
199
+
200
+ // scale the marginal pdf and cdf to [0.0, 1.0]
201
+ for ( let i = 0, l = pdfMarginal.length; i < l; i ++ ) {
202
+
203
+ pdfMarginal[ i ] /= cumulativeWeightMarginal;
204
+ cdfMarginal[ i ] /= cumulativeWeightMarginal;
205
+
206
+ }
207
+
208
+ }
209
+
210
+ // compute a sorted index of distributions and the probabilities along them for both
211
+ // the marginal and conditional data. These will be used to sample with a random number
212
+ // to retrieve a uv value to sample in the environment map.
213
+ // These values continually increase so it's okay to interpolate between them.
214
+ const marginalDataArray = new Float32Array( height );
215
+ const conditionalDataArray = new Float32Array( width * height );
216
+
217
+ // we add a half texel offset so we're sampling the center of the pixel
218
+ for ( let i = 0; i < height; i ++ ) {
219
+
220
+ const dist = ( i + 1 ) / height;
221
+ const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
222
+
223
+ marginalDataArray[ i ] = ( row + 0.5 ) / height;
224
+
225
+ }
226
+
227
+ for ( let y = 0; y < height; y ++ ) {
228
+
229
+ for ( let x = 0; x < width; x ++ ) {
230
+
231
+ const i = y * width + x;
232
+ const dist = ( x + 1 ) / width;
233
+ const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
234
+
235
+ conditionalDataArray[ i ] = ( col + 0.5 ) / width;
236
+
237
+ }
238
+
239
+ }
240
+
241
+ this.dispose();
242
+
243
+ const { marginalWeights, conditionalWeights } = this;
244
+ marginalWeights.image = { width: height, height: 1, data: marginalDataArray };
245
+ marginalWeights.needsUpdate = true;
246
+
247
+ conditionalWeights.image = { width, height, data: conditionalDataArray };
248
+ conditionalWeights.needsUpdate = true;
249
+
250
+ const totalSumWhole = ~ ~ totalSumValue;
251
+ const totalSumDecimal = ( totalSumValue - totalSumWhole );
252
+ this.totalSumWhole = totalSumWhole;
253
+ this.totalSumDecimal = totalSumDecimal;
254
+
255
+ this.map = map;
256
+
257
+ }
258
+
259
+ }