three-gpu-pathtracer 0.0.2 → 0.0.5

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 (38) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +728 -597
  3. package/build/index.module.js +3585 -1739
  4. package/build/index.module.js.map +1 -1
  5. package/build/index.umd.cjs +3589 -1739
  6. package/build/index.umd.cjs.map +1 -1
  7. package/package.json +60 -61
  8. package/src/core/DynamicPathTracingSceneGenerator.js +17 -11
  9. package/src/core/EquirectCamera.js +13 -0
  10. package/src/core/MaterialReducer.js +256 -256
  11. package/src/core/PathTracingRenderer.js +143 -28
  12. package/src/core/PathTracingSceneGenerator.js +68 -52
  13. package/src/core/PhysicalCamera.js +28 -28
  14. package/src/index.js +26 -23
  15. package/src/materials/AlphaDisplayMaterial.js +48 -0
  16. package/src/materials/AmbientOcclusionMaterial.js +197 -197
  17. package/src/materials/BlendMaterial.js +67 -0
  18. package/src/materials/LambertPathTracingMaterial.js +285 -285
  19. package/src/materials/MaterialBase.js +56 -56
  20. package/src/materials/PhysicalPathTracingMaterial.js +848 -420
  21. package/src/shader/shaderEnvMapSampling.js +59 -0
  22. package/src/shader/shaderGGXFunctions.js +108 -107
  23. package/src/shader/shaderLightSampling.js +87 -0
  24. package/src/shader/shaderMaterialSampling.js +501 -333
  25. package/src/shader/shaderStructs.js +191 -42
  26. package/src/shader/shaderUtils.js +287 -191
  27. package/src/uniforms/EquirectHdrInfoUniform.js +263 -0
  28. package/src/uniforms/LightsTexture.js +83 -0
  29. package/src/uniforms/MaterialsTexture.js +319 -0
  30. package/src/uniforms/PhysicalCameraUniform.js +36 -36
  31. package/src/uniforms/RenderTarget2DArray.js +93 -80
  32. package/src/utils/BlurredEnvMapGenerator.js +113 -0
  33. package/src/utils/GeometryPreparationUtils.js +2 -8
  34. package/src/utils/UVUnwrapper.js +101 -101
  35. package/src/workers/PathTracingSceneWorker.js +41 -40
  36. package/src/uniforms/EquirectPdfUniform.js +0 -132
  37. package/src/uniforms/MaterialStructArrayUniform.js +0 -18
  38. package/src/uniforms/MaterialStructUniform.js +0 -114
@@ -0,0 +1,263 @@
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;
7
+ while ( lower < upper ) {
8
+
9
+ const mid = ~ ~ ( 0.5 * upper + 0.5 * lower );
10
+
11
+
12
+ // check if the middle array value is above or below the target and shift
13
+ // which half of the array we're looking at
14
+ if ( array[ offset + mid ] < targetValue ) {
15
+
16
+ lower = mid + 1;
17
+
18
+ } else {
19
+
20
+ upper = mid;
21
+
22
+ }
23
+
24
+ }
25
+
26
+ return lower;
27
+
28
+ }
29
+
30
+ function colorToLuminance( r, g, b ) {
31
+
32
+ // https://en.wikipedia.org/wiki/Relative_luminance
33
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
34
+
35
+ }
36
+
37
+ // ensures the data is all floating point values and flipY is false
38
+ function preprocessEnvMap( envMap ) {
39
+
40
+ const map = envMap.clone();
41
+ map.source = new Source( { ...map.image } );
42
+ const { width, height, data } = map.image;
43
+
44
+ // TODO: is there a simple way to avoid cloning and adjusting the env map data here?
45
+ // convert the data from half float uint 16 arrays to float arrays for cdf computation
46
+ let newData = data;
47
+ if ( map.type === HalfFloatType ) {
48
+
49
+ newData = new Float32Array( data.length );
50
+ for ( const i in data ) {
51
+
52
+ newData[ i ] = DataUtils.fromHalfFloat( data[ i ] );
53
+
54
+ }
55
+
56
+ map.image.data = newData;
57
+ map.type = FloatType;
58
+
59
+ }
60
+
61
+ // remove any y flipping for cdf computation
62
+ if ( map.flipY ) {
63
+
64
+ const ogData = newData;
65
+ newData = newData.slice();
66
+ for ( let y = 0; y < height; y ++ ) {
67
+
68
+ for ( let x = 0; x < width; x ++ ) {
69
+
70
+ const newY = height - y - 1;
71
+ const ogIndex = 4 * ( y * width + x );
72
+ const newIndex = 4 * ( newY * width + x );
73
+
74
+ newData[ newIndex + 0 ] = ogData[ ogIndex + 0 ];
75
+ newData[ newIndex + 1 ] = ogData[ ogIndex + 1 ];
76
+ newData[ newIndex + 2 ] = ogData[ ogIndex + 2 ];
77
+ newData[ newIndex + 3 ] = ogData[ ogIndex + 3 ];
78
+
79
+ }
80
+
81
+ }
82
+
83
+ map.flipY = false;
84
+ map.image.data = newData;
85
+
86
+ }
87
+
88
+ return map;
89
+
90
+ }
91
+
92
+ export class EquirectHdrInfoUniform {
93
+
94
+ constructor() {
95
+
96
+ // Stores a map of [0, 1] value -> cumulative importance row & pdf
97
+ // used to sampling a random value to a relevant row to sample from
98
+ const marginalWeights = new DataTexture();
99
+ marginalWeights.type = FloatType;
100
+ marginalWeights.format = RedFormat;
101
+ marginalWeights.minFilter = LinearFilter;
102
+ marginalWeights.magFilter = LinearFilter;
103
+ marginalWeights.generateMipmaps = false;
104
+
105
+ // Stores a map of [0, 1] value -> cumulative importance column & pdf
106
+ // used to sampling a random value to a relevant pixel to sample from
107
+ const conditionalWeights = new DataTexture();
108
+ conditionalWeights.type = FloatType;
109
+ conditionalWeights.format = RedFormat;
110
+ conditionalWeights.minFilter = LinearFilter;
111
+ conditionalWeights.magFilter = LinearFilter;
112
+ conditionalWeights.generateMipmaps = false;
113
+
114
+ // store the total sum in a 1x1 tex since some android mobile devices have issues
115
+ // storing large values in structs.
116
+ const totalSumTex = new DataTexture();
117
+ totalSumTex.type = FloatType;
118
+ totalSumTex.format = RedFormat;
119
+ totalSumTex.minFilter = LinearFilter;
120
+ totalSumTex.magFilter = LinearFilter;
121
+ totalSumTex.generateMipmaps = false;
122
+
123
+ this.marginalWeights = marginalWeights;
124
+ this.conditionalWeights = conditionalWeights;
125
+ this.totalSum = totalSumTex;
126
+ this.map = null;
127
+
128
+ }
129
+
130
+ dispose() {
131
+
132
+ this.marginalWeights.dispose();
133
+ this.conditionalWeights.dispose();
134
+ this.totalSum.dispose();
135
+ if ( this.map ) this.map.dispose();
136
+
137
+ }
138
+
139
+ updateFrom( hdr ) {
140
+
141
+ // https://github.com/knightcrawler25/GLSL-PathTracer/blob/3c6fd9b6b3da47cd50c527eeb45845eef06c55c3/src/loaders/hdrloader.cpp
142
+ // https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
143
+ const map = preprocessEnvMap( hdr );
144
+ map.wrapS = RepeatWrapping;
145
+ map.wrapT = RepeatWrapping;
146
+
147
+ const { width, height, data } = map.image;
148
+
149
+ // "conditional" = "pixel relative to row pixels sum"
150
+ // "marginal" = "row relative to row sum"
151
+
152
+ // track the importance of any given pixel in the image by tracking its weight relative to other pixels in the image
153
+ const pdfConditional = new Float32Array( width * height );
154
+ const cdfConditional = new Float32Array( width * height );
155
+
156
+ const pdfMarginal = new Float32Array( height );
157
+ const cdfMarginal = new Float32Array( height );
158
+
159
+ let totalSumValue = 0.0;
160
+ let cumulativeWeightMarginal = 0.0;
161
+ for ( let y = 0; y < height; y ++ ) {
162
+
163
+ let cumulativeRowWeight = 0.0;
164
+ for ( let x = 0; x < width; x ++ ) {
165
+
166
+ const i = y * width + x;
167
+ const r = data[ 4 * i + 0 ];
168
+ const g = data[ 4 * i + 1 ];
169
+ const b = data[ 4 * i + 2 ];
170
+
171
+ // the probability of the pixel being selected in this row is the
172
+ // scale of the luminance relative to the rest of the pixels.
173
+ // TODO: this should also account for the solid angle of the pixel when sampling
174
+ const weight = colorToLuminance( r, g, b );
175
+ cumulativeRowWeight += weight;
176
+ totalSumValue += weight;
177
+
178
+ pdfConditional[ i ] = weight;
179
+ cdfConditional[ i ] = cumulativeRowWeight;
180
+
181
+ }
182
+
183
+ // can happen if the row is all black
184
+ if ( cumulativeRowWeight !== 0 ) {
185
+
186
+ // scale the pdf and cdf to [0.0, 1.0]
187
+ for ( let i = y * width, l = y * width + width; i < l; i ++ ) {
188
+
189
+ pdfConditional[ i ] /= cumulativeRowWeight;
190
+ cdfConditional[ i ] /= cumulativeRowWeight;
191
+
192
+ }
193
+
194
+ }
195
+
196
+ cumulativeWeightMarginal += cumulativeRowWeight;
197
+
198
+ // compute the marginal pdf and cdf along the height of the map.
199
+ pdfMarginal[ y ] = cumulativeRowWeight;
200
+ cdfMarginal[ y ] = cumulativeWeightMarginal;
201
+
202
+ }
203
+
204
+ // can happen if the texture is all black
205
+ if ( cumulativeWeightMarginal !== 0 ) {
206
+
207
+ // scale the marginal pdf and cdf to [0.0, 1.0]
208
+ for ( let i = 0, l = pdfMarginal.length; i < l; i ++ ) {
209
+
210
+ pdfMarginal[ i ] /= cumulativeWeightMarginal;
211
+ cdfMarginal[ i ] /= cumulativeWeightMarginal;
212
+
213
+ }
214
+
215
+ }
216
+
217
+ // compute a sorted index of distributions and the probabilities along them for both
218
+ // the marginal and conditional data. These will be used to sample with a random number
219
+ // to retrieve a uv value to sample in the environment map.
220
+ // These values continually increase so it's okay to interpolate between them.
221
+ const marginalDataArray = new Float32Array( height );
222
+ const conditionalDataArray = new Float32Array( width * height );
223
+
224
+ for ( let i = 0; i < height; i ++ ) {
225
+
226
+ const dist = ( i + 1 ) / height;
227
+ const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
228
+
229
+ marginalDataArray[ i ] = row / height;
230
+
231
+ }
232
+
233
+ for ( let y = 0; y < height; y ++ ) {
234
+
235
+ for ( let x = 0; x < width; x ++ ) {
236
+
237
+ const i = y * width + x;
238
+ const dist = ( x + 1 ) / width;
239
+ const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
240
+
241
+ conditionalDataArray[ i ] = col / width;
242
+
243
+ }
244
+
245
+ }
246
+
247
+ this.dispose();
248
+
249
+ const { marginalWeights, conditionalWeights, totalSum } = this;
250
+ marginalWeights.image = { width: height, height: 1, data: marginalDataArray };
251
+ marginalWeights.needsUpdate = true;
252
+
253
+ conditionalWeights.image = { width, height, data: conditionalDataArray };
254
+ conditionalWeights.needsUpdate = true;
255
+
256
+ totalSum.image = { width: 1, height: 1, data: new Float32Array( [ totalSumValue ] ) };
257
+ totalSum.needsUpdate = true;
258
+
259
+ this.map = map;
260
+
261
+ }
262
+
263
+ }
@@ -0,0 +1,83 @@
1
+ import { DataTexture, RGBAFormat, ClampToEdgeWrapping, FloatType, Vector3, Quaternion } from 'three';
2
+
3
+ const LIGHT_PIXELS = 4;
4
+
5
+ export class LightsTexture extends DataTexture {
6
+
7
+ constructor() {
8
+
9
+ super( new Float32Array( 4 ), 1, 1 );
10
+
11
+ this.format = RGBAFormat;
12
+ this.type = FloatType;
13
+ this.wrapS = ClampToEdgeWrapping;
14
+ this.wrapT = ClampToEdgeWrapping;
15
+ this.generateMipmaps = false;
16
+
17
+ }
18
+
19
+ updateFrom( lights ) {
20
+
21
+ let index = 0;
22
+ const pixelCount = lights.length * LIGHT_PIXELS;
23
+ const dimension = Math.ceil( Math.sqrt( pixelCount ) );
24
+
25
+ if ( this.image.width !== dimension ) {
26
+
27
+ this.dispose();
28
+
29
+ this.image.data = new Float32Array( dimension * dimension * 4 );
30
+ this.image.width = dimension;
31
+ this.image.height = dimension;
32
+
33
+ }
34
+
35
+ const floatArray = this.image.data;
36
+
37
+ const u = new Vector3();
38
+ const v = new Vector3();
39
+ const worldQuaternion = new Quaternion();
40
+
41
+ for ( let i = 0, l = lights.length; i < l; i ++ ) {
42
+
43
+ const l = lights[ i ];
44
+
45
+ // position
46
+ l.getWorldPosition( v );
47
+ floatArray[ index ++ ] = v.x;
48
+ floatArray[ index ++ ] = v.y;
49
+ floatArray[ index ++ ] = v.z;
50
+ index ++;
51
+
52
+ // color
53
+ floatArray[ index ++ ] = l.color.r;
54
+ floatArray[ index ++ ] = l.color.g;
55
+ floatArray[ index ++ ] = l.color.b;
56
+
57
+ // intensity
58
+ floatArray[ index ++ ] = l.intensity;
59
+
60
+ // u vector
61
+ l.getWorldQuaternion( worldQuaternion );
62
+ u.set( l.width, 0, 0 ).applyQuaternion( worldQuaternion );
63
+ floatArray[ index ++ ] = u.x;
64
+ floatArray[ index ++ ] = u.y;
65
+ floatArray[ index ++ ] = u.z;
66
+ index ++;
67
+
68
+ // v vector
69
+ v.set( 0, l.height, 0 ).applyQuaternion( worldQuaternion );
70
+ floatArray[ index ++ ] = v.x;
71
+ floatArray[ index ++ ] = v.y;
72
+ floatArray[ index ++ ] = v.z;
73
+
74
+ // area
75
+ floatArray[ index ++ ] = u.cross( v ).length();
76
+
77
+ }
78
+
79
+ this.needsUpdate = true;
80
+
81
+ }
82
+
83
+ }
@@ -0,0 +1,319 @@
1
+ import { DataTexture, RGBAFormat, ClampToEdgeWrapping, FloatType, FrontSide, BackSide, DoubleSide } from 'three';
2
+
3
+ const MATERIAL_PIXELS = 26;
4
+ const MATERIAL_STRIDE = MATERIAL_PIXELS * 4;
5
+
6
+ const SIDE_OFFSET = 7 * 4 + 1;
7
+ const MATTE_OFFSET = 7 * 4 + 2;
8
+ const SHADOW_OFFSET = 7 * 4 + 3;
9
+
10
+ export class MaterialsTexture extends DataTexture {
11
+
12
+ constructor() {
13
+
14
+ super( new Float32Array( 4 ), 1, 1 );
15
+
16
+ this.format = RGBAFormat;
17
+ this.type = FloatType;
18
+ this.wrapS = ClampToEdgeWrapping;
19
+ this.wrapT = ClampToEdgeWrapping;
20
+ this.generateMipmaps = false;
21
+ this.threeCompatibilityTransforms = false;
22
+
23
+ }
24
+
25
+ setCastShadow( materialIndex, cast ) {
26
+
27
+ // invert the shadow value so we default to "true" when initializing a material
28
+ const array = this.image.data;
29
+ const index = materialIndex * MATERIAL_STRIDE + SHADOW_OFFSET;
30
+ array[ index ] = ! cast ? 1 : 0;
31
+
32
+ }
33
+
34
+ getCastShadow( materialIndex ) {
35
+
36
+ const array = this.image.data;
37
+ const index = materialIndex * MATERIAL_STRIDE + SHADOW_OFFSET;
38
+ return ! Boolean( array[ index ] );
39
+
40
+ }
41
+
42
+ setSide( materialIndex, side ) {
43
+
44
+ const array = this.image.data;
45
+ const index = materialIndex * MATERIAL_STRIDE + SIDE_OFFSET;
46
+ switch ( side ) {
47
+
48
+ case FrontSide:
49
+ array[ index ] = 1;
50
+ break;
51
+ case BackSide:
52
+ array[ index ] = - 1;
53
+ break;
54
+ case DoubleSide:
55
+ array[ index ] = 0;
56
+ break;
57
+
58
+ }
59
+
60
+ }
61
+
62
+ getSide( materialIndex ) {
63
+
64
+ const array = this.image.data;
65
+ const index = materialIndex * MATERIAL_STRIDE + SIDE_OFFSET;
66
+ switch ( array[ index ] ) {
67
+
68
+ case 0:
69
+ return DoubleSide;
70
+ case 1:
71
+ return FrontSide;
72
+ case - 1:
73
+ return BackSide;
74
+
75
+ }
76
+
77
+ return 0;
78
+
79
+ }
80
+
81
+ setMatte( materialIndex, matte ) {
82
+
83
+ const array = this.image.data;
84
+ const index = materialIndex * MATERIAL_STRIDE + MATTE_OFFSET;
85
+ array[ index ] = matte ? 1 : 0;
86
+
87
+ }
88
+
89
+ getMatte( materialIndex ) {
90
+
91
+ const array = this.image.data;
92
+ const index = materialIndex * MATERIAL_STRIDE + MATTE_OFFSET;
93
+ return Boolean( array[ index ] );
94
+
95
+ }
96
+
97
+ updateFrom( materials, textures ) {
98
+
99
+ function getTexture( material, key, def = - 1 ) {
100
+
101
+ return key in material ? textures.indexOf( material[ key ] ) : def;
102
+
103
+ }
104
+
105
+ function getField( material, key, def ) {
106
+
107
+ return key in material ? material[ key ] : def;
108
+
109
+ }
110
+
111
+ function getUVTransformTexture( material ) {
112
+
113
+ // https://github.com/mrdoob/three.js/blob/f3a832e637c98a404c64dae8174625958455e038/src/renderers/webgl/WebGLMaterials.js#L204-L306
114
+ // https://threejs.org/docs/#api/en/textures/Texture.offset
115
+ // fallback order of textures to use as a common uv transform
116
+ return material.map ||
117
+ material.specularMap ||
118
+ material.displacementMap ||
119
+ material.normalMap ||
120
+ material.bumpMap ||
121
+ material.roughnessMap ||
122
+ material.metalnessMap ||
123
+ material.alphaMap ||
124
+ material.emissiveMap ||
125
+ material.clearcoatMap ||
126
+ material.clearcoatNormalMap ||
127
+ material.clearcoatRoughnessMap ||
128
+ material.iridescenceMap ||
129
+ material.iridescenceThicknessMap ||
130
+ material.specularIntensityMap ||
131
+ material.specularColorMap ||
132
+ material.transmissionMap ||
133
+ material.thicknessMap ||
134
+ material.sheenColorMap ||
135
+ material.sheenRoughnessMap ||
136
+ null;
137
+
138
+ }
139
+
140
+ function writeTextureMatrixToArray( material, textureKey, array, offset ) {
141
+
142
+ let texture;
143
+ if ( threeCompatibilityTransforms ) {
144
+
145
+ texture = getUVTransformTexture( material );
146
+
147
+ } else {
148
+
149
+ texture = material[ textureKey ] && material[ textureKey ].isTexture ? material[ textureKey ] : null;
150
+
151
+ }
152
+
153
+ // check if texture exists
154
+ if ( texture ) {
155
+
156
+ const elements = texture.matrix.elements;
157
+
158
+ let i = 0;
159
+
160
+ // first row
161
+ array[ offset + i ++ ] = elements[ 0 ];
162
+ array[ offset + i ++ ] = elements[ 3 ];
163
+ array[ offset + i ++ ] = elements[ 6 ];
164
+ i ++;
165
+
166
+ // second row
167
+ array[ offset + i ++ ] = elements[ 1 ];
168
+ array[ offset + i ++ ] = elements[ 4 ];
169
+ array[ offset + i ++ ] = elements[ 7 ];
170
+ i ++;
171
+
172
+ }
173
+
174
+ return 8;
175
+
176
+ }
177
+
178
+ let index = 0;
179
+ const pixelCount = materials.length * MATERIAL_PIXELS;
180
+ const dimension = Math.ceil( Math.sqrt( pixelCount ) );
181
+ const { threeCompatibilityTransforms, image } = this;
182
+
183
+ if ( image.width !== dimension ) {
184
+
185
+ this.dispose();
186
+
187
+ image.data = new Float32Array( dimension * dimension * 4 );
188
+ image.width = dimension;
189
+ image.height = dimension;
190
+
191
+ }
192
+
193
+ const floatArray = image.data;
194
+
195
+ // on some devices (Google Pixel 6) the "floatBitsToInt" function does not work correctly so we
196
+ // can't encode texture ids that way.
197
+ // const intArray = new Int32Array( floatArray.buffer );
198
+
199
+ for ( let i = 0, l = materials.length; i < l; i ++ ) {
200
+
201
+ const m = materials[ i ];
202
+
203
+ // color
204
+ floatArray[ index ++ ] = m.color.r;
205
+ floatArray[ index ++ ] = m.color.g;
206
+ floatArray[ index ++ ] = m.color.b;
207
+ floatArray[ index ++ ] = getTexture( m, 'map' );
208
+
209
+ // metalness & roughness
210
+ floatArray[ index ++ ] = getField( m, 'metalness', 0.0 );
211
+ floatArray[ index ++ ] = textures.indexOf( m.metalnessMap );
212
+ floatArray[ index ++ ] = getField( m, 'roughness', 0.0 );
213
+ floatArray[ index ++ ] = textures.indexOf( m.roughnessMap );
214
+
215
+ // transmission & emissiveIntensity
216
+ floatArray[ index ++ ] = getField( m, 'ior', 1.0 );
217
+ floatArray[ index ++ ] = getField( m, 'transmission', 0.0 );
218
+ floatArray[ index ++ ] = getTexture( m, 'transmissionMap' );
219
+ floatArray[ index ++ ] = getField( m, 'emissiveIntensity', 0.0 );
220
+
221
+ // emission
222
+ if ( 'emissive' in m ) {
223
+
224
+ floatArray[ index ++ ] = m.emissive.r;
225
+ floatArray[ index ++ ] = m.emissive.g;
226
+ floatArray[ index ++ ] = m.emissive.b;
227
+
228
+ } else {
229
+
230
+ floatArray[ index ++ ] = 0.0;
231
+ floatArray[ index ++ ] = 0.0;
232
+ floatArray[ index ++ ] = 0.0;
233
+
234
+ }
235
+
236
+ floatArray[ index ++ ] = getTexture( m, 'emissiveMap' );
237
+
238
+ // normals
239
+ floatArray[ index ++ ] = getTexture( m, 'normalMap' );
240
+ if ( 'normalScale' in m ) {
241
+
242
+ floatArray[ index ++ ] = m.normalScale.x;
243
+ floatArray[ index ++ ] = m.normalScale.y;
244
+
245
+ } else {
246
+
247
+ floatArray[ index ++ ] = 1;
248
+ floatArray[ index ++ ] = 1;
249
+
250
+ }
251
+
252
+ // clearcoat
253
+ floatArray[ index ++ ] = getField( m, 'clearcoat', 0.0 );
254
+ floatArray[ index ++ ] = getTexture( m, 'clearcoatMap' );
255
+
256
+ // clearcoatRoughness
257
+ floatArray[ index ++ ] = getField( m, 'clearcoatRoughness', 0.0 );
258
+ floatArray[ index ++ ] = getTexture( m, 'clearcoatRoughnessMap' );
259
+
260
+ // clearcoatNormal
261
+ floatArray[ index ++ ] = getTexture( m, 'clearcoatNormalMap' );
262
+
263
+ if ( 'clearcoatNormalScale' in m ) {
264
+
265
+ floatArray[ index ++ ] = m.clearcoatNormalScale.x;
266
+ floatArray[ index ++ ] = m.clearcoatNormalScale.y;
267
+
268
+ } else {
269
+
270
+ floatArray[ index ++ ] = 1;
271
+ floatArray[ index ++ ] = 1;
272
+
273
+ }
274
+
275
+ // alphaMap
276
+ floatArray[ index ++ ] = getTexture( m, 'alphaMap' );
277
+
278
+ // side & matte
279
+ floatArray[ index ++ ] = m.opacity;
280
+ floatArray[ index ++ ] = m.alphaTest;
281
+ index ++; // side
282
+ index ++; // matte
283
+
284
+ index ++; // shadow
285
+
286
+ // map transform
287
+ index += writeTextureMatrixToArray( m, 'map', floatArray, index );
288
+
289
+ // metalnessMap transform
290
+ index += writeTextureMatrixToArray( m, 'metalnessMap', floatArray, index );
291
+
292
+ // roughnessMap transform
293
+ index += writeTextureMatrixToArray( m, 'roughnessMap', floatArray, index );
294
+
295
+ // transmissionMap transform
296
+ index += writeTextureMatrixToArray( m, 'transmissionMap', floatArray, index );
297
+
298
+ // emissiveMap transform
299
+ index += writeTextureMatrixToArray( m, 'emissiveMap', floatArray, index );
300
+
301
+ // normalMap transform
302
+ index += writeTextureMatrixToArray( m, 'normalMap', floatArray, index );
303
+
304
+ // clearcoatMap transform
305
+ index += writeTextureMatrixToArray( m, 'clearcoatMap', floatArray, index );
306
+
307
+ // clearcoatNormalMap transform
308
+ index += writeTextureMatrixToArray( m, 'clearcoatNormalMap', floatArray, index );
309
+
310
+ // clearcoatRoughnessMap transform
311
+ index += writeTextureMatrixToArray( m, 'clearcoatRoughnessMap', floatArray, index );
312
+
313
+ }
314
+
315
+ this.needsUpdate = true;
316
+
317
+ }
318
+
319
+ }