three-gpu-pathtracer 0.0.8 → 0.0.10

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.
@@ -0,0 +1,256 @@
1
+ // References
2
+ // - https://jcgt.org/published/0009/04/01/
3
+ // - Code from https://www.shadertoy.com/view/WtGyDm
4
+
5
+ // functions to generate multi-dimensions variables of the same functions
6
+ // to support 1, 2, 3, and 4 dimensional sobol sampling.
7
+ function generateSobolFunctionVariants( dim = 1 ) {
8
+
9
+ let type = 'uint';
10
+ if ( dim > 1 ) {
11
+
12
+ type = 'uvec' + dim;
13
+
14
+ }
15
+
16
+ return /* glsl */`
17
+ ${ type } sobolReverseBits( ${ type } x ) {
18
+
19
+ x = ( ( ( x & 0xaaaaaaaau ) >> 1 ) | ( ( x & 0x55555555u ) << 1 ) );
20
+ x = ( ( ( x & 0xccccccccu ) >> 2 ) | ( ( x & 0x33333333u ) << 2 ) );
21
+ x = ( ( ( x & 0xf0f0f0f0u ) >> 4 ) | ( ( x & 0x0f0f0f0fu ) << 4 ) );
22
+ x = ( ( ( x & 0xff00ff00u ) >> 8 ) | ( ( x & 0x00ff00ffu ) << 8 ) );
23
+ return ( ( x >> 16 ) | ( x << 16 ) );
24
+
25
+ }
26
+
27
+ ${ type } sobolHashCombine( uint seed, ${ type } v ) {
28
+
29
+ return seed ^ ( v + ${ type }( ( seed << 6 ) + ( seed >> 2 ) ) );
30
+
31
+ }
32
+
33
+ ${ type } sobolLaineKarrasPermutation( ${ type } x, ${ type } seed ) {
34
+
35
+ x += seed;
36
+ x ^= x * 0x6c50b47cu;
37
+ x ^= x * 0xb82f1e52u;
38
+ x ^= x * 0xc7afe638u;
39
+ x ^= x * 0x8d22f6e6u;
40
+ return x;
41
+
42
+ }
43
+
44
+ ${ type } nestedUniformScrambleBase2( ${ type } x, ${ type } seed ) {
45
+
46
+ x = sobolLaineKarrasPermutation( x, seed );
47
+ x = sobolReverseBits( x );
48
+ return x;
49
+
50
+ }
51
+ `;
52
+
53
+ }
54
+
55
+ function generateSobolSampleFunctions( dim = 1 ) {
56
+
57
+ let utype = 'uint';
58
+ let vtype = 'float';
59
+ let num = '';
60
+ let components = '.r';
61
+ let combineValues = '1u';
62
+ if ( dim > 1 ) {
63
+
64
+ utype = 'uvec' + dim;
65
+ vtype = 'vec' + dim;
66
+ num = dim + '';
67
+ if ( dim === 2 ) {
68
+
69
+ components = '.rg';
70
+ combineValues = 'uvec2( 1u, 2u )';
71
+
72
+ } else if ( dim === 3 ) {
73
+
74
+ components = '.rgb';
75
+ combineValues = 'uvec3( 1u, 2u, 3u )';
76
+
77
+ } else {
78
+
79
+ components = '';
80
+ combineValues = 'uvec4( 1u, 2u, 3u, 4u )';
81
+
82
+ }
83
+
84
+ }
85
+
86
+ return /* glsl */`
87
+
88
+ ${ vtype } sobol${ num }( int effect ) {
89
+
90
+ uint seed = sobolGetSeed( sobolBounceIndex, uint( effect ) );
91
+ uint index = sobolPathIndex;
92
+
93
+ uint shuffle_seed = sobolHashCombine( seed, 0u );
94
+ uint shuffled_index = nestedUniformScrambleBase2( sobolReverseBits( index ), shuffle_seed );
95
+ ${ vtype } sobol_pt = sobolGetTexturePoint( shuffled_index )${ components };
96
+ ${ utype } result = ${ utype }( sobol_pt * 16777216.0 );
97
+
98
+ ${ utype } seed2 = sobolHashCombine( seed, ${ combineValues } );
99
+ result = nestedUniformScrambleBase2( result, seed2 );
100
+
101
+ return SOBOL_FACTOR * ${ vtype }( result >> 8 );
102
+
103
+ }
104
+ `;
105
+
106
+ }
107
+
108
+ export const shaderSobolCommon = /* glsl */`
109
+
110
+ // Utils
111
+ const float SOBOL_FACTOR = 1.0 / 16777216.0;
112
+ const uint SOBOL_MAX_POINTS = 256u * 256u;
113
+
114
+ ${ generateSobolFunctionVariants( 1 ) }
115
+ ${ generateSobolFunctionVariants( 2 ) }
116
+ ${ generateSobolFunctionVariants( 3 ) }
117
+ ${ generateSobolFunctionVariants( 4 ) }
118
+
119
+ uint sobolHash( uint x ) {
120
+
121
+ // finalizer from murmurhash3
122
+ x ^= x >> 16;
123
+ x *= 0x85ebca6bu;
124
+ x ^= x >> 13;
125
+ x *= 0xc2b2ae35u;
126
+ x ^= x >> 16;
127
+ return x;
128
+
129
+ }
130
+
131
+ `;
132
+
133
+ export const shaderSobolGeneration = /* glsl */`
134
+
135
+ const uint SOBOL_DIRECTIONS_1[ 32 ] = uint[ 32 ](
136
+ 0x80000000u, 0xc0000000u, 0xa0000000u, 0xf0000000u,
137
+ 0x88000000u, 0xcc000000u, 0xaa000000u, 0xff000000u,
138
+ 0x80800000u, 0xc0c00000u, 0xa0a00000u, 0xf0f00000u,
139
+ 0x88880000u, 0xcccc0000u, 0xaaaa0000u, 0xffff0000u,
140
+ 0x80008000u, 0xc000c000u, 0xa000a000u, 0xf000f000u,
141
+ 0x88008800u, 0xcc00cc00u, 0xaa00aa00u, 0xff00ff00u,
142
+ 0x80808080u, 0xc0c0c0c0u, 0xa0a0a0a0u, 0xf0f0f0f0u,
143
+ 0x88888888u, 0xccccccccu, 0xaaaaaaaau, 0xffffffffu
144
+ );
145
+
146
+ const uint SOBOL_DIRECTIONS_2[ 32 ] = uint[ 32 ](
147
+ 0x80000000u, 0xc0000000u, 0x60000000u, 0x90000000u,
148
+ 0xe8000000u, 0x5c000000u, 0x8e000000u, 0xc5000000u,
149
+ 0x68800000u, 0x9cc00000u, 0xee600000u, 0x55900000u,
150
+ 0x80680000u, 0xc09c0000u, 0x60ee0000u, 0x90550000u,
151
+ 0xe8808000u, 0x5cc0c000u, 0x8e606000u, 0xc5909000u,
152
+ 0x6868e800u, 0x9c9c5c00u, 0xeeee8e00u, 0x5555c500u,
153
+ 0x8000e880u, 0xc0005cc0u, 0x60008e60u, 0x9000c590u,
154
+ 0xe8006868u, 0x5c009c9cu, 0x8e00eeeeu, 0xc5005555u
155
+ );
156
+
157
+ const uint SOBOL_DIRECTIONS_3[ 32 ] = uint[ 32 ](
158
+ 0x80000000u, 0xc0000000u, 0x20000000u, 0x50000000u,
159
+ 0xf8000000u, 0x74000000u, 0xa2000000u, 0x93000000u,
160
+ 0xd8800000u, 0x25400000u, 0x59e00000u, 0xe6d00000u,
161
+ 0x78080000u, 0xb40c0000u, 0x82020000u, 0xc3050000u,
162
+ 0x208f8000u, 0x51474000u, 0xfbea2000u, 0x75d93000u,
163
+ 0xa0858800u, 0x914e5400u, 0xdbe79e00u, 0x25db6d00u,
164
+ 0x58800080u, 0xe54000c0u, 0x79e00020u, 0xb6d00050u,
165
+ 0x800800f8u, 0xc00c0074u, 0x200200a2u, 0x50050093u
166
+ );
167
+
168
+ const uint SOBOL_DIRECTIONS_4[ 32 ] = uint[ 32 ](
169
+ 0x80000000u, 0x40000000u, 0x20000000u, 0xb0000000u,
170
+ 0xf8000000u, 0xdc000000u, 0x7a000000u, 0x9d000000u,
171
+ 0x5a800000u, 0x2fc00000u, 0xa1600000u, 0xf0b00000u,
172
+ 0xda880000u, 0x6fc40000u, 0x81620000u, 0x40bb0000u,
173
+ 0x22878000u, 0xb3c9c000u, 0xfb65a000u, 0xddb2d000u,
174
+ 0x78022800u, 0x9c0b3c00u, 0x5a0fb600u, 0x2d0ddb00u,
175
+ 0xa2878080u, 0xf3c9c040u, 0xdb65a020u, 0x6db2d0b0u,
176
+ 0x800228f8u, 0x400b3cdcu, 0x200fb67au, 0xb00ddb9du
177
+ );
178
+
179
+ uint getMaskedSobol( uint index, uint directions[ 32 ] ) {
180
+
181
+ uint X = 0u;
182
+ for ( int bit = 0; bit < 32; bit ++ ) {
183
+
184
+ uint mask = ( index >> bit ) & 1u;
185
+ X ^= mask * directions[ bit ];
186
+
187
+ }
188
+ return X;
189
+
190
+ }
191
+
192
+ vec4 generateSobolPoint( uint index ) {
193
+
194
+ if ( index >= SOBOL_MAX_POINTS ) {
195
+
196
+ return vec4( 0.0 );
197
+
198
+ }
199
+
200
+ // NOTEL this sobol "direction" is also available but we can't write out 5 components
201
+ // uint x = index & 0x00ffffffu;
202
+ uint x = sobolReverseBits( getMaskedSobol( index, SOBOL_DIRECTIONS_1 ) ) & 0x00ffffffu;
203
+ uint y = sobolReverseBits( getMaskedSobol( index, SOBOL_DIRECTIONS_2 ) ) & 0x00ffffffu;
204
+ uint z = sobolReverseBits( getMaskedSobol( index, SOBOL_DIRECTIONS_3 ) ) & 0x00ffffffu;
205
+ uint w = sobolReverseBits( getMaskedSobol( index, SOBOL_DIRECTIONS_4 ) ) & 0x00ffffffu;
206
+
207
+ return vec4( x, y, z, w ) * SOBOL_FACTOR;
208
+
209
+ }
210
+
211
+ `;
212
+
213
+ export const shaderSobolSampling = /* glsl */`
214
+
215
+ // Seeds
216
+ uniform sampler2D sobolTexture;
217
+ uint sobolPixelIndex;
218
+ uint sobolPathIndex;
219
+ uint sobolBounceIndex;
220
+
221
+ uint sobolGetSeed( uint bounce, uint effect ) {
222
+
223
+ return sobolHash(
224
+ sobolHashCombine(
225
+ sobolHashCombine(
226
+ sobolHash( bounce ),
227
+ sobolPixelIndex
228
+ ),
229
+ effect
230
+ )
231
+ );
232
+
233
+ }
234
+
235
+ vec4 sobolGetTexturePoint( uint index ) {
236
+
237
+ if ( index >= SOBOL_MAX_POINTS ) {
238
+
239
+ index = index % SOBOL_MAX_POINTS;
240
+
241
+ }
242
+
243
+ uvec2 dim = uvec2( textureSize( sobolTexture, 0 ).xy );
244
+ uint y = index / dim.x;
245
+ uint x = index - y * dim.x;
246
+ vec2 uv = vec2( x, y ) / vec2( dim );
247
+ return texture( sobolTexture, uv );
248
+
249
+ }
250
+
251
+ ${ generateSobolSampleFunctions( 1 ) }
252
+ ${ generateSobolSampleFunctions( 2 ) }
253
+ ${ generateSobolSampleFunctions( 3 ) }
254
+ ${ generateSobolSampleFunctions( 4 ) }
255
+
256
+ `;
@@ -82,6 +82,7 @@ export const shaderMaterialStructs = /* glsl */ `
82
82
  int sheenRoughnessMap;
83
83
 
84
84
  bool vertexColors;
85
+ bool flatShading;
85
86
  bool transparent;
86
87
 
87
88
  mat3 mapTransform;
@@ -194,7 +195,8 @@ export const shaderMaterialStructs = /* glsl */ `
194
195
 
195
196
  m.matte = bool( s14.r );
196
197
  m.castShadow = ! bool( s14.g );
197
- m.vertexColors = bool( s14.b );
198
+ m.vertexColors = bool( int( s14.b ) & 1 );
199
+ m.flatShading = bool( int( s14.b ) & 2 );
198
200
  m.transparent = bool( s14.a );
199
201
 
200
202
  uint firstTextureTransformIdx = i + 15u;
@@ -226,6 +228,8 @@ export const shaderLightStruct = /* glsl */ `
226
228
  #define RECT_AREA_LIGHT_TYPE 0
227
229
  #define CIRC_AREA_LIGHT_TYPE 1
228
230
  #define SPOT_LIGHT_TYPE 2
231
+ #define DIR_LIGHT_TYPE 3
232
+ #define POINT_LIGHT_TYPE 4
229
233
 
230
234
  struct LightsInfo {
231
235
 
@@ -277,7 +281,7 @@ export const shaderLightStruct = /* glsl */ `
277
281
  l.v = s3.rgb;
278
282
  l.area = s3.a;
279
283
 
280
- if ( l.type == SPOT_LIGHT_TYPE ) {
284
+ if ( l.type == SPOT_LIGHT_TYPE || l.type == POINT_LIGHT_TYPE ) {
281
285
 
282
286
  vec4 s4 = texelFetch1D( tex, i + 4u );
283
287
  vec4 s5 = texelFetch1D( tex, i + 5u );
@@ -131,74 +131,7 @@ export const shaderUtils = /* glsl */`
131
131
 
132
132
  }
133
133
 
134
- // https://www.shadertoy.com/view/wltcRS
135
- uvec4 s0;
136
-
137
- void rng_initialize(vec2 p, int frame) {
138
-
139
- // white noise seed
140
- s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
141
-
142
- }
143
-
144
- // https://www.pcg-random.org/
145
- void pcg4d( inout uvec4 v ) {
146
-
147
- v = v * 1664525u + 1013904223u;
148
- v.x += v.y * v.w;
149
- v.y += v.z * v.x;
150
- v.z += v.x * v.y;
151
- v.w += v.y * v.z;
152
- v = v ^ ( v >> 16u );
153
- v.x += v.y*v.w;
154
- v.y += v.z*v.x;
155
- v.z += v.x*v.y;
156
- v.w += v.y*v.z;
157
-
158
- }
159
-
160
- // returns [ 0, 1 ]
161
- float rand() {
162
-
163
- pcg4d(s0);
164
- return float( s0.x ) / float( 0xffffffffu );
165
-
166
- }
167
-
168
- vec2 rand2() {
169
-
170
- pcg4d( s0 );
171
- return vec2( s0.xy ) / float(0xffffffffu);
172
-
173
- }
174
-
175
- vec3 rand3() {
176
-
177
- pcg4d(s0);
178
- return vec3( s0.xyz ) / float( 0xffffffffu );
179
-
180
- }
181
-
182
- vec4 rand4() {
183
-
184
- pcg4d(s0);
185
- return vec4(s0)/float(0xffffffffu);
186
-
187
- }
188
-
189
- // https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
190
- vec3 randDirection() {
191
-
192
- vec2 r = rand2();
193
- float u = ( r.x - 0.5 ) * 2.0;
194
- float t = r.y * PI * 2.0;
195
- float f = sqrt( 1.0 - u * u );
196
-
197
- return vec3( f * cos( t ), f * sin( t ), u );
198
-
199
- }
200
-
201
- vec2 triangleSample( vec2 a, vec2 b, vec2 c ) {
134
+ vec2 sampleTriangle( vec2 a, vec2 b, vec2 c, vec2 r ) {
202
135
 
203
136
  // get the edges of the triangle and the diagonal across the
204
137
  // center of the parallelogram
@@ -206,8 +139,7 @@ export const shaderUtils = /* glsl */`
206
139
  vec2 e2 = c - b;
207
140
  vec2 diag = normalize( e1 + e2 );
208
141
 
209
- // pick a random point in the parallelogram
210
- vec2 r = rand2();
142
+ // pick the point in the parallelogram
211
143
  if ( r.x + r.y > 1.0 ) {
212
144
 
213
145
  r = vec2( 1.0 ) - r;
@@ -218,40 +150,48 @@ export const shaderUtils = /* glsl */`
218
150
 
219
151
  }
220
152
 
221
- // samples an aperture shape with the given number of sides. 0 means circle
222
- vec2 sampleAperture( int blades ) {
153
+ vec2 sampleCircle( vec2 uv ) {
223
154
 
224
- if ( blades == 0 ) {
155
+ float angle = 2.0 * PI * uv.x;
156
+ float radius = sqrt( uv.y );
157
+ return vec2( cos( angle ), sin( angle ) ) * radius;
225
158
 
226
- vec2 r = rand2();
227
- float angle = 2.0 * PI * r.x;
228
- float radius = sqrt( rand() );
229
- return vec2( cos( angle ), sin( angle ) ) * radius;
159
+ }
230
160
 
231
- } else {
161
+ vec3 sampleSphere( vec2 uv ) {
232
162
 
233
- blades = max( blades, 3 );
163
+ float u = ( uv.x - 0.5 ) * 2.0;
164
+ float t = uv.y * PI * 2.0;
165
+ float f = sqrt( 1.0 - u * u );
234
166
 
235
- vec3 r = rand3();
236
- float anglePerSegment = 2.0 * PI / float( blades );
237
- float segment = floor( float( blades ) * r.x );
167
+ return vec3( f * cos( t ), f * sin( t ), u );
238
168
 
239
- float angle1 = anglePerSegment * segment;
240
- float angle2 = angle1 + anglePerSegment;
241
- vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
242
- vec2 b = vec2( 0.0, 0.0 );
243
- vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
169
+ }
244
170
 
245
- return triangleSample( a, b, c );
171
+ vec2 sampleRegularNGon( int sides, vec3 uvw ) {
246
172
 
247
- }
173
+ sides = max( sides, 3 );
174
+
175
+ vec3 r = uvw;
176
+ float anglePerSegment = 2.0 * PI / float( sides );
177
+ float segment = floor( float( sides ) * r.x );
178
+
179
+ float angle1 = anglePerSegment * segment;
180
+ float angle2 = angle1 + anglePerSegment;
181
+ vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
182
+ vec2 b = vec2( 0.0, 0.0 );
183
+ vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
184
+
185
+ return sampleTriangle( a, b, c, r.yz );
248
186
 
249
187
  }
250
188
 
251
- float colorToLuminance( vec3 color ) {
189
+ // samples an aperture shape with the given number of sides. 0 means circle
190
+ vec2 sampleAperture( int blades, vec3 uvw ) {
252
191
 
253
- // https://en.wikipedia.org/wiki/Relative_luminance
254
- return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
192
+ return blades == 0 ?
193
+ sampleCircle( uvw.xy ) :
194
+ sampleRegularNGon( blades, uvw );
255
195
 
256
196
  }
257
197
 
@@ -332,6 +272,17 @@ export const shaderUtils = /* glsl */`
332
272
 
333
273
  }
334
274
 
275
+ vec2 rotateVector( vec2 v, float t ) {
276
+
277
+ float ac = cos( t );
278
+ float as = sin( t );
279
+ return vec2(
280
+ v.x * ac - v.y * as,
281
+ v.x * as + v.y * ac
282
+ );
283
+
284
+ }
285
+
335
286
  // Finds the point where the ray intersects the plane defined by u and v and checks if this point
336
287
  // falls in the bounds of the rectangle on that same plane.
337
288
  // Plane intersection: https://lousodrome.net/blog/light/2020/07/03/intersection-of-a-ray-and-a-plane/
@@ -400,4 +351,11 @@ export const shaderUtils = /* glsl */`
400
351
 
401
352
  }
402
353
 
354
+ // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
355
+ // erichlof/THREE.js-PathTracing-Renderer/
356
+ float tentFilter( float x ) {
357
+
358
+ return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
359
+
360
+ }
403
361
  `;
@@ -3,12 +3,11 @@ import { DataTexture, FloatType, RedFormat, LinearFilter, DataUtils, HalfFloatTy
3
3
  function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
4
4
 
5
5
  let lower = 0;
6
- let upper = count;
6
+ let upper = count - 1;
7
7
  while ( lower < upper ) {
8
8
 
9
9
  const mid = ~ ~ ( 0.5 * upper + 0.5 * lower );
10
10
 
11
-
12
11
  // check if the middle array value is above or below the target and shift
13
12
  // which half of the array we're looking at
14
13
  if ( array[ offset + mid ] < targetValue ) {
@@ -215,12 +214,13 @@ export class EquirectHdrInfoUniform {
215
214
  const marginalDataArray = new Float32Array( height );
216
215
  const conditionalDataArray = new Float32Array( width * height );
217
216
 
217
+ // we add a half texel offset so we're sampling the center of the pixel
218
218
  for ( let i = 0; i < height; i ++ ) {
219
219
 
220
220
  const dist = ( i + 1 ) / height;
221
221
  const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
222
222
 
223
- marginalDataArray[ i ] = row / height;
223
+ marginalDataArray[ i ] = ( row + 0.5 ) / height;
224
224
 
225
225
  }
226
226
 
@@ -232,7 +232,7 @@ export class EquirectHdrInfoUniform {
232
232
  const dist = ( x + 1 ) / width;
233
233
  const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
234
234
 
235
- conditionalDataArray[ i ] = col / width;
235
+ conditionalDataArray[ i ] = ( col + 0.5 ) / width;
236
236
 
237
237
  }
238
238
 
@@ -4,6 +4,8 @@ const LIGHT_PIXELS = 6;
4
4
  const RECT_AREA_LIGHT = 0;
5
5
  const CIRC_AREA_LIGHT = 1;
6
6
  const SPOT_LIGHT = 2;
7
+ const DIR_LIGHT = 3;
8
+ const POINT_LIGHT = 4;
7
9
  export class LightsInfoUniformStruct {
8
10
 
9
11
  constructor() {
@@ -62,8 +64,24 @@ export class LightsInfoUniformStruct {
62
64
 
63
65
  // type
64
66
  let type = RECT_AREA_LIGHT;
65
- if ( l.isRectAreaLight && l.isCircular ) type = CIRC_AREA_LIGHT;
66
- else if ( l.isSpotLight ) type = SPOT_LIGHT;
67
+ if ( l.isRectAreaLight && l.isCircular ) {
68
+
69
+ type = CIRC_AREA_LIGHT;
70
+
71
+ } else if ( l.isSpotLight ) {
72
+
73
+ type = SPOT_LIGHT;
74
+
75
+ } else if ( l.isDirectionalLight ) {
76
+
77
+ type = DIR_LIGHT;
78
+
79
+ } else if ( l.isPointLight ) {
80
+
81
+ type = POINT_LIGHT;
82
+
83
+ }
84
+
67
85
  floatArray[ baseIndex + ( index ++ ) ] = type;
68
86
 
69
87
  // sample 2
@@ -150,6 +168,33 @@ export class LightsInfoUniformStruct {
150
168
  // iesProfile
151
169
  floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
152
170
 
171
+ } else if ( l.isPointLight ) {
172
+
173
+ const worldPosition = l.getWorldPosition( u );
174
+ floatArray[ baseIndex + ( index ++ ) ] = worldPosition.x;
175
+ floatArray[ baseIndex + ( index ++ ) ] = worldPosition.y;
176
+ floatArray[ baseIndex + ( index ++ ) ] = worldPosition.z;
177
+ index ++;
178
+
179
+ // sample 4
180
+ index += 4;
181
+
182
+ // sample 5
183
+ index += 2;
184
+
185
+ floatArray[ baseIndex + ( index ++ ) ] = l.decay;
186
+ floatArray[ baseIndex + ( index ++ ) ] = l.distance;
187
+
188
+ } else if ( l.isDirectionalLight ) {
189
+
190
+ const worldPosition = l.getWorldPosition( u );
191
+ const targetPosition = l.target.getWorldPosition( v );
192
+
193
+ target.subVectors( worldPosition, targetPosition ).normalize();
194
+ floatArray[ baseIndex + ( index ++ ) ] = target.x;
195
+ floatArray[ baseIndex + ( index ++ ) ] = target.y;
196
+ floatArray[ baseIndex + ( index ++ ) ] = target.z;
197
+
153
198
  }
154
199
 
155
200
  }
@@ -1,5 +1,5 @@
1
1
  import { DataTexture, RGBAFormat, ClampToEdgeWrapping, FloatType, FrontSide, BackSide, DoubleSide } from 'three';
2
- import { reduceTexturesToUniqueSources } from './utils.js';
2
+ import { reduceTexturesToUniqueSources, getTextureHash } from './utils.js';
3
3
 
4
4
  const MATERIAL_PIXELS = 45;
5
5
  const MATERIAL_STRIDE = MATERIAL_PIXELS * 4;
@@ -61,8 +61,8 @@ export class MaterialsTexture extends DataTexture {
61
61
 
62
62
  if ( key in material && material[ key ] ) {
63
63
 
64
- const source = material[ key ].source;
65
- return uniqueTextures.findIndex( tex => tex.source === source );
64
+ const hash = getTextureHash( material[ key ] );
65
+ return uniqueTextureLookup[ hash ];
66
66
 
67
67
  } else {
68
68
 
@@ -152,6 +152,12 @@ export class MaterialsTexture extends DataTexture {
152
152
 
153
153
  // get the list of textures with unique sources
154
154
  const uniqueTextures = reduceTexturesToUniqueSources( textures );
155
+ const uniqueTextureLookup = {};
156
+ for ( let i = 0, l = uniqueTextures.length; i < l; i ++ ) {
157
+
158
+ uniqueTextureLookup[ getTextureHash( uniqueTextures[ i ] ) ] = i;
159
+
160
+ }
155
161
 
156
162
  if ( image.width !== dimension ) {
157
163
 
@@ -183,9 +189,9 @@ export class MaterialsTexture extends DataTexture {
183
189
  // sample 1
184
190
  // metalness & roughness
185
191
  floatArray[ index ++ ] = getField( m, 'metalness', 0.0 );
186
- floatArray[ index ++ ] = uniqueTextures.indexOf( m.metalnessMap );
192
+ floatArray[ index ++ ] = getTexture( m, 'metalnessMap' );
187
193
  floatArray[ index ++ ] = getField( m, 'roughness', 0.0 );
188
- floatArray[ index ++ ] = uniqueTextures.indexOf( m.roughnessMap );
194
+ floatArray[ index ++ ] = getTexture( m, 'roughnessMap' );
189
195
 
190
196
  // sample 2
191
197
  // transmission & emissiveIntensity
@@ -363,7 +369,7 @@ export class MaterialsTexture extends DataTexture {
363
369
  // sample 14
364
370
  index ++; // matte
365
371
  index ++; // shadow
366
- floatArray[ index ++ ] = Number( m.vertexColors ); // vertexColors
372
+ floatArray[ index ++ ] = Number( m.vertexColors ) | ( Number( m.flatShading ) << 1 ); // vertexColors & flatShading
367
373
  floatArray[ index ++ ] = Number( m.transparent ); // transparent
368
374
 
369
375
  // map transform 15
@@ -1,3 +1,11 @@
1
+ // we must hash the texture to determine uniqueness using the encoding, as well, because the
2
+ // when rendering each texture to the texture array they must have a consistent color space.
3
+ export function getTextureHash( t ) {
4
+
5
+ return `${ t.source.uuid }:${ t.encoding }`;
6
+
7
+ }
8
+
1
9
  // reduce the set of textures to just those with a unique source while retaining
2
10
  // the order of the textures.
3
11
  export function reduceTexturesToUniqueSources( textures ) {
@@ -7,9 +15,10 @@ export function reduceTexturesToUniqueSources( textures ) {
7
15
  for ( let i = 0, l = textures.length; i < l; i ++ ) {
8
16
 
9
17
  const tex = textures[ i ];
10
- if ( ! sourceSet.has( tex.source ) ) {
18
+ const hash = getTextureHash( tex );
19
+ if ( ! sourceSet.has( hash ) ) {
11
20
 
12
- sourceSet.add( tex.source );
21
+ sourceSet.add( hash );
13
22
  result.push( tex );
14
23
 
15
24
  }