three-gpu-pathtracer 0.0.13 → 0.0.14

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