three-gpu-pathtracer 0.0.20 → 0.0.21

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 (75) hide show
  1. package/README.md +111 -464
  2. package/build/index.module.js +5691 -5312
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +5369 -5003
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +12 -6
  7. package/src/core/PathTracingRenderer.js +59 -46
  8. package/src/core/PathTracingSceneGenerator.js +245 -10
  9. package/src/core/WebGLPathTracer.js +472 -0
  10. package/src/core/utils/BakedGeometry.js +35 -0
  11. package/src/core/utils/BufferAttributeUtils.js +64 -0
  12. package/src/{utils → core/utils}/GeometryPreparationUtils.js +35 -35
  13. package/src/core/utils/MeshDiff.js +102 -0
  14. package/src/core/utils/StaticGeometryGenerator.js +285 -0
  15. package/src/core/utils/convertToStaticGeometry.js +344 -0
  16. package/src/core/utils/mergeGeometries.js +218 -0
  17. package/src/core/utils/sceneUpdateUtils.js +96 -0
  18. package/src/index.d.ts +274 -0
  19. package/src/index.js +4 -20
  20. package/src/materials/MaterialBase.js +4 -0
  21. package/src/materials/fullscreen/ClampedInterpolationMaterial.js +112 -0
  22. package/src/materials/fullscreen/DenoiseMaterial.js +4 -0
  23. package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +73 -76
  24. package/src/materials/pathtracing/glsl/{attenuateHit.glsl.js → attenuate_hit_function.glsl.js} +1 -1
  25. package/src/materials/pathtracing/glsl/{cameraUtils.glsl.js → camera_util_functions.glsl.js} +1 -1
  26. package/src/materials/pathtracing/glsl/{directLightContribution.glsl.js → direct_light_contribution_function.glsl.js} +1 -1
  27. package/src/materials/pathtracing/glsl/{getSurfaceRecord.glsl.js → get_surface_record_function.glsl.js} +1 -1
  28. package/src/materials/pathtracing/glsl/index.js +6 -0
  29. package/src/materials/pathtracing/glsl/{renderStructs.glsl.js → render_structs.glsl.js} +1 -1
  30. package/src/materials/pathtracing/glsl/{traceScene.glsl.js → trace_scene_function.glsl.js} +1 -3
  31. package/src/materials/surface/AmbientOcclusionMaterial.js +8 -8
  32. package/src/objects/PhysicalSpotLight.js +2 -2
  33. package/src/shader/bsdf/{bsdfSampling.glsl.js → bsdf_functions.glsl.js} +19 -72
  34. package/src/shader/bsdf/{fog.glsl.js → fog_functions.glsl.js} +1 -1
  35. package/src/shader/bsdf/{ggx.glsl.js → ggx_functions.glsl.js} +1 -1
  36. package/src/shader/bsdf/index.js +5 -0
  37. package/src/shader/bsdf/{iridescence.glsl.js → iridescence_functions.glsl.js} +1 -1
  38. package/src/shader/bsdf/{sheen.glsl.js → sheen_functions.glsl.js} +1 -1
  39. package/src/shader/bvh/index.js +2 -0
  40. package/src/shader/{structs/fogMaterialBvh.glsl.js → bvh/inside_fog_volume_function.glsl.js} +1 -1
  41. package/src/shader/{common/bvhAnyHit.glsl.js → bvh/ray_any_hit_function.glsl.js} +1 -1
  42. package/src/shader/common/{fresnel.glsl.js → fresnel_functions.glsl.js} +1 -1
  43. package/src/shader/common/index.js +5 -0
  44. package/src/shader/common/{math.glsl.js → math_functions.glsl.js} +1 -1
  45. package/src/shader/common/{intersectShapes.glsl.js → shape_intersection_functions.glsl.js} +1 -1
  46. package/src/shader/common/{arraySamplerTexelFetch.glsl.js → texture_sample_functions.glsl.js} +1 -1
  47. package/src/shader/common/{utils.glsl.js → util_functions.glsl.js} +1 -1
  48. package/src/shader/rand/index.js +3 -0
  49. package/src/shader/rand/pcg.glsl.js +1 -1
  50. package/src/shader/rand/sobol.glsl.js +4 -4
  51. package/src/shader/rand/{stratifiedTexture.glsl.js → stratified.glsl.js} +7 -2
  52. package/src/shader/sampling/{equirectSampling.glsl.js → equirect_sampling_functions.glsl.js} +1 -2
  53. package/src/shader/sampling/index.js +3 -0
  54. package/src/shader/sampling/{lightSampling.glsl.js → light_sampling_functions.glsl.js} +3 -3
  55. package/src/shader/sampling/{shapeSampling.glsl.js → shape_sampling_functions.glsl.js} +1 -1
  56. package/src/shader/structs/{cameraStruct.glsl.js → camera_struct.glsl.js} +1 -1
  57. package/src/shader/structs/{equirectStruct.glsl.js → equirect_struct.glsl.js} +1 -1
  58. package/src/shader/structs/index.js +5 -0
  59. package/src/shader/structs/{lightsStruct.glsl.js → lights_struct.glsl.js} +1 -1
  60. package/src/shader/structs/{materialStruct.glsl.js → material_struct.glsl.js} +2 -2
  61. package/src/shader/structs/surface_record_struct.glsl.js +63 -0
  62. package/src/uniforms/EquirectHdrInfoUniform.js +16 -11
  63. package/src/uniforms/LightsInfoUniformStruct.js +21 -10
  64. package/src/uniforms/MaterialsTexture.js +27 -86
  65. package/src/uniforms/RenderTarget2DArray.js +60 -20
  66. package/src/utils/BlurredEnvMapGenerator.js +12 -5
  67. package/src/utils/SobolNumberMapGenerator.js +3 -3
  68. package/src/utils/bufferToHash.js +22 -0
  69. package/src/core/DynamicPathTracingSceneGenerator.js +0 -164
  70. package/src/core/MaterialReducer.js +0 -256
  71. package/src/materials/pathtracing/LambertPathTracingMaterial.js +0 -297
  72. package/src/uniforms/IESProfilesTexture.js +0 -100
  73. package/src/uniforms/utils.js +0 -30
  74. package/src/utils/IESLoader.js +0 -327
  75. package/src/workers/PathTracingSceneWorker.js +0 -52
@@ -0,0 +1,472 @@
1
+ import { PerspectiveCamera, Scene, Vector2, Clock, NormalBlending, NoBlending, AdditiveBlending } from 'three';
2
+ import { PathTracingSceneGenerator } from './PathTracingSceneGenerator.js';
3
+ import { PathTracingRenderer } from './PathTracingRenderer.js';
4
+ import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
5
+ import { GradientEquirectTexture } from '../textures/GradientEquirectTexture.js';
6
+ import { getIesTextures, getLights, getTextures } from './utils/sceneUpdateUtils.js';
7
+ import { ClampedInterpolationMaterial } from '../materials/fullscreen/ClampedInterpolationMaterial.js';
8
+
9
+ function supportsFloatBlending( renderer ) {
10
+
11
+ return renderer.extensions.get( 'EXT_float_blend' );
12
+
13
+ }
14
+
15
+ const _resolution = new Vector2();
16
+ export class WebGLPathTracer {
17
+
18
+ get multipleImportanceSampling() {
19
+
20
+ return Boolean( this._pathTracer.material.defines.FEATURE_MIS );
21
+
22
+ }
23
+
24
+ set multipleImportanceSampling( v ) {
25
+
26
+ this._pathTracer.material.setDefine( 'FEATURE_MIS', v ? 1 : 0 );
27
+
28
+ }
29
+
30
+ get transmissiveBounces() {
31
+
32
+ return this._pathTracer.material.transmissiveBounces;
33
+
34
+ }
35
+
36
+ set transmissiveBounces( v ) {
37
+
38
+ this._pathTracer.material.transmissiveBounces = v;
39
+
40
+ }
41
+
42
+ get bounces() {
43
+
44
+ return this._pathTracer.material.bounces;
45
+
46
+ }
47
+
48
+ set bounces( v ) {
49
+
50
+ this._pathTracer.material.bounces = v;
51
+
52
+ }
53
+
54
+ get filterGlossyFactor() {
55
+
56
+ return this._pathTracer.material.filterGlossyFactor;
57
+
58
+ }
59
+
60
+ set filterGlossyFactor( v ) {
61
+
62
+ this._pathTracer.material.filterGlossyFactor = v;
63
+
64
+ }
65
+
66
+ get samples() {
67
+
68
+ return this._pathTracer.samples;
69
+
70
+ }
71
+
72
+ get target() {
73
+
74
+ return this._pathTracer.target;
75
+
76
+ }
77
+
78
+ get tiles() {
79
+
80
+ return this._pathTracer.tiles;
81
+
82
+ }
83
+
84
+ constructor( renderer ) {
85
+
86
+ // members
87
+ this._renderer = renderer;
88
+ this._generator = new PathTracingSceneGenerator();
89
+ this._pathTracer = new PathTracingRenderer( renderer );
90
+ this._queueReset = false;
91
+ this._clock = new Clock();
92
+
93
+ this._lowResPathTracer = new PathTracingRenderer( renderer );
94
+ this._lowResPathTracer.tiles.set( 1, 1 );
95
+ this._quad = new FullScreenQuad( new ClampedInterpolationMaterial( {
96
+ map: null,
97
+ transparent: true,
98
+ blending: NoBlending,
99
+
100
+ premultipliedAlpha: renderer.getContextAttributes().premultipliedAlpha,
101
+ } ) );
102
+ this._materials = null;
103
+
104
+ // options
105
+ this.renderDelay = 100;
106
+ this.minSamples = 5;
107
+ this.fadeDuration = 500;
108
+ this.enablePathTracing = true;
109
+ this.pausePathTracing = false;
110
+ this.dynamicLowRes = false;
111
+ this.lowResScale = 0.25;
112
+ this.renderScale = 1;
113
+ this.synchronizeRenderSize = true;
114
+ this.rasterizeScene = true;
115
+ this.renderToCanvas = true;
116
+ this.textureSize = new Vector2( 1024, 1024 );
117
+ this.rasterizeSceneCallback = ( scene, camera ) => {
118
+
119
+ this._renderer.render( scene, camera );
120
+
121
+ };
122
+
123
+ this.renderToCanvasCallback = ( target, renderer, quad ) => {
124
+
125
+ const currentAutoClear = renderer.autoClear;
126
+ renderer.autoClear = false;
127
+ quad.render( renderer );
128
+ renderer.autoClear = currentAutoClear;
129
+
130
+ };
131
+
132
+ // initialize the scene so it doesn't fail
133
+ this.setScene( new Scene(), new PerspectiveCamera() );
134
+
135
+ }
136
+
137
+ setBVHWorker( worker ) {
138
+
139
+ this._generator.setBVHWorker( worker );
140
+
141
+ }
142
+
143
+ setScene( scene, camera, options = {} ) {
144
+
145
+ scene.updateMatrixWorld( true );
146
+ camera.updateMatrixWorld();
147
+
148
+ const generator = this._generator;
149
+ generator.setObjects( scene );
150
+
151
+ if ( this._buildAsync ) {
152
+
153
+ return generator.generateAsync( options.onProgress ).then( result => {
154
+
155
+ return this._updateFromResults( scene, camera, result );
156
+
157
+ } );
158
+
159
+ } else {
160
+
161
+ const result = generator.generate();
162
+ return this._updateFromResults( scene, camera, result );
163
+
164
+ }
165
+
166
+ }
167
+
168
+ setSceneAsync( ...args ) {
169
+
170
+ this._buildAsync = true;
171
+ const result = this.setScene( ...args );
172
+ this._buildAsync = false;
173
+
174
+ return result;
175
+
176
+ }
177
+
178
+ setCamera( camera ) {
179
+
180
+ this.camera = camera;
181
+ this.updateCamera();
182
+
183
+ }
184
+
185
+ updateCamera() {
186
+
187
+ const camera = this.camera;
188
+ camera.updateMatrixWorld();
189
+
190
+ this._pathTracer.setCamera( camera );
191
+ this._lowResPathTracer.setCamera( camera );
192
+ this.reset();
193
+
194
+ }
195
+
196
+ updateMaterials() {
197
+
198
+ const material = this._pathTracer.material;
199
+ const renderer = this._renderer;
200
+ const materials = this._materials;
201
+ const textureSize = this.textureSize;
202
+
203
+ // reduce texture sources here - we don't want to do this in the
204
+ // textures array because we need to pass the textures array into the
205
+ // material target
206
+ const textures = getTextures( materials );
207
+ material.textures.setTextures( renderer, textures, textureSize.x, textureSize.y );
208
+ material.materials.updateFrom( materials, textures );
209
+ this.reset();
210
+
211
+ }
212
+
213
+ updateLights() {
214
+
215
+ const scene = this.scene;
216
+ const renderer = this._renderer;
217
+ const material = this._pathTracer.material;
218
+
219
+ const lights = getLights( scene );
220
+ const iesTextures = getIesTextures( lights );
221
+ material.lights.updateFrom( lights, iesTextures );
222
+ material.iesProfiles.setTextures( renderer, iesTextures );
223
+ this.reset();
224
+
225
+ }
226
+
227
+ updateEnvironment() {
228
+
229
+ const scene = this.scene;
230
+ const material = this._pathTracer.material;
231
+
232
+ // update scene background
233
+ material.backgroundBlur = scene.backgroundBlurriness;
234
+ material.backgroundIntensity = scene.backgroundIntensity ?? 1;
235
+ material.backgroundRotation.makeRotationFromEuler( scene.backgroundRotation ).invert();
236
+ if ( scene.background === null ) {
237
+
238
+ material.backgroundMap = null;
239
+ material.backgroundAlpha = 0;
240
+
241
+ } else if ( scene.background.isColor ) {
242
+
243
+ this._colorBackground = this._colorBackground || new GradientEquirectTexture( 16 );
244
+
245
+ const colorBackground = this._colorBackground;
246
+ if ( ! colorBackground.topColor.equals( scene.background ) ) {
247
+
248
+ // set the texture color
249
+ colorBackground.topColor.set( scene.background );
250
+ colorBackground.bottomColor.set( scene.background );
251
+ colorBackground.update();
252
+
253
+ }
254
+
255
+ // assign to material
256
+ material.backgroundMap = colorBackground;
257
+ material.backgroundAlpha = 1;
258
+
259
+ } else {
260
+
261
+ material.backgroundMap = scene.background;
262
+ material.backgroundAlpha = 1;
263
+
264
+ }
265
+
266
+ // update scene environment
267
+ material.environmentIntensity = scene.environmentIntensity ?? 1;
268
+ material.environmentRotation.makeRotationFromEuler( scene.environmentRotation ).invert();
269
+ if ( this._previousEnvironment !== scene.environment ) {
270
+
271
+ if ( scene.environment ) {
272
+
273
+ // TODO: Consider setting this to the highest supported bit depth by checking for
274
+ // OES_texture_float_linear or OES_texture_half_float_linear. Requires changes to
275
+ // the equirect uniform
276
+ material.envMapInfo.updateFrom( scene.environment );
277
+
278
+ } else {
279
+
280
+ material.environmentIntensity = 0;
281
+
282
+ }
283
+
284
+ }
285
+
286
+ this._previousEnvironment = scene.environment;
287
+ this.reset();
288
+
289
+ }
290
+
291
+ _updateFromResults( scene, camera, results ) {
292
+
293
+ const {
294
+ materials,
295
+ geometry,
296
+ bvh,
297
+ bvhChanged,
298
+ } = results;
299
+
300
+ this._materials = materials;
301
+
302
+ const pathTracer = this._pathTracer;
303
+ const material = pathTracer.material;
304
+
305
+ if ( bvhChanged ) {
306
+
307
+ material.bvh.updateFrom( bvh );
308
+ material.attributesArray.updateFrom(
309
+ geometry.attributes.normal,
310
+ geometry.attributes.tangent,
311
+ geometry.attributes.uv,
312
+ geometry.attributes.color,
313
+ );
314
+
315
+ material.materialIndexAttribute.updateFrom( geometry.attributes.materialIndex );
316
+
317
+ }
318
+
319
+ // save previously used items
320
+ this._previousScene = scene;
321
+ this.scene = scene;
322
+ this.camera = camera;
323
+
324
+ this.updateCamera();
325
+ this.updateMaterials();
326
+ this.updateEnvironment();
327
+ this.updateLights();
328
+
329
+ return results;
330
+
331
+ }
332
+
333
+ renderSample() {
334
+
335
+ const lowResPathTracer = this._lowResPathTracer;
336
+ const pathTracer = this._pathTracer;
337
+ const renderer = this._renderer;
338
+ const clock = this._clock;
339
+ const quad = this._quad;
340
+
341
+ this._updateScale();
342
+
343
+ if ( this._queueReset ) {
344
+
345
+ pathTracer.reset();
346
+ lowResPathTracer.reset();
347
+ this._queueReset = false;
348
+
349
+ quad.material.opacity = 0;
350
+ clock.start();
351
+
352
+ }
353
+
354
+ // render the path tracing sample after enough time has passed
355
+ const delta = clock.getDelta() * 1e3;
356
+ const elapsedTime = clock.getElapsedTime() * 1e3;
357
+ if ( ! this.pausePathTracing && this.enablePathTracing && this.renderDelay <= elapsedTime ) {
358
+
359
+ pathTracer.update();
360
+
361
+ }
362
+
363
+ // when alpha is enabled we use a manual blending system rather than
364
+ // rendering with a blend function
365
+ pathTracer.alpha = pathTracer.material.backgroundAlpha !== 1 || ! supportsFloatBlending( renderer );
366
+ lowResPathTracer.alpha = pathTracer.alpha;
367
+
368
+ if ( this.renderToCanvas ) {
369
+
370
+ const renderer = this._renderer;
371
+ const minSamples = this.minSamples;
372
+
373
+ if ( elapsedTime >= this.renderDelay && this.samples >= this.minSamples ) {
374
+
375
+ if ( this.fadeDuration !== 0 ) {
376
+
377
+ quad.material.opacity = Math.min( quad.material.opacity + delta / this.fadeDuration, 1 );
378
+
379
+ } else {
380
+
381
+ quad.material.opacity = 1;
382
+
383
+ }
384
+
385
+ }
386
+
387
+ // render the fallback if we haven't rendered enough samples, are paused, or are occluded
388
+ if ( ! this.enablePathTracing || this.samples < minSamples || quad.material.opacity < 1 ) {
389
+
390
+ if ( this.dynamicLowRes ) {
391
+
392
+ if ( lowResPathTracer.samples < 1 ) {
393
+
394
+ lowResPathTracer.material = pathTracer.material;
395
+ lowResPathTracer.update();
396
+
397
+ }
398
+
399
+ const currentOpacity = quad.material.opacity;
400
+ quad.material.opacity = 1 - quad.material.opacity;
401
+ quad.material.map = lowResPathTracer.target.texture;
402
+ quad.render( renderer );
403
+ quad.material.opacity = currentOpacity;
404
+
405
+ } else if ( this.rasterizeScene ) {
406
+
407
+ this.rasterizeSceneCallback( this.scene, this.camera );
408
+
409
+ }
410
+
411
+ }
412
+
413
+
414
+ if ( this.enablePathTracing && quad.material.opacity > 0 ) {
415
+
416
+ if ( quad.material.opacity < 1 ) {
417
+
418
+ // use additive blending when the low res texture is rendered so we can fade the
419
+ // background out while the full res fades in
420
+ quad.material.blending = this.dynamicLowRes ? AdditiveBlending : NormalBlending;
421
+
422
+ }
423
+
424
+ quad.material.map = pathTracer.target.texture;
425
+ this.renderToCanvasCallback( pathTracer.target, renderer, quad );
426
+ quad.material.blending = NoBlending;
427
+
428
+ }
429
+
430
+ }
431
+
432
+ }
433
+
434
+ reset() {
435
+
436
+ this._queueReset = true;
437
+ this._pathTracer.samples = 0;
438
+
439
+ }
440
+
441
+ dispose() {
442
+
443
+ this._renderQuad.dispose();
444
+ this._renderQuad.material.dispose();
445
+ this._pathTracer.dispose();
446
+
447
+ }
448
+
449
+ _updateScale() {
450
+
451
+ // update the path tracer scale if it has changed
452
+ if ( this.synchronizeRenderSize ) {
453
+
454
+ this._renderer.getDrawingBufferSize( _resolution );
455
+
456
+ const w = Math.floor( this.renderScale * _resolution.x );
457
+ const h = Math.floor( this.renderScale * _resolution.y );
458
+
459
+ this._pathTracer.getSize( _resolution );
460
+ if ( _resolution.x !== w || _resolution.y !== h ) {
461
+
462
+ const lowResScale = this.lowResScale;
463
+ this._pathTracer.setSize( w, h );
464
+ this._lowResPathTracer.setSize( Math.floor( w * lowResScale ), Math.floor( h * lowResScale ) );
465
+
466
+ }
467
+
468
+ }
469
+
470
+ }
471
+
472
+ }
@@ -0,0 +1,35 @@
1
+ import { BufferGeometry } from 'three';
2
+ import { MeshDiff } from './MeshDiff.js';
3
+ import { convertToStaticGeometry } from './convertToStaticGeometry.js';
4
+
5
+ export class BakedGeometry extends BufferGeometry {
6
+
7
+ constructor() {
8
+
9
+ super();
10
+ this.version = 0;
11
+ this.hash = null;
12
+ this._diff = new MeshDiff();
13
+
14
+ }
15
+
16
+ updateFrom( mesh, options ) {
17
+
18
+ const diff = this._diff;
19
+ if ( diff.didChange( mesh ) ) {
20
+
21
+ convertToStaticGeometry( mesh, options, this );
22
+ diff.updateFrom( mesh );
23
+ this.version ++;
24
+ this.hash = `${ this.uuid }_${ this.version }`;
25
+ return true;
26
+
27
+ } else {
28
+
29
+ return false;
30
+
31
+ }
32
+
33
+ }
34
+
35
+ }
@@ -0,0 +1,64 @@
1
+ import { BufferAttribute } from 'three';
2
+
3
+ // target offset is the number of elements in the target buffer stride to skip before copying the
4
+ // attributes contents in to.
5
+ export function copyAttributeContents( attr, target, targetOffset = 0 ) {
6
+
7
+ if ( attr.isInterleavedBufferAttribute ) {
8
+
9
+ const itemSize = attr.itemSize;
10
+ for ( let i = 0, l = attr.count; i < l; i ++ ) {
11
+
12
+ const io = i + targetOffset;
13
+ target.setX( io, attr.getX( i ) );
14
+ if ( itemSize >= 2 ) target.setY( io, attr.getY( i ) );
15
+ if ( itemSize >= 3 ) target.setZ( io, attr.getZ( i ) );
16
+ if ( itemSize >= 4 ) target.setW( io, attr.getW( i ) );
17
+
18
+ }
19
+
20
+ } else {
21
+
22
+ const array = target.array;
23
+ const cons = array.constructor;
24
+ const byteOffset = array.BYTES_PER_ELEMENT * attr.itemSize * targetOffset;
25
+ const temp = new cons( array.buffer, byteOffset, attr.array.length );
26
+ temp.set( attr.array );
27
+
28
+ }
29
+
30
+ }
31
+
32
+ // Clones the given attribute with a new compatible buffer attribute but no data
33
+ export function createAttributeClone( attr, countOverride = null ) {
34
+
35
+ const cons = attr.array.constructor;
36
+ const normalized = attr.normalized;
37
+ const itemSize = attr.itemSize;
38
+ const count = countOverride === null ? attr.count : countOverride;
39
+
40
+ return new BufferAttribute( new cons( itemSize * count ), itemSize, normalized );
41
+
42
+ }
43
+
44
+ // Confirms that the two provided attributes are compatible
45
+ export function validateAttributes( attr1, attr2 ) {
46
+
47
+ if ( ! attr1 && ! attr2 ) {
48
+
49
+ return;
50
+
51
+ }
52
+
53
+ const sameCount = attr1.count === attr2.count;
54
+ const sameNormalized = attr1.normalized === attr2.normalized;
55
+ const sameType = attr1.array.constructor === attr2.array.constructor;
56
+ const sameItemSize = attr1.itemSize === attr2.itemSize;
57
+
58
+ if ( ! sameCount || ! sameNormalized || ! sameType || ! sameItemSize ) {
59
+
60
+ throw new Error();
61
+
62
+ }
63
+
64
+ }
@@ -1,6 +1,6 @@
1
1
  import { BufferAttribute } from 'three';
2
- import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
3
- export function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
2
+
3
+ export function updateMaterialIndexAttribute( geometry, materials, allMaterials ) {
4
4
 
5
5
  const indexAttr = geometry.index;
6
6
  const posAttr = geometry.attributes.position;
@@ -13,18 +13,28 @@ export function getGroupMaterialIndicesAttribute( geometry, materials, allMateri
13
13
 
14
14
  }
15
15
 
16
- // use an array with the minimum precision required to store all material id references.
17
- let materialArray;
18
- if ( allMaterials.length <= 255 ) {
16
+ let materialIndexAttribute = geometry.getAttribute( 'materialIndex' );
17
+ if ( ! materialIndexAttribute || materialIndexAttribute.count !== vertCount ) {
18
+
19
+ // use an array with the minimum precision required to store all material id references.
20
+ let array;
21
+ if ( allMaterials.length <= 255 ) {
22
+
23
+ array = new Uint8Array( vertCount );
24
+
25
+ } else {
19
26
 
20
- materialArray = new Uint8Array( vertCount );
27
+ array = new Uint16Array( vertCount );
21
28
 
22
- } else {
29
+ }
23
30
 
24
- materialArray = new Uint16Array( vertCount );
31
+ materialIndexAttribute = new BufferAttribute( array, 1, false );
32
+ geometry.deleteAttribute( 'materialIndex' );
33
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
25
34
 
26
35
  }
27
36
 
37
+ const materialArray = materialIndexAttribute.array;
28
38
  for ( let i = 0; i < groups.length; i ++ ) {
29
39
 
30
40
  const group = groups[ i ];
@@ -50,13 +60,24 @@ export function getGroupMaterialIndicesAttribute( geometry, materials, allMateri
50
60
 
51
61
  }
52
62
 
53
- return new BufferAttribute( materialArray, 1, false );
54
-
55
63
  }
56
64
 
57
- export function setCommonAttributes( geometry, options ) {
65
+ export function setCommonAttributes( geometry, attributes ) {
66
+
67
+ if ( ! geometry.index ) {
68
+
69
+ // TODO: compute a typed array
70
+ const indexCount = geometry.attributes.position.count;
71
+ const array = new Array( indexCount );
72
+ for ( let i = 0; i < indexCount; i ++ ) {
73
+
74
+ array[ i ] = i;
75
+
76
+ }
77
+
78
+ geometry.setIndex( array );
58
79
 
59
- const { attributes = [], normalMapRequired = false } = options;
80
+ }
60
81
 
61
82
  if ( ! geometry.attributes.normal && ( attributes && attributes.includes( 'normal' ) ) ) {
62
83
 
@@ -80,14 +101,8 @@ export function setCommonAttributes( geometry, options ) {
80
101
 
81
102
  if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
82
103
 
83
- if ( normalMapRequired ) {
84
-
85
- // computeTangents requires an index buffer
86
- if ( geometry.index === null ) {
87
-
88
- geometry = mergeVertices( geometry );
89
-
90
- }
104
+ // compute tangents requires a uv and normal buffer
105
+ if ( geometry.attributes.uv && geometry.attributes.normal ) {
91
106
 
92
107
  geometry.computeTangents();
93
108
 
@@ -109,19 +124,4 @@ export function setCommonAttributes( geometry, options ) {
109
124
 
110
125
  }
111
126
 
112
- if ( ! geometry.index ) {
113
-
114
- // TODO: compute a typed array
115
- const indexCount = geometry.attributes.position.count;
116
- const array = new Array( indexCount );
117
- for ( let i = 0; i < indexCount; i ++ ) {
118
-
119
- array[ i ] = i;
120
-
121
- }
122
-
123
- geometry.setIndex( array );
124
-
125
- }
126
-
127
127
  }