three-gpu-pathtracer 0.0.24 → 0.0.26

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 (73) hide show
  1. package/README.md +53 -530
  2. package/build/index.module.js +262 -50
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +261 -48
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +29 -11
  7. package/src/core/WebGLPathTracer.js +8 -0
  8. package/src/core/utils/sceneUpdateUtils.js +8 -12
  9. package/src/detectors/PrecisionMaterial.js +9 -9
  10. package/src/index.js +1 -0
  11. package/src/objects/PhysicalSpotLight.js +2 -0
  12. package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
  13. package/src/textures/BlueNoiseTexture.js +6 -6
  14. package/src/textures/ProceduralEquirectTexture.js +8 -7
  15. package/src/textures/turquinMetal.png +0 -0
  16. package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
  17. package/src/uniforms/FloatAttributeTextureArray.js +11 -11
  18. package/src/uniforms/MaterialsTexture.js +9 -9
  19. package/src/webgpu/API.md +762 -0
  20. package/src/webgpu/AtlasTexture.js +471 -0
  21. package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
  22. package/src/webgpu/EquirectBackgroundInfo.js +97 -0
  23. package/src/webgpu/EquirectHdrInfoNode.js +151 -0
  24. package/src/webgpu/LightsInfoNode.js +224 -0
  25. package/src/webgpu/MegaKernelPathTracer.js +277 -0
  26. package/src/webgpu/PathTracerBackend.js +216 -0
  27. package/src/webgpu/TurquinTexture.js +169 -0
  28. package/src/webgpu/WaveFrontPathTracer.js +586 -0
  29. package/src/webgpu/WebGPUPathTracer.js +1302 -0
  30. package/src/webgpu/compute/ComputeKernel.js +92 -0
  31. package/src/webgpu/compute/CopyBufferKernel.js +40 -0
  32. package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
  33. package/src/webgpu/compute/SampleDebugKernel.js +51 -0
  34. package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
  35. package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
  36. package/src/webgpu/compute/ZeroOutKernel.js +31 -0
  37. package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
  38. package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
  39. package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
  40. package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
  41. package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
  42. package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
  43. package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
  44. package/src/webgpu/compute/wavefront/structs.js +155 -0
  45. package/src/webgpu/constants.js +62 -0
  46. package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
  47. package/src/webgpu/index.d.ts +176 -0
  48. package/src/webgpu/index.js +11 -0
  49. package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
  50. package/src/webgpu/materials/GraphMaterial.js +331 -0
  51. package/src/webgpu/materials/PathtracingMaterial.js +89 -0
  52. package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
  53. package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
  54. package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
  55. package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
  56. package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
  57. package/src/webgpu/nodes/eon.wgsl.js +262 -0
  58. package/src/webgpu/nodes/ggx.wgsl.js +190 -0
  59. package/src/webgpu/nodes/lights.wgsl.js +203 -0
  60. package/src/webgpu/nodes/material.wgsl.js +990 -0
  61. package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
  62. package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
  63. package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
  64. package/src/webgpu/nodes/random.wgsl.js +20 -0
  65. package/src/webgpu/nodes/reset.wgsl.js +21 -0
  66. package/src/webgpu/nodes/sampling.wgsl.js +193 -0
  67. package/src/webgpu/nodes/sheen.wgsl.js +148 -0
  68. package/src/webgpu/nodes/structs.wgsl.js +272 -0
  69. package/src/webgpu/nodes/utils.wgsl.js +366 -0
  70. package/src/webgpu/shims/ArrayCameraShim.js +74 -0
  71. package/src/webgpu/shims/EquirectCameraShim.js +37 -0
  72. package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
  73. package/src/webgpu/upscale/FSRUpscaler.js +108 -0
@@ -0,0 +1,1037 @@
1
+ import { BackSide, FrontSide, DoubleSide, BufferAttribute, BufferGeometry, StorageBufferAttribute, StructTypeNode, Vector4, SkinnedMesh, RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping, NearestFilter } from 'three/webgpu';
2
+ import { BVHComputeData, intersectRayTriangle, bvhNodeBoundsStruct, bvhNodeStruct, rayStruct, rayIntersectionResultStruct as intersectionResultStruct, wgslTagFn } from 'three-mesh-bvh/webgpu';
3
+ import { storage, float, texture, uniformArray, uint } from 'three/tsl';
4
+ import { SkinnedMeshBVH, MeshBVH, SAH } from 'three-mesh-bvh';
5
+ import { materialStruct } from './structs.wgsl.js';
6
+ import { getTextureHash } from '../../core/utils/sceneUpdateUtils.js';
7
+ import { sampleTexelFunc } from './utils.wgsl.js';
8
+ import { getSurfaceRecordFunc } from './material.wgsl.js';
9
+ import { AtlasTexture } from '../AtlasTexture.js';
10
+
11
+ const _colorVec = new Vector4();
12
+ const transformStruct = new StructTypeNode( {
13
+ matrixWorld: 'mat4x4f',
14
+ inverseMatrixWorld: 'mat4x4f',
15
+ visible: 'uint',
16
+ materialIndex: 'uint',
17
+ _alignment0: 'uint',
18
+ _alignment1: 'uint',
19
+ color: 'vec4f',
20
+ }, 'TransformStruct' );
21
+
22
+ // Pathtracer-specific version of the BVHComputeData tht includes material mapping, property structs
23
+ export class PathtracerBVHComputeData extends BVHComputeData {
24
+
25
+ constructor( bvh, options = {} ) {
26
+
27
+ // TODO: once supported we should use the appropriately-sized member sizes
28
+ super( bvh, {
29
+ attributes: {
30
+ position: 'vec4f',
31
+ normal: 'vec4f',
32
+ tangent: 'vec4f',
33
+ color: 'vec4f',
34
+ uv: 'vec4f',
35
+ },
36
+ ...options,
37
+ } );
38
+
39
+ this.structs.transform = transformStruct;
40
+ this.structs.material = materialStruct;
41
+ this.storage.materials = null;
42
+ this.materialsMap = new Map();
43
+ this.materials = [];
44
+ this.bvhMap = new Map();
45
+ this.textureAtlas = new AtlasTexture();
46
+
47
+ }
48
+
49
+ updateUvAttributesFromScene() {
50
+
51
+ const { attributes } = this;
52
+ const keys = new Set( [ 'color' ] );
53
+ for ( let i = 0; i < 8; i ++ ) {
54
+
55
+ const key = i === 0 ? 'uv' : 'uv' + i;
56
+ delete attributes[ key ];
57
+ keys.add( key );
58
+
59
+ }
60
+
61
+ this.getRootObject().traverse( c => {
62
+
63
+ if ( c.geometry ) {
64
+
65
+ for ( const key in c.geometry.attributes ) {
66
+
67
+ if ( keys.has( key ) ) {
68
+
69
+ attributes[ key ] = 'vec4f';
70
+
71
+ }
72
+
73
+ }
74
+
75
+ }
76
+
77
+ } );
78
+
79
+ }
80
+
81
+ updateUvSampleFunction() {
82
+
83
+ // TODO: eventually it may be best to pack the uvs into an array in the
84
+ // attribute struct so they can be sampled via array index but TSL structs
85
+ // make this difficult at the moment. It may break down when a uv channel
86
+ // unused, as well.
87
+ const { structs, fns } = this;
88
+
89
+ // generate the switch cases for the uv channels
90
+ const cases = [];
91
+ let fallback = null;
92
+ structs.attributes.membersLayout.forEach( ( { name } ) => {
93
+
94
+ if ( /^uv/.test( name ) ) {
95
+
96
+ const channel = name === 'uv' ? 0 : Number( name.replace( /^uv/, '' ) );
97
+ cases.push( /* wgsl */`
98
+ case ${ channel }u: {
99
+
100
+ return vertexData.${ name }.xy;
101
+
102
+ }
103
+ ` );
104
+
105
+ if ( fallback === null ) {
106
+
107
+ fallback = `vertexData.${ name }.xy`;
108
+
109
+ }
110
+
111
+ }
112
+
113
+ } );
114
+
115
+ fns.getUvFromChannel = wgslTagFn/* wgsl */`
116
+ fn getUvFromChannel( vertexData: ${ structs.attributes }, packed: i32 ) -> vec2f {
117
+
118
+ // the uv channel is packed into bits 23-25 of the "*Map" descriptor
119
+ let channel = u32( ( packed >> 23 ) & 0x7 );
120
+ switch ( channel ) {
121
+
122
+ ${ cases.join( '\n' ) }
123
+ default: {
124
+
125
+ return ${ fallback ?? 'vec2f( 0.0 )' };
126
+
127
+ }
128
+
129
+ }
130
+
131
+ }
132
+ `;
133
+
134
+ }
135
+
136
+ updateColorSampleFn() {
137
+
138
+ // create a function for retrieving the color from the vertex data instance. If the struct does not include
139
+ // color then return white.
140
+ const { structs, fns } = this;
141
+ const hasColor = Boolean( structs.attributes.membersLayout.find( ( { name } ) => name === 'color' ) );
142
+ if ( hasColor ) {
143
+
144
+ fns.getColor = wgslTagFn/* wgsl */`
145
+ fn getColor( vertexData: ${ structs.attributes } ) -> vec4f {
146
+
147
+ return vertexData.color;
148
+
149
+ }
150
+ `;
151
+
152
+ } else {
153
+
154
+ fns.getColor = wgslTagFn/* wgsl */`
155
+ fn getColor( vertexData: ${ structs.attributes } ) -> vec4f {
156
+
157
+ return vec4f( 1.0 );
158
+
159
+ }
160
+ `;
161
+
162
+ }
163
+
164
+ }
165
+
166
+ useTransparencyRaycastFn() {
167
+
168
+ const { textureAtlas, storage, fns } = this;
169
+ const textures = textureAtlas.texture;
170
+ const textureInfo = uniformArray( textureAtlas.textureInfo, 'uvec4' );
171
+
172
+ // build the single sampleTexel bound to this instance's textureInfo node
173
+ const sampleTexel = sampleTexelFunc( textureInfo, texture( textures ) );
174
+
175
+ // getSurfaceRecord shares the same sampleTexel, so the surface shading and
176
+ // the transparency raycast resolve to one textureInfo binding per pipeline
177
+ fns.getSurfaceRecord = getSurfaceRecordFunc( sampleTexel, fns.getUvFromChannel, fns.getColor );
178
+
179
+ // raycast first hit, bounded by the ray's "maxDist" - 0 means unbounded
180
+ const currentMaterialIndex = uint().toVar( 'bvh_materialIndex' );
181
+ const scratchRayScalar = float( 1.0 ).toVar( 'bvh_rayScalar' );
182
+ const baseOpacityScalar = float( 1.0 ).toVar( 'bvh_baseOpacity' );
183
+
184
+ fns.raycastFirstHit = this.getShapecastFn( {
185
+ name: 'raycastFirstHit',
186
+ shapeStruct: rayStruct,
187
+ resultStruct: intersectionResultStruct,
188
+
189
+ boundsOrderFn: wgslTagFn/* wgsl */`
190
+ fn getBoundsOrder( ray: ${ rayStruct }, splitAxis: u32, node: ${ bvhNodeStruct } ) -> bool {
191
+
192
+ return ray.direction[ splitAxis ] >= 0.0;
193
+
194
+ }
195
+ `,
196
+ intersectsBoundsFn: wgslTagFn/* wgsl */`
197
+ fn rayIntersectsBounds( ray: ${ rayStruct }, bounds: ${ bvhNodeBoundsStruct }, result: ptr<function, ${ intersectionResultStruct }> ) -> u32 {
198
+
199
+ // early-out if our object is completely transparent
200
+ if ( ${ baseOpacityScalar } == 0.0 ) {
201
+
202
+ return 0u;
203
+
204
+ }
205
+
206
+ let boundsMin = vec3( bounds.min[0], bounds.min[1], bounds.min[2] );
207
+ let boundsMax = vec3( bounds.max[0], bounds.max[1], bounds.max[2] );
208
+
209
+ let invDir = 1.0 / ray.direction;
210
+ let tMinPlane = ( boundsMin - ray.origin ) * invDir;
211
+ let tMaxPlane = ( boundsMax - ray.origin ) * invDir;
212
+
213
+ let tMinHit = min( tMinPlane, tMaxPlane );
214
+ let tMaxHit = max( tMinPlane, tMaxPlane );
215
+
216
+ let t0 = max( max( tMinHit.x, tMinHit.y ), tMinHit.z );
217
+ let t1 = min( min( tMaxHit.x, tMaxHit.y ), tMaxHit.z );
218
+
219
+ let dist = max( t0, 0.0 );
220
+ if ( t1 < dist ) {
221
+
222
+ return 0u;
223
+
224
+ } else if ( ray.maxDist > 0.0 && dist * ${ scratchRayScalar } >= ray.maxDist ) {
225
+
226
+ return 0u;
227
+
228
+ } else if ( result.didHit && dist * ${ scratchRayScalar } >= result.dist ) {
229
+
230
+ return 0u;
231
+
232
+ } else {
233
+
234
+ return 1u;
235
+
236
+ }
237
+
238
+ }
239
+
240
+ `,
241
+ intersectRangeFn: wgslTagFn/* wgsl */`
242
+ fn intersectRange( ray: ${ rayStruct }, offset: u32, count: u32, result: ptr<function, ${ intersectionResultStruct }> ) -> bool {
243
+
244
+ var didHit = false;
245
+ for ( var ti = offset; ti < offset + count; ti = ti + 1u ) {
246
+
247
+ let i0 = ${ storage.index }[ ti * 3u ];
248
+ let i1 = ${ storage.index }[ ti * 3u + 1u ];
249
+ let i2 = ${ storage.index }[ ti * 3u + 2u ];
250
+
251
+ let a = ${ storage.attributes }[ i0 ].position.xyz;
252
+ let b = ${ storage.attributes }[ i1 ].position.xyz;
253
+ let c = ${ storage.attributes }[ i2 ].position.xyz;
254
+
255
+ var triResult = ${ intersectRayTriangle }( ray, a, b, c, 0.0 );
256
+ triResult.dist *= ${ scratchRayScalar };
257
+ if ( triResult.didHit && ( ray.maxDist <= 0.0 || triResult.dist < ray.maxDist ) && ( ! result.didHit || triResult.dist < result.dist ) ) {
258
+
259
+ let material = ${ storage.materials }[ ${ currentMaterialIndex } ];
260
+
261
+ // TODO: if material is a transmissive volume we may need to assume double-sidedness
262
+ if ( material.side != 0 && triResult.side != material.side ) {
263
+
264
+ continue;
265
+
266
+ }
267
+
268
+ if ( material.transparent != 0 || material.alphaTest > 0.0 ) {
269
+
270
+ var opacity = ${ baseOpacityScalar };
271
+
272
+ // add support for vertex color opacity
273
+ if ( material.vertexColors == 1 ) {
274
+
275
+ let barycoord = triResult.barycoord;
276
+ let a = ${ fns.getColor }( ${ storage.attributes }[ i0 ] );
277
+ let b = ${ fns.getColor }( ${ storage.attributes }[ i1 ] );
278
+ let c = ${ fns.getColor }( ${ storage.attributes }[ i2 ] );
279
+ let col = barycoord.x * a + barycoord.y * b + barycoord.z * c;
280
+
281
+ opacity *= col.a;
282
+
283
+ }
284
+
285
+ // account for alpha component of albedo map
286
+ if ( material.map != - 1 ) {
287
+
288
+ let barycoord = triResult.barycoord;
289
+ let a = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i0 ], material.map );
290
+ let b = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i1 ], material.map );
291
+ let c = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i2 ], material.map );
292
+ let uv = barycoord.x * a + barycoord.y * b + barycoord.z * c;
293
+ let uvPrime = material.mapTransform * vec3f( uv, 1 );
294
+
295
+ opacity *= ${ sampleTexel }( uvPrime.xy, material.map, 0 ).a;
296
+
297
+ }
298
+
299
+ // account for green component of alpha map
300
+ if ( material.alphaMap != - 1 ) {
301
+
302
+ let barycoord = triResult.barycoord;
303
+ let a = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i0 ], material.alphaMap );
304
+ let b = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i1 ], material.alphaMap );
305
+ let c = ${ fns.getUvFromChannel }( ${ storage.attributes }[ i2 ], material.alphaMap );
306
+ let uv = barycoord.x * a + barycoord.y * b + barycoord.z * c;
307
+ let uvPrime = material.alphaMapTransform * vec3f( uv, 1 );
308
+
309
+ opacity *= ${ sampleTexel }( uvPrime.xy, material.alphaMap, 0 ).g;
310
+
311
+ }
312
+
313
+ // Opacity is resolved at surface processing but skip full transparent hits here.
314
+ if ( material.transparent != 0 && opacity <= 0.0 ) {
315
+
316
+ continue;
317
+
318
+ }
319
+
320
+ if ( opacity < material.alphaTest ) {
321
+
322
+ continue;
323
+
324
+ }
325
+
326
+ }
327
+
328
+ result.didHit = true;
329
+ result.dist = triResult.dist;
330
+ result.normal = triResult.normal;
331
+ result.side = triResult.side;
332
+ result.barycoord = triResult.barycoord;
333
+ result.indices = vec4u( i0, i1, i2, ti );
334
+
335
+ didHit = true;
336
+
337
+ }
338
+
339
+ }
340
+
341
+ return didHit;
342
+
343
+ }
344
+ `,
345
+ transformShapeFn: wgslTagFn/* wgsl */`
346
+ fn transformRay( ray: ptr<function, ${ rayStruct }>, objectIndex: u32 ) -> void {
347
+
348
+ let toLocal = ${ storage.transforms }[ objectIndex ].inverseMatrixWorld;
349
+ ray.origin = ( toLocal * vec4f( ray.origin, 1.0 ) ).xyz;
350
+ ray.direction = ( toLocal * vec4f( ray.direction, 0.0 ) ).xyz;
351
+
352
+ let len = length( ray.direction );
353
+ ray.direction /= len;
354
+ ${ scratchRayScalar } = 1.0 / len;
355
+
356
+ let object = ${ storage.transforms }[ objectIndex ];
357
+ ${ currentMaterialIndex } = object.materialIndex;
358
+
359
+ let material = ${ storage.materials }[ object.materialIndex ];
360
+ if ( material.transparent == 1 ) {
361
+
362
+ ${ baseOpacityScalar } = material.opacity * object.color.a;
363
+
364
+ } else {
365
+
366
+ ${ baseOpacityScalar } = 1.0;
367
+
368
+ }
369
+
370
+ }
371
+ `,
372
+ transformResultFn: wgslTagFn/* wgsl */`
373
+ fn transformResult( hit: ptr<function, ${ intersectionResultStruct }>, objectIndex: u32 ) -> void {
374
+
375
+ let toLocal = ${ storage.transforms }[ objectIndex ].inverseMatrixWorld;
376
+ hit.normal = normalize( ( transpose( toLocal ) * vec4f( hit.normal, 0.0 ) ).xyz );
377
+ hit.objectIndex = objectIndex;
378
+
379
+ }
380
+ `,
381
+ resetShapeFn: wgslTagFn/* wgsl */`
382
+ fn resetRayScalar( objectIndex: u32 ) -> void {
383
+
384
+ ${ scratchRayScalar } = 1.0;
385
+ ${ baseOpacityScalar } = 1.0;
386
+
387
+ }
388
+ `,
389
+ } );
390
+
391
+ }
392
+
393
+ update() {
394
+
395
+ const { structs } = this;
396
+ const attr = new StorageBufferAttribute( new Uint8Array(), structs.material.getLength() );
397
+ this.storage.materials = storage( attr, structs.material ).toReadOnly().setName( 'bvh_materials' );
398
+
399
+ this.updateUvAttributesFromScene();
400
+
401
+ super.update();
402
+
403
+ // build the channel -> uv lookup now that the geometry struct (and its uv members) exist
404
+ this.updateUvSampleFunction();
405
+ this.updateColorSampleFn();
406
+
407
+ // build material storage
408
+ this.updateMaterials();
409
+
410
+ this.bvhMap.clear();
411
+ this.useTransparencyRaycastFn();
412
+
413
+ }
414
+
415
+ updateMaterialsMap() {
416
+
417
+ const { materials, materialsMap } = this;
418
+ materialsMap.clear();
419
+ materials.length = 0;
420
+ this.getRootObject().traverse( o => {
421
+
422
+ if ( o.material ) {
423
+
424
+ if ( Array.isArray( o.material ) ) {
425
+
426
+ o.material.forEach( m => add( m ) );
427
+
428
+ } else {
429
+
430
+ add( o.material );
431
+
432
+ }
433
+
434
+ }
435
+
436
+ } );
437
+
438
+ materials
439
+ .sort( ( a, b ) => {
440
+
441
+ return a.uuid < b.uuid ? 1 : - 1;
442
+
443
+ } )
444
+ .forEach( ( m, i ) => {
445
+
446
+ materialsMap.set( m, i );
447
+
448
+ } );
449
+
450
+ function add( mat ) {
451
+
452
+ if ( ! materialsMap.has( mat ) ) {
453
+
454
+ materials.push( mat );
455
+ materialsMap.set( mat, - 1 );
456
+
457
+ }
458
+
459
+ }
460
+
461
+ }
462
+
463
+ updateMaterials() {
464
+
465
+ this.updateMaterialsMap();
466
+
467
+ const { materials, storage, structs, bvh } = this;
468
+ const { materialData, textures } = this.writeMaterialsBuffer( materials );
469
+
470
+ const materialsStorage = storage.materials.proxyNode;
471
+ const transformsStorage = storage.transforms.proxyNode;
472
+ const count = materialData.length / structs.material.getLength();
473
+ if ( materialsStorage.value.count < count ) {
474
+
475
+ materialsStorage.value.dispose();
476
+ materialsStorage.value = new StorageBufferAttribute( materialData, structs.material.getLength() );
477
+
478
+ }
479
+
480
+ // copy the material buffer content
481
+ materialsStorage.value.array.set( materialData );
482
+ materialsStorage.value.needsUpdate = true;
483
+
484
+ // update the transform content
485
+ this._getTransformMap( bvh ).forEach( info => {
486
+
487
+ this.writeMaterialData( info, info.slot, transformsStorage.value.array.buffer );
488
+
489
+ } );
490
+ transformsStorage.value.needsUpdate = true;
491
+
492
+ // save the textures
493
+ this.textures = textures;
494
+
495
+ }
496
+
497
+ writeMaterialsBuffer( materials ) {
498
+
499
+ function encodeTextureWrap( wrap ) {
500
+
501
+ switch ( wrap ) {
502
+
503
+ case RepeatWrapping:
504
+ return 0;
505
+ case ClampToEdgeWrapping:
506
+ return 1;
507
+ case MirroredRepeatWrapping:
508
+ return 2;
509
+ default:
510
+ return 0;
511
+
512
+ }
513
+
514
+ }
515
+
516
+ function getTexture( material, key ) {
517
+
518
+ if ( key in material && material[ key ] ) {
519
+
520
+ const texture = material[ key ];
521
+ const hash = getTextureHash( texture );
522
+
523
+ if ( ! textureLookUp.has( hash ) ) {
524
+
525
+ textureLookUp.set( hash, textureLookUp.size );
526
+ textures.push( texture );
527
+
528
+ }
529
+
530
+ const idx = textureLookUp.get( hash ); // 23 bits
531
+ const channel = texture.channel & 7; // 3 bits
532
+ const wrapS = encodeTextureWrap( texture.wrapS ); // 2 bits
533
+ const wrapT = encodeTextureWrap( texture.wrapT ); // 2 bits
534
+ const nearest = texture.magFilter === NearestFilter ? 1 : 0; // 1 bit
535
+ return ( nearest << 30 ) | ( wrapT << 28 ) | ( wrapS << 26 ) | ( channel << 23 ) | ( idx & 0x7fffff );
536
+
537
+ } else {
538
+
539
+ return - 1;
540
+
541
+ }
542
+
543
+ }
544
+
545
+ function getField( material, key, def ) {
546
+
547
+ return key in material ? material[ key ] : def;
548
+
549
+ }
550
+
551
+ function writeTextureMatrixToArray( material, textureKey, array, offset ) {
552
+
553
+ const texture = material[ textureKey ] && material[ textureKey ].isTexture ? material[ textureKey ] : null;
554
+
555
+ // check if texture exists
556
+ if ( texture ) {
557
+
558
+ if ( texture.matrixAutoUpdate ) {
559
+
560
+ texture.updateMatrix();
561
+
562
+ }
563
+
564
+ const elements = texture.matrix.elements;
565
+
566
+ // Both wgsl struct and elements should be in column-major format
567
+ for ( let i = 0; i < 3; i ++ ) {
568
+
569
+ array[ offset + 4 * i + 0 ] = elements[ 3 * i + 0 ];
570
+ array[ offset + 4 * i + 1 ] = elements[ 3 * i + 1 ];
571
+ array[ offset + 4 * i + 2 ] = elements[ 3 * i + 2 ];
572
+ array[ offset + 4 * i + 3 ] = 0; // padding float
573
+
574
+ }
575
+
576
+ }
577
+
578
+ return 12;
579
+
580
+ }
581
+
582
+ let index = 0;
583
+
584
+ // Collect and index the list of textures based on shareable source
585
+ const textureLookUp = new Map();
586
+ const textures = [];
587
+
588
+ // NOTE: make the minimum material buffer length 2 in order to avoid TSL converting it to a scalar
589
+ // TODO: remove this when fixed in three
590
+ const materialBufferLength = Math.max( materials.length, 2 );
591
+ const floatArray = new Float32Array( materialBufferLength * this.structs.material.getLength() );
592
+ const intArray = new Int32Array( floatArray.buffer );
593
+
594
+ // TODO: make features work
595
+ // features.reset();
596
+ for ( let i = 0, l = materials.length; i < l; i ++ ) {
597
+
598
+ const m = materials[ i ];
599
+
600
+ // if ( m.isFogVolumeMaterial ) {
601
+ //
602
+ // // features.setUsed( 'FOG' );
603
+ //
604
+ // for ( let j = 0; j < MATERIAL_STRIDE; j ++ ) {
605
+ //
606
+ // floatArray[ index + j ] = 0;
607
+ //
608
+ // }
609
+ //
610
+ // // sample 0 .rgb
611
+ // floatArray[ index + 0 * 4 + 0 ] = m.color.r;
612
+ // floatArray[ index + 0 * 4 + 1 ] = m.color.g;
613
+ // floatArray[ index + 0 * 4 + 2 ] = m.color.b;
614
+ //
615
+ // // sample 2 .a
616
+ // floatArray[ index + 2 * 4 + 3 ] = getField( m, 'emissiveIntensity', 0.0 );
617
+ //
618
+ // // sample 3 .rgb
619
+ // floatArray[ index + 3 * 4 + 0 ] = m.emissive.r;
620
+ // floatArray[ index + 3 * 4 + 1 ] = m.emissive.g;
621
+ // floatArray[ index + 3 * 4 + 2 ] = m.emissive.b;
622
+ //
623
+ // // sample 13 .g
624
+ // // reusing opacity field
625
+ // floatArray[ index + 13 * 4 + 1 ] = m.density;
626
+ //
627
+ // // side
628
+ // floatArray[ index + 13 * 4 + 3 ] = 0.0;
629
+ //
630
+ // // sample 14 .b
631
+ // floatArray[ index + 14 * 4 + 2 ] = 1 << 2;
632
+ //
633
+ // index += MATERIAL_STRIDE;
634
+ // continue;
635
+ //
636
+ // }
637
+
638
+ // color - offset 0
639
+ floatArray[ index ++ ] = m.color.r;
640
+ floatArray[ index ++ ] = m.color.g;
641
+ floatArray[ index ++ ] = m.color.b;
642
+ intArray[ index ++ ] = getTexture( m, 'map' );
643
+
644
+ // metalness & roughness - offset 4
645
+ floatArray[ index ++ ] = getField( m, 'metalness', 0.0 );
646
+ intArray[ index ++ ] = getTexture( m, 'metalnessMap' );
647
+ floatArray[ index ++ ] = getField( m, 'roughness', 0.0 );
648
+ intArray[ index ++ ] = getTexture( m, 'roughnessMap' );
649
+
650
+ // transmission & emissiveIntensity - offset 8
651
+ // three.js assumes a default f0 of 0.04 if no ior is provided which equates to an ior of 1.5
652
+ floatArray[ index ++ ] = getField( m, 'ior', 1.5 );
653
+ floatArray[ index ++ ] = getField( m, 'transmission', 0.0 );
654
+ intArray[ index ++ ] = getTexture( m, 'transmissionMap' );
655
+ floatArray[ index ++ ] = getField( m, 'emissiveIntensity', 0.0 );
656
+
657
+ // emission - offset 12
658
+ if ( 'emissive' in m ) {
659
+
660
+ floatArray[ index ++ ] = m.emissive.r;
661
+ floatArray[ index ++ ] = m.emissive.g;
662
+ floatArray[ index ++ ] = m.emissive.b;
663
+
664
+ } else {
665
+
666
+ floatArray[ index ++ ] = 0.0;
667
+ floatArray[ index ++ ] = 0.0;
668
+ floatArray[ index ++ ] = 0.0;
669
+
670
+ }
671
+
672
+ intArray[ index ++ ] = getTexture( m, 'emissiveMap' );
673
+
674
+ // normals - offset 16
675
+ intArray[ index ++ ] = getTexture( m, 'normalMap' );
676
+ index ++; // because of vec2 alignment
677
+ if ( 'normalScale' in m ) {
678
+
679
+ floatArray[ index ++ ] = m.normalScale.x;
680
+ floatArray[ index ++ ] = m.normalScale.y;
681
+
682
+ } else {
683
+
684
+ floatArray[ index ++ ] = 1;
685
+ floatArray[ index ++ ] = 1;
686
+
687
+ }
688
+
689
+ // clearcoat - offset 20
690
+ floatArray[ index ++ ] = getField( m, 'clearcoat', 0.0 );
691
+ intArray[ index ++ ] = getTexture( m, 'clearcoatMap' );
692
+ intArray[ index ++ ] = getTexture( m, 'clearcoatNormalMap' );
693
+ index ++; // because of vec2 alignment
694
+
695
+ // offset 24
696
+ if ( 'clearcoatNormalScale' in m ) {
697
+
698
+ floatArray[ index ++ ] = m.clearcoatNormalScale.x;
699
+ floatArray[ index ++ ] = m.clearcoatNormalScale.y;
700
+
701
+ } else {
702
+
703
+ floatArray[ index ++ ] = 1;
704
+ floatArray[ index ++ ] = 1;
705
+
706
+ }
707
+
708
+ floatArray[ index ++ ] = getField( m, 'clearcoatRoughness', 0.0 );
709
+ intArray[ index ++ ] = getTexture( m, 'clearcoatRoughnessMap' );
710
+
711
+ // iridescence - offset 28
712
+ intArray[ index ++ ] = getTexture( m, 'iridescenceMap' );
713
+ intArray[ index ++ ] = getTexture( m, 'iridescenceThicknessMap' );
714
+
715
+ floatArray[ index ++ ] = getField( m, 'iridescence', 0.0 );
716
+ floatArray[ index ++ ] = getField( m, 'iridescenceIOR', 1.3 );
717
+
718
+ // offset 32
719
+ const iridescenceThicknessRange = getField( m, 'iridescenceThicknessRange', [ 100, 400 ] );
720
+ floatArray[ index ++ ] = iridescenceThicknessRange[ 0 ];
721
+ floatArray[ index ++ ] = iridescenceThicknessRange[ 1 ];
722
+ floatArray[ index ++ ] = getField( m, 'diffuseRoughness', 0.0 );
723
+ floatArray[ index ++ ] = getField( m, 'dispersion', 0.0 );
724
+
725
+ // specular color - offset 36
726
+ if ( 'specularColor' in m ) {
727
+
728
+ floatArray[ index ++ ] = m.specularColor.r;
729
+ floatArray[ index ++ ] = m.specularColor.g;
730
+ floatArray[ index ++ ] = m.specularColor.b;
731
+
732
+ } else {
733
+
734
+ floatArray[ index ++ ] = 1.0;
735
+ floatArray[ index ++ ] = 1.0;
736
+ floatArray[ index ++ ] = 1.0;
737
+
738
+ }
739
+
740
+ intArray[ index ++ ] = getTexture( m, 'specularColorMap' );
741
+
742
+ // specular intensity - offset 40
743
+ floatArray[ index ++ ] = getField( m, 'specularIntensity', 1.0 );
744
+ intArray[ index ++ ] = getTexture( m, 'specularIntensityMap' );
745
+
746
+ // isThinWall
747
+ const isThinWall = getField( m, 'thickness', 0.0 ) === 0.0 && getField( m, 'attenuationDistance', Infinity ) === Infinity;
748
+ intArray[ index ++ ] = Number( isThinWall );
749
+ index ++;
750
+
751
+ // attenuation - offset 44
752
+ if ( 'attenuationColor' in m ) {
753
+
754
+ floatArray[ index ++ ] = m.attenuationColor.r;
755
+ floatArray[ index ++ ] = m.attenuationColor.g;
756
+ floatArray[ index ++ ] = m.attenuationColor.b;
757
+
758
+ } else {
759
+
760
+ floatArray[ index ++ ] = 1.0;
761
+ floatArray[ index ++ ] = 1.0;
762
+ floatArray[ index ++ ] = 1.0;
763
+
764
+ }
765
+
766
+ floatArray[ index ++ ] = getField( m, 'attenuationDistance', Infinity );
767
+
768
+ // alphaMap - offset 48
769
+ intArray[ index ++ ] = getTexture( m, 'alphaMap' );
770
+ intArray[ index ++ ] = Number( getField( m, 'castShadow', true ) ); // shadow
771
+ floatArray[ index ++ ] = m.opacity;
772
+ floatArray[ index ++ ] = m.alphaTest;
773
+
774
+ // side & matte - offset 52
775
+ if ( ! isThinWall && m.transmission > 0.0 ) {
776
+
777
+ floatArray[ index ++ ] = 0;
778
+
779
+ } else {
780
+
781
+ switch ( m.side ) {
782
+
783
+ case FrontSide:
784
+ floatArray[ index ++ ] = 1;
785
+ break;
786
+ case BackSide:
787
+ floatArray[ index ++ ] = - 1;
788
+ break;
789
+ case DoubleSide:
790
+ floatArray[ index ++ ] = 0;
791
+ break;
792
+
793
+ }
794
+
795
+ }
796
+
797
+ intArray[ index ++ ] = Number( getField( m, 'matte', false ) ); // matte
798
+ floatArray[ index ++ ] = getField( m, 'sheen', 0.0 );
799
+ index ++; // vec3 alignment requirements
800
+
801
+ // sheenColor - offset 56
802
+ if ( 'sheenColor' in m ) {
803
+
804
+ floatArray[ index ++ ] = m.sheenColor.r;
805
+ floatArray[ index ++ ] = m.sheenColor.g;
806
+ floatArray[ index ++ ] = m.sheenColor.b;
807
+
808
+ } else {
809
+
810
+ floatArray[ index ++ ] = 0.0;
811
+ floatArray[ index ++ ] = 0.0;
812
+ floatArray[ index ++ ] = 0.0;
813
+
814
+ }
815
+
816
+ intArray[ index ++ ] = getTexture( m, 'sheenColorMap' );
817
+
818
+ // sheenRoughness, flags - offset 60
819
+ floatArray[ index ++ ] = getField( m, 'sheenRoughness', 0.0 );
820
+ intArray[ index ++ ] = getTexture( m, 'sheenRoughnessMap' );
821
+
822
+ intArray[ index ++ ] = Number( m.vertexColors );
823
+ intArray[ index ++ ] = Number( m.flatShading );
824
+
825
+ // transparent, fogVolume - offset 64
826
+ intArray[ index ++ ] = Number( m.transparent );
827
+ intArray[ index ++ ] = 0;
828
+
829
+ // anisotropy, anisotropyRotation, anisotropyMap - offset 66
830
+ floatArray[ index ++ ] = getField( m, 'anisotropy', 0.0 );
831
+ floatArray[ index ++ ] = getField( m, 'anisotropyRotation', 0.0 );
832
+ intArray[ index ++ ] = getTexture( m, 'anisotropyMap' );
833
+ index ++; // padding for mat3 alignment
834
+ index ++;
835
+ index ++;
836
+
837
+ // map transform - offset 72
838
+ index += writeTextureMatrixToArray( m, 'map', floatArray, index );
839
+
840
+ // metalnessMap transform - offset 84
841
+ index += writeTextureMatrixToArray( m, 'metalnessMap', floatArray, index );
842
+
843
+ // roughnessMap transform - offset 96
844
+ index += writeTextureMatrixToArray( m, 'roughnessMap', floatArray, index );
845
+
846
+ // transmissionMap transform - offset 108
847
+ index += writeTextureMatrixToArray( m, 'transmissionMap', floatArray, index );
848
+
849
+ // emissiveMap transform - offset 120
850
+ index += writeTextureMatrixToArray( m, 'emissiveMap', floatArray, index );
851
+
852
+ // normalMap transform - offset 132
853
+ index += writeTextureMatrixToArray( m, 'normalMap', floatArray, index );
854
+
855
+ // clearcoatMap transform - offset 144
856
+ index += writeTextureMatrixToArray( m, 'clearcoatMap', floatArray, index );
857
+
858
+ // clearcoatNormalMap transform - offset 156
859
+ index += writeTextureMatrixToArray( m, 'clearcoatNormalMap', floatArray, index );
860
+
861
+ // clearcoatRoughnessMap transform - offset 168
862
+ index += writeTextureMatrixToArray( m, 'clearcoatRoughnessMap', floatArray, index );
863
+
864
+ // sheenColorMap transform - offset 180
865
+ index += writeTextureMatrixToArray( m, 'sheenColorMap', floatArray, index );
866
+
867
+ // sheenRoughnessMap transform - offset 192
868
+ index += writeTextureMatrixToArray( m, 'sheenRoughnessMap', floatArray, index );
869
+
870
+ // iridescenceMap transform - offset 204
871
+ index += writeTextureMatrixToArray( m, 'iridescenceMap', floatArray, index );
872
+
873
+ // iridescenceThicknessMap transform - offset 216
874
+ index += writeTextureMatrixToArray( m, 'iridescenceThicknessMap', floatArray, index );
875
+
876
+ // specularColorMap transform - offset 228
877
+ index += writeTextureMatrixToArray( m, 'specularColorMap', floatArray, index );
878
+
879
+ // specularIntensityMap transform - offset 240
880
+ index += writeTextureMatrixToArray( m, 'specularIntensityMap', floatArray, index );
881
+
882
+ // alphaMap transform - offset 252
883
+ index += writeTextureMatrixToArray( m, 'alphaMap', floatArray, index );
884
+
885
+ // anisotropyMap transform - offset 264
886
+ index += writeTextureMatrixToArray( m, 'anisotropyMap', floatArray, index );
887
+
888
+ }
889
+
890
+ return { materialData: intArray, textures };
891
+
892
+ }
893
+
894
+ writeMaterialData( info, writeOffset, targetBuffer ) {
895
+
896
+ // write material data to the transforms
897
+ const { materialsMap } = this;
898
+ const { object, instanceId, root } = info;
899
+
900
+ // get the material associated with the bvh group
901
+ let material = object.material;
902
+ if ( Array.isArray( material ) ) {
903
+
904
+ const { materialIndex } = object.geometry.groups[ root ];
905
+ material = material[ materialIndex ];
906
+
907
+ }
908
+
909
+ // save the index
910
+ const index = materialsMap.get( material ) || 0;
911
+ const transformBufferU32 = new Uint32Array( targetBuffer );
912
+ transformBufferU32[ writeOffset * transformStruct.getLength() + 33 ] = index;
913
+
914
+ // write color
915
+ // TODO: note that both BatchedMesh and InstancedMesh "getColorAt" functions throw
916
+ // if colors have not been defined.
917
+ if ( object.isInstancedMesh && object.instanceColor ) {
918
+
919
+ object.getColorAt( instanceId, _colorVec );
920
+ _colorVec.w = 1.0;
921
+
922
+ } else if ( object.isBatchedMesh && object._colorsTexture ) {
923
+
924
+ object.getColorAt( instanceId, _colorVec );
925
+
926
+ } else {
927
+
928
+ _colorVec.setScalar( 1 );
929
+
930
+ }
931
+
932
+ const transformBufferF32 = new Float32Array( targetBuffer );
933
+ _colorVec.toArray( transformBufferF32, writeOffset * transformStruct.getLength() + 36 );
934
+
935
+ }
936
+
937
+ writeTransformData( info, premultiplyMatrix, writeOffset, targetBuffer ) {
938
+
939
+ super.writeTransformData( info, premultiplyMatrix, writeOffset, targetBuffer );
940
+ this.writeMaterialData( info, writeOffset, targetBuffer );
941
+
942
+ }
943
+
944
+ getBVH( object, instanceId, rangeTarget ) {
945
+
946
+ const { bvhMap } = this;
947
+ const bvh = super.getBVH( object, instanceId, rangeTarget );
948
+ if ( bvhMap.has( bvh ) ) {
949
+
950
+ const data = bvhMap.get( bvh );
951
+ Object.assign( rangeTarget, data.range );
952
+
953
+ // make sure the mesh and bvh are updated if it's being reused across updates
954
+ if ( bvh !== data.bvh && bvh instanceof SkinnedMeshBVH ) {
955
+
956
+ const sourceMesh = bvh.mesh;
957
+ const clonedMesh = data.bvh.mesh;
958
+ clonedMesh.matrixWorld
959
+ .copy( sourceMesh.matrixWorld )
960
+ .decompose( clonedMesh.position, clonedMesh.quaternion, clonedMesh.scale );
961
+
962
+ bvh.refit();
963
+ bvh.getBoundingBox( clonedMesh.boundingBox );
964
+ bvh.geometry.computeBoundingBox();
965
+
966
+ }
967
+
968
+ return data.bvh;
969
+
970
+ } else if ( bvh.indirect ) {
971
+
972
+ // "indirect" bvhs are not supported since they cannot be unpacked in a way tht will allow for coherent material indices
973
+ const proxyGeometry = new BufferGeometry();
974
+ proxyGeometry.attributes = bvh.geometry.attributes;
975
+
976
+ let array;
977
+ if ( bvh.geometry.index ) {
978
+
979
+ array = bvh.geometry.index.array.slice( rangeTarget.start, rangeTarget.count + rangeTarget.start );
980
+
981
+ } else {
982
+
983
+ const { start, count } = rangeTarget;
984
+ array = new Uint32Array( count );
985
+ for ( let i = 0, l = rangeTarget.count; i < l; i ++ ) {
986
+
987
+ array[ i ] = start + i;
988
+
989
+ }
990
+
991
+ }
992
+
993
+ proxyGeometry.index = new BufferAttribute( array, 1 );
994
+ rangeTarget.start = 0;
995
+
996
+ let newBVH;
997
+ if ( bvh instanceof SkinnedMeshBVH ) {
998
+
999
+ const sourceMesh = bvh.mesh;
1000
+ const clonedMesh = new SkinnedMesh( proxyGeometry );
1001
+ clonedMesh.copy( sourceMesh );
1002
+ clonedMesh.matrixWorld
1003
+ .copy( sourceMesh.matrixWorld )
1004
+ .decompose( clonedMesh.position, clonedMesh.quaternion, clonedMesh.scale );
1005
+
1006
+ newBVH = new SkinnedMeshBVH( clonedMesh, { strategy: SAH, targetLeafSize: 5 } );
1007
+
1008
+ } else {
1009
+
1010
+ newBVH = new MeshBVH( proxyGeometry, { strategy: SAH, targetLeafSize: 5 } );
1011
+
1012
+ }
1013
+
1014
+ bvhMap.set( bvh, { bvh: newBVH, range: { ...rangeTarget } } );
1015
+ return newBVH;
1016
+
1017
+ } else {
1018
+
1019
+ return bvh;
1020
+
1021
+ }
1022
+
1023
+ }
1024
+
1025
+ dispose() {
1026
+
1027
+ // TODO: This belongs in three-mesh-bvh
1028
+ const { storage } = this;
1029
+ for ( const key in storage ) {
1030
+
1031
+ storage[ key ].value?.dispose();
1032
+
1033
+ }
1034
+
1035
+ }
1036
+
1037
+ }