@luma.gl/gltf 9.3.0-alpha.6 → 9.3.0-alpha.8

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 (65) hide show
  1. package/dist/dist.dev.js +942 -141
  2. package/dist/dist.min.js +4 -4
  3. package/dist/gltf/create-gltf-model.d.ts +9 -1
  4. package/dist/gltf/create-gltf-model.d.ts.map +1 -1
  5. package/dist/gltf/create-gltf-model.js +58 -4
  6. package/dist/gltf/create-gltf-model.js.map +1 -1
  7. package/dist/gltf/create-scenegraph-from-gltf.d.ts +22 -1
  8. package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
  9. package/dist/gltf/create-scenegraph-from-gltf.js +63 -1
  10. package/dist/gltf/create-scenegraph-from-gltf.js.map +1 -1
  11. package/dist/gltf/gltf-extension-support.d.ts +10 -0
  12. package/dist/gltf/gltf-extension-support.d.ts.map +1 -0
  13. package/dist/gltf/gltf-extension-support.js +173 -0
  14. package/dist/gltf/gltf-extension-support.js.map +1 -0
  15. package/dist/index.cjs +899 -114
  16. package/dist/index.cjs.map +4 -4
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
  22. package/dist/parsers/parse-gltf-animations.js +34 -12
  23. package/dist/parsers/parse-gltf-animations.js.map +1 -1
  24. package/dist/parsers/parse-gltf-lights.d.ts.map +1 -1
  25. package/dist/parsers/parse-gltf-lights.js +86 -20
  26. package/dist/parsers/parse-gltf-lights.js.map +1 -1
  27. package/dist/parsers/parse-gltf.d.ts +3 -1
  28. package/dist/parsers/parse-gltf.d.ts.map +1 -1
  29. package/dist/parsers/parse-gltf.js +41 -9
  30. package/dist/parsers/parse-gltf.js.map +1 -1
  31. package/dist/parsers/parse-pbr-material.d.ts +69 -1
  32. package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
  33. package/dist/parsers/parse-pbr-material.js +429 -42
  34. package/dist/parsers/parse-pbr-material.js.map +1 -1
  35. package/dist/pbr/pbr-environment.d.ts.map +1 -1
  36. package/dist/pbr/pbr-environment.js +14 -12
  37. package/dist/pbr/pbr-environment.js.map +1 -1
  38. package/dist/pbr/pbr-material.d.ts +8 -3
  39. package/dist/pbr/pbr-material.d.ts.map +1 -1
  40. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +5 -5
  41. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
  42. package/dist/webgl-to-webgpu/convert-webgl-sampler.js +12 -12
  43. package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
  44. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +1 -10
  45. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
  46. package/dist/webgl-to-webgpu/convert-webgl-topology.js +1 -15
  47. package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
  48. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts +27 -0
  49. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts.map +1 -0
  50. package/dist/webgl-to-webgpu/gltf-webgl-constants.js +34 -0
  51. package/dist/webgl-to-webgpu/gltf-webgl-constants.js.map +1 -0
  52. package/package.json +5 -6
  53. package/src/gltf/create-gltf-model.ts +113 -5
  54. package/src/gltf/create-scenegraph-from-gltf.ts +97 -6
  55. package/src/gltf/gltf-extension-support.ts +214 -0
  56. package/src/index.ts +10 -1
  57. package/src/parsers/parse-gltf-animations.ts +39 -15
  58. package/src/parsers/parse-gltf-lights.ts +114 -25
  59. package/src/parsers/parse-gltf.ts +86 -19
  60. package/src/parsers/parse-pbr-material.ts +664 -69
  61. package/src/pbr/pbr-environment.ts +29 -16
  62. package/src/pbr/pbr-material.ts +13 -3
  63. package/src/webgl-to-webgpu/convert-webgl-sampler.ts +29 -29
  64. package/src/webgl-to-webgpu/convert-webgl-topology.ts +1 -15
  65. package/src/webgl-to-webgpu/gltf-webgl-constants.ts +35 -0
@@ -4,12 +4,12 @@
4
4
 
5
5
  import type {Device, SamplerProps, TextureFormat, TypedArray} from '@luma.gl/core';
6
6
  import {Texture, log, textureFormatDecoder} from '@luma.gl/core';
7
- import type {GLTFSampler} from '@loaders.gl/gltf';
8
- import {GL} from '@luma.gl/constants';
7
+ import type {GLTFPostprocessed, GLTFSampler} from '@loaders.gl/gltf';
9
8
 
10
9
  import {type ParsedPBRMaterial} from '../pbr/pbr-material';
11
10
  import {type PBREnvironment} from '../pbr/pbr-environment';
12
11
  import {type PBRMaterialBindings} from '@luma.gl/shadertools';
12
+ import {GLEnum} from '../webgl-to-webgpu/gltf-webgl-constants';
13
13
  import {convertSampler} from '../webgl-to-webgpu/convert-webgl-sampler';
14
14
 
15
15
  // TODO - synchronize the GLTF... types with loaders.gl
@@ -19,6 +19,7 @@ import {convertSampler} from '../webgl-to-webgpu/convert-webgl-sampler';
19
19
 
20
20
  type GLTFTexture = {
21
21
  id: string;
22
+ index?: number;
22
23
  texture: {source: {image: any}; sampler: {parameters: any}};
23
24
  uniformName?: string;
24
25
  // is this on all textures?
@@ -36,16 +37,114 @@ type GLTFPBRMetallicRoughness = {
36
37
  };
37
38
 
38
39
  type GLTFPBRMaterial = {
40
+ extensions?: GLTFMaterialExtensions;
39
41
  unlit?: boolean;
40
42
  pbrMetallicRoughness?: GLTFPBRMetallicRoughness;
41
43
  normalTexture?: GLTFTexture;
42
44
  occlusionTexture?: GLTFTexture;
43
45
  emissiveTexture?: GLTFTexture;
44
46
  emissiveFactor?: [number, number, number];
45
- alphaMode?: 'MASK' | 'BLEND';
47
+ alphaMode?: 'OPAQUE' | 'MASK' | 'BLEND';
48
+ doubleSided?: boolean;
46
49
  alphaCutoff?: number;
47
50
  };
48
51
 
52
+ type GLTFMaterialSpecularExtension = {
53
+ specularFactor?: number;
54
+ specularTexture?: GLTFTexture;
55
+ specularColorFactor?: [number, number, number];
56
+ specularColorTexture?: GLTFTexture;
57
+ };
58
+
59
+ type GLTFMaterialIorExtension = {
60
+ ior?: number;
61
+ };
62
+
63
+ type GLTFMaterialTransmissionExtension = {
64
+ transmissionFactor?: number;
65
+ transmissionTexture?: GLTFTexture;
66
+ };
67
+
68
+ type GLTFMaterialVolumeExtension = {
69
+ thicknessFactor?: number;
70
+ thicknessTexture?: GLTFTexture;
71
+ attenuationDistance?: number;
72
+ attenuationColor?: [number, number, number];
73
+ };
74
+
75
+ type GLTFMaterialClearcoatExtension = {
76
+ clearcoatFactor?: number;
77
+ clearcoatTexture?: GLTFTexture;
78
+ clearcoatRoughnessFactor?: number;
79
+ clearcoatRoughnessTexture?: GLTFTexture;
80
+ clearcoatNormalTexture?: GLTFTexture;
81
+ };
82
+
83
+ type GLTFMaterialSheenExtension = {
84
+ sheenColorFactor?: [number, number, number];
85
+ sheenColorTexture?: GLTFTexture;
86
+ sheenRoughnessFactor?: number;
87
+ sheenRoughnessTexture?: GLTFTexture;
88
+ };
89
+
90
+ type GLTFMaterialIridescenceExtension = {
91
+ iridescenceFactor?: number;
92
+ iridescenceTexture?: GLTFTexture;
93
+ iridescenceIor?: number;
94
+ iridescenceThicknessMinimum?: number;
95
+ iridescenceThicknessMaximum?: number;
96
+ iridescenceThicknessTexture?: GLTFTexture;
97
+ };
98
+
99
+ type GLTFMaterialAnisotropyExtension = {
100
+ anisotropyStrength?: number;
101
+ anisotropyRotation?: number;
102
+ anisotropyTexture?: GLTFTexture;
103
+ };
104
+
105
+ type GLTFMaterialEmissiveStrengthExtension = {
106
+ emissiveStrength?: number;
107
+ };
108
+
109
+ type GLTFMaterialExtensions = {
110
+ KHR_materials_unlit?: Record<string, never>;
111
+ KHR_materials_specular?: GLTFMaterialSpecularExtension;
112
+ KHR_materials_ior?: GLTFMaterialIorExtension;
113
+ KHR_materials_transmission?: GLTFMaterialTransmissionExtension;
114
+ KHR_materials_volume?: GLTFMaterialVolumeExtension;
115
+ KHR_materials_clearcoat?: GLTFMaterialClearcoatExtension;
116
+ KHR_materials_sheen?: GLTFMaterialSheenExtension;
117
+ KHR_materials_iridescence?: GLTFMaterialIridescenceExtension;
118
+ KHR_materials_anisotropy?: GLTFMaterialAnisotropyExtension;
119
+ KHR_materials_emissive_strength?: GLTFMaterialEmissiveStrengthExtension;
120
+ };
121
+
122
+ type TextureEnabledUniformName =
123
+ | 'baseColorMapEnabled'
124
+ | 'normalMapEnabled'
125
+ | 'emissiveMapEnabled'
126
+ | 'metallicRoughnessMapEnabled'
127
+ | 'occlusionMapEnabled'
128
+ | 'specularColorMapEnabled'
129
+ | 'specularIntensityMapEnabled'
130
+ | 'transmissionMapEnabled'
131
+ | 'clearcoatMapEnabled'
132
+ | 'clearcoatRoughnessMapEnabled'
133
+ | 'sheenColorMapEnabled'
134
+ | 'sheenRoughnessMapEnabled'
135
+ | 'iridescenceMapEnabled'
136
+ | 'anisotropyMapEnabled';
137
+
138
+ type TextureFeatureOptions = {
139
+ define?: string;
140
+ enabledUniformName?: TextureEnabledUniformName;
141
+ };
142
+
143
+ type TextureParseOptions = {
144
+ featureOptions?: TextureFeatureOptions;
145
+ gltf?: GLTFPostprocessed;
146
+ };
147
+
49
148
  export type ParsePBRMaterialOptions = {
50
149
  /** Debug PBR shader */
51
150
  pbrDebug?: boolean;
@@ -55,6 +154,10 @@ export type ParsePBRMaterialOptions = {
55
154
  useTangents?: boolean;
56
155
  /** provide an image based (texture cube) lighting environment */
57
156
  imageBasedLightingEnvironment?: PBREnvironment;
157
+ /** parent post-processed glTF, used to resolve extension texture infos */
158
+ gltf?: GLTFPostprocessed;
159
+ /** run primitive-attribute diagnostics such as missing TEXCOORD_0 / NORMAL */
160
+ validateAttributes?: boolean;
58
161
  };
59
162
 
60
163
  /**
@@ -93,7 +196,8 @@ export function parsePBRMaterial(
93
196
  imageBasedLightingEnvironment.diffuseEnvSampler.texture;
94
197
  parsedMaterial.bindings.pbr_specularEnvSampler =
95
198
  imageBasedLightingEnvironment.specularEnvSampler.texture;
96
- parsedMaterial.bindings.pbr_BrdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
199
+ parsedMaterial.bindings.pbr_brdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
200
+ parsedMaterial.uniforms.IBLenabled = true;
97
201
  parsedMaterial.uniforms.scaleIBLAmbient = [1, 1];
98
202
  }
99
203
 
@@ -114,106 +218,217 @@ export function parsePBRMaterial(
114
218
  if (options?.lights) parsedMaterial.defines['USE_LIGHTS'] = true;
115
219
 
116
220
  if (material) {
117
- parseMaterial(device, material, parsedMaterial);
221
+ if (options.validateAttributes !== false) {
222
+ warnOnMissingExpectedAttributes(material, attributes);
223
+ }
224
+ parseMaterial(device, material, parsedMaterial, options.gltf);
118
225
  }
119
226
 
120
227
  return parsedMaterial;
121
228
  }
122
229
 
230
+ function warnOnMissingExpectedAttributes(
231
+ material: GLTFPBRMaterial,
232
+ attributes: Record<string, any>
233
+ ): void {
234
+ const uvDependentTextureSlots = getUvDependentTextureSlots(material);
235
+ if (uvDependentTextureSlots.length > 0 && !attributes['TEXCOORD_0']) {
236
+ log.warn(
237
+ `glTF material uses ${uvDependentTextureSlots.join(', ')} but primitive is missing TEXCOORD_0; textured shading will sample the default UV coordinates`
238
+ )();
239
+ }
240
+
241
+ const isUnlitMaterial = Boolean(material.unlit || material.extensions?.KHR_materials_unlit);
242
+ if (isUnlitMaterial || attributes['NORMAL']) {
243
+ return;
244
+ }
245
+
246
+ const missingNormalReason = material.normalTexture
247
+ ? 'lit PBR shading with normalTexture'
248
+ : 'lit PBR shading';
249
+ log.warn(
250
+ `glTF primitive is missing NORMAL while using ${missingNormalReason}; shading will fall back to geometric normals`
251
+ )();
252
+ }
253
+
254
+ function getUvDependentTextureSlots(material: GLTFPBRMaterial): string[] {
255
+ const uvDependentTextureSlots: string[] = [];
256
+
257
+ if (material.pbrMetallicRoughness?.baseColorTexture) {
258
+ uvDependentTextureSlots.push('baseColorTexture');
259
+ }
260
+ if (material.pbrMetallicRoughness?.metallicRoughnessTexture) {
261
+ uvDependentTextureSlots.push('metallicRoughnessTexture');
262
+ }
263
+ if (material.normalTexture) {
264
+ uvDependentTextureSlots.push('normalTexture');
265
+ }
266
+ if (material.occlusionTexture) {
267
+ uvDependentTextureSlots.push('occlusionTexture');
268
+ }
269
+ if (material.emissiveTexture) {
270
+ uvDependentTextureSlots.push('emissiveTexture');
271
+ }
272
+ if (material.extensions?.KHR_materials_specular?.specularTexture) {
273
+ uvDependentTextureSlots.push('KHR_materials_specular.specularTexture');
274
+ }
275
+ if (material.extensions?.KHR_materials_specular?.specularColorTexture) {
276
+ uvDependentTextureSlots.push('KHR_materials_specular.specularColorTexture');
277
+ }
278
+ if (material.extensions?.KHR_materials_transmission?.transmissionTexture) {
279
+ uvDependentTextureSlots.push('KHR_materials_transmission.transmissionTexture');
280
+ }
281
+ if (material.extensions?.KHR_materials_clearcoat?.clearcoatTexture) {
282
+ uvDependentTextureSlots.push('KHR_materials_clearcoat.clearcoatTexture');
283
+ }
284
+ if (material.extensions?.KHR_materials_clearcoat?.clearcoatRoughnessTexture) {
285
+ uvDependentTextureSlots.push('KHR_materials_clearcoat.clearcoatRoughnessTexture');
286
+ }
287
+ if (material.extensions?.KHR_materials_sheen?.sheenColorTexture) {
288
+ uvDependentTextureSlots.push('KHR_materials_sheen.sheenColorTexture');
289
+ }
290
+ if (material.extensions?.KHR_materials_sheen?.sheenRoughnessTexture) {
291
+ uvDependentTextureSlots.push('KHR_materials_sheen.sheenRoughnessTexture');
292
+ }
293
+ if (material.extensions?.KHR_materials_iridescence?.iridescenceTexture) {
294
+ uvDependentTextureSlots.push('KHR_materials_iridescence.iridescenceTexture');
295
+ }
296
+ if (material.extensions?.KHR_materials_anisotropy?.anisotropyTexture) {
297
+ uvDependentTextureSlots.push('KHR_materials_anisotropy.anisotropyTexture');
298
+ }
299
+
300
+ return uvDependentTextureSlots;
301
+ }
302
+
123
303
  /** Parse GLTF material record */
124
304
  function parseMaterial(
125
305
  device: Device,
126
306
  material: GLTFPBRMaterial,
127
- parsedMaterial: ParsedPBRMaterial
307
+ parsedMaterial: ParsedPBRMaterial,
308
+ gltf?: GLTFPostprocessed
128
309
  ): void {
129
- parsedMaterial.uniforms.unlit = Boolean(material.unlit);
310
+ parsedMaterial.uniforms.unlit = Boolean(
311
+ material.unlit || material.extensions?.KHR_materials_unlit
312
+ );
130
313
 
131
314
  if (material.pbrMetallicRoughness) {
132
- parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial);
315
+ parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial, gltf);
133
316
  }
134
317
  if (material.normalTexture) {
135
- addTexture(
136
- device,
137
- material.normalTexture,
138
- 'pbr_normalSampler',
139
- 'HAS_NORMALMAP',
140
- parsedMaterial
141
- );
318
+ addTexture(device, material.normalTexture, 'pbr_normalSampler', parsedMaterial, {
319
+ featureOptions: {
320
+ define: 'HAS_NORMALMAP',
321
+ enabledUniformName: 'normalMapEnabled'
322
+ },
323
+ gltf
324
+ });
142
325
 
143
326
  const {scale = 1} = material.normalTexture;
144
327
  parsedMaterial.uniforms.normalScale = scale;
145
328
  }
146
329
  if (material.occlusionTexture) {
147
- addTexture(
148
- device,
149
- material.occlusionTexture,
150
- 'pbr_occlusionSampler',
151
- 'HAS_OCCLUSIONMAP',
152
- parsedMaterial
153
- );
330
+ addTexture(device, material.occlusionTexture, 'pbr_occlusionSampler', parsedMaterial, {
331
+ featureOptions: {
332
+ define: 'HAS_OCCLUSIONMAP',
333
+ enabledUniformName: 'occlusionMapEnabled'
334
+ },
335
+ gltf
336
+ });
154
337
 
155
338
  const {strength = 1} = material.occlusionTexture;
156
339
  parsedMaterial.uniforms.occlusionStrength = strength;
157
340
  }
341
+ parsedMaterial.uniforms.emissiveFactor = material.emissiveFactor || [0, 0, 0];
158
342
  if (material.emissiveTexture) {
159
- addTexture(
160
- device,
161
- material.emissiveTexture,
162
- 'pbr_emissiveSampler',
163
- 'HAS_EMISSIVEMAP',
164
- parsedMaterial
165
- );
166
- parsedMaterial.uniforms.emissiveFactor = material.emissiveFactor || [0, 0, 0];
343
+ addTexture(device, material.emissiveTexture, 'pbr_emissiveSampler', parsedMaterial, {
344
+ featureOptions: {
345
+ define: 'HAS_EMISSIVEMAP',
346
+ enabledUniformName: 'emissiveMapEnabled'
347
+ },
348
+ gltf
349
+ });
167
350
  }
168
351
 
169
- switch (material.alphaMode || 'MASK') {
170
- case 'MASK':
352
+ parseMaterialExtensions(device, material.extensions, parsedMaterial, gltf);
353
+
354
+ switch (material.alphaMode || 'OPAQUE') {
355
+ case 'OPAQUE':
356
+ break;
357
+ case 'MASK': {
171
358
  const {alphaCutoff = 0.5} = material;
172
359
  parsedMaterial.defines['ALPHA_CUTOFF'] = true;
360
+ parsedMaterial.uniforms.alphaCutoffEnabled = true;
173
361
  parsedMaterial.uniforms.alphaCutoff = alphaCutoff;
174
362
  break;
363
+ }
175
364
  case 'BLEND':
176
365
  log.warn('glTF BLEND alphaMode might not work well because it requires mesh sorting')();
177
-
178
- // WebGPU style parameters
179
- parsedMaterial.parameters.blend = true;
180
-
181
- parsedMaterial.parameters.blendColorOperation = 'add';
182
- parsedMaterial.parameters.blendColorSrcFactor = 'src-alpha';
183
- parsedMaterial.parameters.blendColorDstFactor = 'one-minus-src-alpha';
184
-
185
- parsedMaterial.parameters.blendAlphaOperation = 'add';
186
- parsedMaterial.parameters.blendAlphaSrcFactor = 'one';
187
- parsedMaterial.parameters.blendAlphaDstFactor = 'one-minus-src-alpha';
188
-
189
- // GL parameters
190
- // TODO - remove in favor of parameters
191
- parsedMaterial.glParameters['blend'] = true;
192
- parsedMaterial.glParameters['blendEquation'] = GL.FUNC_ADD;
193
- parsedMaterial.glParameters['blendFunc'] = [
194
- GL.SRC_ALPHA,
195
- GL.ONE_MINUS_SRC_ALPHA,
196
- GL.ONE,
197
- GL.ONE_MINUS_SRC_ALPHA
198
- ];
366
+ applyAlphaBlendParameters(parsedMaterial);
199
367
 
200
368
  break;
201
369
  }
202
370
  }
203
371
 
372
+ function applyAlphaBlendParameters(parsedMaterial: ParsedPBRMaterial): void {
373
+ parsedMaterial.parameters.blend = true;
374
+ parsedMaterial.parameters.blendColorOperation = 'add';
375
+ parsedMaterial.parameters.blendColorSrcFactor = 'src-alpha';
376
+ parsedMaterial.parameters.blendColorDstFactor = 'one-minus-src-alpha';
377
+ parsedMaterial.parameters.blendAlphaOperation = 'add';
378
+ parsedMaterial.parameters.blendAlphaSrcFactor = 'one';
379
+ parsedMaterial.parameters.blendAlphaDstFactor = 'one-minus-src-alpha';
380
+
381
+ parsedMaterial.glParameters['blend'] = true;
382
+ parsedMaterial.glParameters['blendEquation'] = GLEnum.FUNC_ADD;
383
+ parsedMaterial.glParameters['blendFunc'] = [
384
+ GLEnum.SRC_ALPHA,
385
+ GLEnum.ONE_MINUS_SRC_ALPHA,
386
+ GLEnum.ONE,
387
+ GLEnum.ONE_MINUS_SRC_ALPHA
388
+ ];
389
+ }
390
+
391
+ function applyTransmissionBlendApproximation(parsedMaterial: ParsedPBRMaterial): void {
392
+ parsedMaterial.parameters.blend = true;
393
+ parsedMaterial.parameters.depthWriteEnabled = false;
394
+ parsedMaterial.parameters.blendColorOperation = 'add';
395
+ parsedMaterial.parameters.blendColorSrcFactor = 'one';
396
+ parsedMaterial.parameters.blendColorDstFactor = 'one-minus-src-alpha';
397
+ parsedMaterial.parameters.blendAlphaOperation = 'add';
398
+ parsedMaterial.parameters.blendAlphaSrcFactor = 'one';
399
+ parsedMaterial.parameters.blendAlphaDstFactor = 'one-minus-src-alpha';
400
+
401
+ parsedMaterial.glParameters['blend'] = true;
402
+ parsedMaterial.glParameters['depthMask'] = false;
403
+ parsedMaterial.glParameters['blendEquation'] = GLEnum.FUNC_ADD;
404
+ parsedMaterial.glParameters['blendFunc'] = [
405
+ GLEnum.ONE,
406
+ GLEnum.ONE_MINUS_SRC_ALPHA,
407
+ GLEnum.ONE,
408
+ GLEnum.ONE_MINUS_SRC_ALPHA
409
+ ];
410
+ }
411
+
204
412
  /** Parse GLTF material sub record */
205
413
  function parsePbrMetallicRoughness(
206
414
  device: Device,
207
415
  pbrMetallicRoughness: GLTFPBRMetallicRoughness,
208
- parsedMaterial: ParsedPBRMaterial
416
+ parsedMaterial: ParsedPBRMaterial,
417
+ gltf?: GLTFPostprocessed
209
418
  ): void {
210
419
  if (pbrMetallicRoughness.baseColorTexture) {
211
420
  addTexture(
212
421
  device,
213
422
  pbrMetallicRoughness.baseColorTexture,
214
423
  'pbr_baseColorSampler',
215
- 'HAS_BASECOLORMAP',
216
- parsedMaterial
424
+ parsedMaterial,
425
+ {
426
+ featureOptions: {
427
+ define: 'HAS_BASECOLORMAP',
428
+ enabledUniformName: 'baseColorMapEnabled'
429
+ },
430
+ gltf
431
+ }
217
432
  );
218
433
  }
219
434
  parsedMaterial.uniforms.baseColorFactor = pbrMetallicRoughness.baseColorFactor || [1, 1, 1, 1];
@@ -223,34 +438,374 @@ function parsePbrMetallicRoughness(
223
438
  device,
224
439
  pbrMetallicRoughness.metallicRoughnessTexture,
225
440
  'pbr_metallicRoughnessSampler',
226
- 'HAS_METALROUGHNESSMAP',
227
- parsedMaterial
441
+ parsedMaterial,
442
+ {
443
+ featureOptions: {
444
+ define: 'HAS_METALROUGHNESSMAP',
445
+ enabledUniformName: 'metallicRoughnessMapEnabled'
446
+ },
447
+ gltf
448
+ }
228
449
  );
229
450
  }
230
451
  const {metallicFactor = 1, roughnessFactor = 1} = pbrMetallicRoughness;
231
452
  parsedMaterial.uniforms.metallicRoughnessValues = [metallicFactor, roughnessFactor];
232
453
  }
233
454
 
455
+ function parseMaterialExtensions(
456
+ device: Device,
457
+ extensions: GLTFMaterialExtensions | undefined,
458
+ parsedMaterial: ParsedPBRMaterial,
459
+ gltf?: GLTFPostprocessed
460
+ ): void {
461
+ if (!extensions) {
462
+ return;
463
+ }
464
+
465
+ if (hasMaterialExtensionShading(extensions)) {
466
+ parsedMaterial.defines['USE_MATERIAL_EXTENSIONS'] = true;
467
+ }
468
+
469
+ parseSpecularExtension(device, extensions.KHR_materials_specular, parsedMaterial, gltf);
470
+ parseIorExtension(extensions.KHR_materials_ior, parsedMaterial);
471
+ parseTransmissionExtension(device, extensions.KHR_materials_transmission, parsedMaterial, gltf);
472
+ parseVolumeExtension(device, extensions.KHR_materials_volume, parsedMaterial, gltf);
473
+ parseClearcoatExtension(device, extensions.KHR_materials_clearcoat, parsedMaterial, gltf);
474
+ parseSheenExtension(device, extensions.KHR_materials_sheen, parsedMaterial, gltf);
475
+ parseIridescenceExtension(device, extensions.KHR_materials_iridescence, parsedMaterial, gltf);
476
+ parseAnisotropyExtension(device, extensions.KHR_materials_anisotropy, parsedMaterial, gltf);
477
+ parseEmissiveStrengthExtension(extensions.KHR_materials_emissive_strength, parsedMaterial);
478
+ }
479
+
480
+ function hasMaterialExtensionShading(extensions: GLTFMaterialExtensions): boolean {
481
+ return Boolean(
482
+ extensions.KHR_materials_specular ||
483
+ extensions.KHR_materials_ior ||
484
+ extensions.KHR_materials_transmission ||
485
+ extensions.KHR_materials_volume ||
486
+ extensions.KHR_materials_clearcoat ||
487
+ extensions.KHR_materials_sheen ||
488
+ extensions.KHR_materials_iridescence ||
489
+ extensions.KHR_materials_anisotropy
490
+ );
491
+ }
492
+
493
+ function parseSpecularExtension(
494
+ device: Device,
495
+ extension: GLTFMaterialSpecularExtension | undefined,
496
+ parsedMaterial: ParsedPBRMaterial,
497
+ gltf?: GLTFPostprocessed
498
+ ): void {
499
+ if (!extension) {
500
+ return;
501
+ }
502
+
503
+ if (extension.specularColorFactor) {
504
+ parsedMaterial.uniforms.specularColorFactor = extension.specularColorFactor;
505
+ }
506
+ if (extension.specularFactor !== undefined) {
507
+ parsedMaterial.uniforms.specularIntensityFactor = extension.specularFactor;
508
+ }
509
+ if (extension.specularColorTexture) {
510
+ addTexture(device, extension.specularColorTexture, 'pbr_specularColorSampler', parsedMaterial, {
511
+ featureOptions: {
512
+ define: 'HAS_SPECULARCOLORMAP',
513
+ enabledUniformName: 'specularColorMapEnabled'
514
+ },
515
+ gltf
516
+ });
517
+ }
518
+ if (extension.specularTexture) {
519
+ addTexture(device, extension.specularTexture, 'pbr_specularIntensitySampler', parsedMaterial, {
520
+ featureOptions: {
521
+ define: 'HAS_SPECULARINTENSITYMAP',
522
+ enabledUniformName: 'specularIntensityMapEnabled'
523
+ },
524
+ gltf
525
+ });
526
+ }
527
+ }
528
+
529
+ function parseIorExtension(
530
+ extension: GLTFMaterialIorExtension | undefined,
531
+ parsedMaterial: ParsedPBRMaterial
532
+ ): void {
533
+ if (extension?.ior !== undefined) {
534
+ parsedMaterial.uniforms.ior = extension.ior;
535
+ }
536
+ }
537
+
538
+ function parseTransmissionExtension(
539
+ device: Device,
540
+ extension: GLTFMaterialTransmissionExtension | undefined,
541
+ parsedMaterial: ParsedPBRMaterial,
542
+ gltf?: GLTFPostprocessed
543
+ ): void {
544
+ if (!extension) {
545
+ return;
546
+ }
547
+
548
+ if (extension.transmissionFactor !== undefined) {
549
+ parsedMaterial.uniforms.transmissionFactor = extension.transmissionFactor;
550
+ }
551
+ if (extension.transmissionTexture) {
552
+ addTexture(device, extension.transmissionTexture, 'pbr_transmissionSampler', parsedMaterial, {
553
+ featureOptions: {
554
+ define: 'HAS_TRANSMISSIONMAP',
555
+ enabledUniformName: 'transmissionMapEnabled'
556
+ },
557
+ gltf
558
+ });
559
+ }
560
+
561
+ if ((extension.transmissionFactor ?? 0) > 0 || extension.transmissionTexture) {
562
+ log.warn(
563
+ 'KHR_materials_transmission uses a premultiplied-alpha blending approximation and may require mesh sorting'
564
+ )();
565
+ applyTransmissionBlendApproximation(parsedMaterial);
566
+ }
567
+ }
568
+
569
+ function parseVolumeExtension(
570
+ device: Device,
571
+ extension: GLTFMaterialVolumeExtension | undefined,
572
+ parsedMaterial: ParsedPBRMaterial,
573
+ gltf?: GLTFPostprocessed
574
+ ): void {
575
+ if (!extension) {
576
+ return;
577
+ }
578
+
579
+ if (extension.thicknessFactor !== undefined) {
580
+ parsedMaterial.uniforms.thicknessFactor = extension.thicknessFactor;
581
+ }
582
+ if (extension.thicknessTexture) {
583
+ addTexture(device, extension.thicknessTexture, 'pbr_thicknessSampler', parsedMaterial, {
584
+ featureOptions: {
585
+ define: 'HAS_THICKNESSMAP'
586
+ },
587
+ gltf
588
+ });
589
+ }
590
+ if (extension.attenuationDistance !== undefined) {
591
+ parsedMaterial.uniforms.attenuationDistance = extension.attenuationDistance;
592
+ }
593
+ if (extension.attenuationColor) {
594
+ parsedMaterial.uniforms.attenuationColor = extension.attenuationColor;
595
+ }
596
+ }
597
+
598
+ function parseClearcoatExtension(
599
+ device: Device,
600
+ extension: GLTFMaterialClearcoatExtension | undefined,
601
+ parsedMaterial: ParsedPBRMaterial,
602
+ gltf?: GLTFPostprocessed
603
+ ): void {
604
+ if (!extension) {
605
+ return;
606
+ }
607
+
608
+ if (extension.clearcoatFactor !== undefined) {
609
+ parsedMaterial.uniforms.clearcoatFactor = extension.clearcoatFactor;
610
+ }
611
+ if (extension.clearcoatRoughnessFactor !== undefined) {
612
+ parsedMaterial.uniforms.clearcoatRoughnessFactor = extension.clearcoatRoughnessFactor;
613
+ }
614
+ if (extension.clearcoatTexture) {
615
+ addTexture(device, extension.clearcoatTexture, 'pbr_clearcoatSampler', parsedMaterial, {
616
+ featureOptions: {
617
+ define: 'HAS_CLEARCOATMAP',
618
+ enabledUniformName: 'clearcoatMapEnabled'
619
+ },
620
+ gltf
621
+ });
622
+ }
623
+ if (extension.clearcoatRoughnessTexture) {
624
+ addTexture(
625
+ device,
626
+ extension.clearcoatRoughnessTexture,
627
+ 'pbr_clearcoatRoughnessSampler',
628
+ parsedMaterial,
629
+ {
630
+ featureOptions: {
631
+ define: 'HAS_CLEARCOATROUGHNESSMAP',
632
+ enabledUniformName: 'clearcoatRoughnessMapEnabled'
633
+ },
634
+ gltf
635
+ }
636
+ );
637
+ }
638
+ if (extension.clearcoatNormalTexture) {
639
+ addTexture(
640
+ device,
641
+ extension.clearcoatNormalTexture,
642
+ 'pbr_clearcoatNormalSampler',
643
+ parsedMaterial,
644
+ {
645
+ featureOptions: {
646
+ define: 'HAS_CLEARCOATNORMALMAP'
647
+ },
648
+ gltf
649
+ }
650
+ );
651
+ }
652
+ }
653
+
654
+ function parseSheenExtension(
655
+ device: Device,
656
+ extension: GLTFMaterialSheenExtension | undefined,
657
+ parsedMaterial: ParsedPBRMaterial,
658
+ gltf?: GLTFPostprocessed
659
+ ): void {
660
+ if (!extension) {
661
+ return;
662
+ }
663
+
664
+ if (extension.sheenColorFactor) {
665
+ parsedMaterial.uniforms.sheenColorFactor = extension.sheenColorFactor;
666
+ }
667
+ if (extension.sheenRoughnessFactor !== undefined) {
668
+ parsedMaterial.uniforms.sheenRoughnessFactor = extension.sheenRoughnessFactor;
669
+ }
670
+ if (extension.sheenColorTexture) {
671
+ addTexture(device, extension.sheenColorTexture, 'pbr_sheenColorSampler', parsedMaterial, {
672
+ featureOptions: {
673
+ define: 'HAS_SHEENCOLORMAP',
674
+ enabledUniformName: 'sheenColorMapEnabled'
675
+ },
676
+ gltf
677
+ });
678
+ }
679
+ if (extension.sheenRoughnessTexture) {
680
+ addTexture(
681
+ device,
682
+ extension.sheenRoughnessTexture,
683
+ 'pbr_sheenRoughnessSampler',
684
+ parsedMaterial,
685
+ {
686
+ featureOptions: {
687
+ define: 'HAS_SHEENROUGHNESSMAP',
688
+ enabledUniformName: 'sheenRoughnessMapEnabled'
689
+ },
690
+ gltf
691
+ }
692
+ );
693
+ }
694
+ }
695
+
696
+ function parseIridescenceExtension(
697
+ device: Device,
698
+ extension: GLTFMaterialIridescenceExtension | undefined,
699
+ parsedMaterial: ParsedPBRMaterial,
700
+ gltf?: GLTFPostprocessed
701
+ ): void {
702
+ if (!extension) {
703
+ return;
704
+ }
705
+
706
+ if (extension.iridescenceFactor !== undefined) {
707
+ parsedMaterial.uniforms.iridescenceFactor = extension.iridescenceFactor;
708
+ }
709
+ if (extension.iridescenceIor !== undefined) {
710
+ parsedMaterial.uniforms.iridescenceIor = extension.iridescenceIor;
711
+ }
712
+ if (
713
+ extension.iridescenceThicknessMinimum !== undefined ||
714
+ extension.iridescenceThicknessMaximum !== undefined
715
+ ) {
716
+ parsedMaterial.uniforms.iridescenceThicknessRange = [
717
+ extension.iridescenceThicknessMinimum ?? 100,
718
+ extension.iridescenceThicknessMaximum ?? 400
719
+ ];
720
+ }
721
+ if (extension.iridescenceTexture) {
722
+ addTexture(device, extension.iridescenceTexture, 'pbr_iridescenceSampler', parsedMaterial, {
723
+ featureOptions: {
724
+ define: 'HAS_IRIDESCENCEMAP',
725
+ enabledUniformName: 'iridescenceMapEnabled'
726
+ },
727
+ gltf
728
+ });
729
+ }
730
+ if (extension.iridescenceThicknessTexture) {
731
+ addTexture(
732
+ device,
733
+ extension.iridescenceThicknessTexture,
734
+ 'pbr_iridescenceThicknessSampler',
735
+ parsedMaterial,
736
+ {
737
+ featureOptions: {
738
+ define: 'HAS_IRIDESCENCETHICKNESSMAP'
739
+ },
740
+ gltf
741
+ }
742
+ );
743
+ }
744
+ }
745
+
746
+ function parseAnisotropyExtension(
747
+ device: Device,
748
+ extension: GLTFMaterialAnisotropyExtension | undefined,
749
+ parsedMaterial: ParsedPBRMaterial,
750
+ gltf?: GLTFPostprocessed
751
+ ): void {
752
+ if (!extension) {
753
+ return;
754
+ }
755
+
756
+ if (extension.anisotropyStrength !== undefined) {
757
+ parsedMaterial.uniforms.anisotropyStrength = extension.anisotropyStrength;
758
+ }
759
+ if (extension.anisotropyRotation !== undefined) {
760
+ parsedMaterial.uniforms.anisotropyRotation = extension.anisotropyRotation;
761
+ }
762
+ if (extension.anisotropyTexture) {
763
+ addTexture(device, extension.anisotropyTexture, 'pbr_anisotropySampler', parsedMaterial, {
764
+ featureOptions: {
765
+ define: 'HAS_ANISOTROPYMAP',
766
+ enabledUniformName: 'anisotropyMapEnabled'
767
+ },
768
+ gltf
769
+ });
770
+ }
771
+ }
772
+
773
+ function parseEmissiveStrengthExtension(
774
+ extension: GLTFMaterialEmissiveStrengthExtension | undefined,
775
+ parsedMaterial: ParsedPBRMaterial
776
+ ): void {
777
+ if (extension?.emissiveStrength !== undefined) {
778
+ parsedMaterial.uniforms.emissiveStrength = extension.emissiveStrength;
779
+ }
780
+ }
781
+
234
782
  /** Create a texture from a glTF texture/sampler/image combo and add it to bindings */
235
783
  function addTexture(
236
784
  device: Device,
237
785
  gltfTexture: GLTFTexture,
238
786
  uniformName: keyof PBRMaterialBindings,
239
- define: string,
240
- parsedMaterial: ParsedPBRMaterial
787
+ parsedMaterial: ParsedPBRMaterial,
788
+ textureParseOptions: TextureParseOptions = {}
241
789
  ): void {
242
- const image = gltfTexture.texture.source.image;
790
+ const {featureOptions = {}, gltf} = textureParseOptions;
791
+ const {define, enabledUniformName} = featureOptions;
792
+ const resolvedTextureInfo = resolveTextureInfo(gltfTexture, gltf);
793
+ const image = resolvedTextureInfo.texture?.source?.image;
794
+ if (!image) {
795
+ log.warn(`Skipping unresolved glTF texture for ${String(uniformName)}`)();
796
+ return;
797
+ }
243
798
 
244
799
  const gltfSampler = {
245
800
  wrapS: 10497, // default REPEAT S (U) wrapping mode.
246
801
  wrapT: 10497, // default REPEAT T (V) wrapping mode.
247
802
  minFilter: 9729, // default LINEAR filtering
248
803
  magFilter: 9729, // default LINEAR filtering
249
- ...gltfTexture?.texture?.sampler
804
+ ...resolvedTextureInfo?.texture?.sampler
250
805
  } as GLTFSampler;
251
806
 
252
807
  const baseOptions = {
253
- id: gltfTexture.uniformName || gltfTexture.id,
808
+ id: resolvedTextureInfo.uniformName || resolvedTextureInfo.id,
254
809
  sampler: convertSampler(gltfSampler)
255
810
  };
256
811
 
@@ -270,9 +825,43 @@ function addTexture(
270
825
 
271
826
  parsedMaterial.bindings[uniformName] = texture;
272
827
  if (define) parsedMaterial.defines[define] = true;
828
+ if (enabledUniformName) {
829
+ parsedMaterial.uniforms[enabledUniformName] = true;
830
+ }
273
831
  parsedMaterial.generatedTextures.push(texture);
274
832
  }
275
833
 
834
+ function resolveTextureInfo(gltfTexture: GLTFTexture, gltf?: GLTFPostprocessed): GLTFTexture {
835
+ if (gltfTexture.texture || gltfTexture.index === undefined || !gltf?.textures) {
836
+ return gltfTexture;
837
+ }
838
+
839
+ const resolvedTextureEntry = gltf.textures[gltfTexture.index] as
840
+ | Partial<GLTFTexture>
841
+ | GLTFTexture['texture']
842
+ | undefined;
843
+ if (!resolvedTextureEntry) {
844
+ return gltfTexture;
845
+ }
846
+
847
+ if ('texture' in resolvedTextureEntry && resolvedTextureEntry.texture) {
848
+ return {
849
+ ...resolvedTextureEntry,
850
+ ...gltfTexture,
851
+ texture: resolvedTextureEntry.texture
852
+ } as GLTFTexture;
853
+ }
854
+
855
+ if (!('source' in resolvedTextureEntry)) {
856
+ return gltfTexture;
857
+ }
858
+
859
+ return {
860
+ ...gltfTexture,
861
+ texture: resolvedTextureEntry
862
+ };
863
+ }
864
+
276
865
  /** One mip level as produced by loaders.gl compressed texture parsers */
277
866
  export type CompressedMipLevel = {
278
867
  data: TypedArray;
@@ -502,7 +1091,7 @@ export class PBRMaterialParser {
502
1091
  if (imageBasedLightingEnvironment) {
503
1092
  this.bindings.pbr_diffuseEnvSampler = imageBasedLightingEnvironment.getDiffuseEnvSampler();
504
1093
  this.bindings.pbr_specularEnvSampler = imageBasedLightingEnvironment.getSpecularEnvSampler();
505
- this.bindings.pbr_BrdfLUT = imageBasedLightingEnvironment.getBrdfTexture();
1094
+ this.bindings.pbr_brdfLUT = imageBasedLightingEnvironment.getBrdfTexture();
506
1095
  this.uniforms.scaleIBLAmbient = [1, 1];
507
1096
  }
508
1097
 
@@ -572,8 +1161,13 @@ export class PBRMaterialParser {
572
1161
  log.warn('BLEND alphaMode might not work well because it requires mesh sorting')();
573
1162
  Object.assign(this.parameters, {
574
1163
  blend: true,
575
- blendEquation: GL.FUNC_ADD,
576
- blendFunc: [GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA]
1164
+ blendEquation: GLEnum.FUNC_ADD,
1165
+ blendFunc: [
1166
+ GLEnum.SRC_ALPHA,
1167
+ GLEnum.ONE_MINUS_SRC_ALPHA,
1168
+ GLEnum.ONE,
1169
+ GLEnum.ONE_MINUS_SRC_ALPHA
1170
+ ]
577
1171
  });
578
1172
  }
579
1173
  }
@@ -610,7 +1204,8 @@ export class PBRMaterialParser {
610
1204
  if (image.compressed) {
611
1205
  textureOptions = image;
612
1206
  specialTextureParameters = {
613
- [GL.TEXTURE_MIN_FILTER]: image.data.length > 1 ? GL.LINEAR_MIPMAP_NEAREST : GL.LINEAR
1207
+ [GLEnum.TEXTURE_MIN_FILTER]:
1208
+ image.data.length > 1 ? GLEnum.LINEAR_MIPMAP_NEAREST : GLEnum.LINEAR
614
1209
  };
615
1210
  } else {
616
1211
  // Texture2D accepts a promise that returns an image as data (Async Textures)
@@ -624,7 +1219,7 @@ export class PBRMaterialParser {
624
1219
  ...specialTextureParameters
625
1220
  },
626
1221
  pixelStore: {
627
- [GL.UNPACK_FLIP_Y_WEBGL]: false
1222
+ [GLEnum.UNPACK_FLIP_Y_WEBGL]: false
628
1223
  },
629
1224
  ...textureOptions
630
1225
  });