@luma.gl/gltf 9.2.5 → 9.3.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dist.dev.js +1362 -313
- package/dist/dist.min.js +98 -46
- package/dist/gltf/animations/animations.d.ts +16 -4
- package/dist/gltf/animations/animations.d.ts.map +1 -1
- package/dist/gltf/animations/interpolate.d.ts +4 -3
- package/dist/gltf/animations/interpolate.d.ts.map +1 -1
- package/dist/gltf/animations/interpolate.js +27 -36
- package/dist/gltf/animations/interpolate.js.map +1 -1
- package/dist/gltf/create-gltf-model.d.ts +15 -1
- package/dist/gltf/create-gltf-model.d.ts.map +1 -1
- package/dist/gltf/create-gltf-model.js +154 -48
- package/dist/gltf/create-gltf-model.js.map +1 -1
- package/dist/gltf/create-scenegraph-from-gltf.d.ts +39 -2
- package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
- package/dist/gltf/create-scenegraph-from-gltf.js +76 -6
- package/dist/gltf/create-scenegraph-from-gltf.js.map +1 -1
- package/dist/gltf/gltf-animator.d.ts +26 -0
- package/dist/gltf/gltf-animator.d.ts.map +1 -1
- package/dist/gltf/gltf-animator.js +22 -19
- package/dist/gltf/gltf-animator.js.map +1 -1
- package/dist/gltf/gltf-extension-support.d.ts +10 -0
- package/dist/gltf/gltf-extension-support.d.ts.map +1 -0
- package/dist/gltf/gltf-extension-support.js +173 -0
- package/dist/gltf/gltf-extension-support.js.map +1 -0
- package/dist/index.cjs +1302 -276
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/parse-gltf-animations.d.ts +1 -0
- package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
- package/dist/parsers/parse-gltf-animations.js +73 -28
- package/dist/parsers/parse-gltf-animations.js.map +1 -1
- package/dist/parsers/parse-gltf-lights.d.ts +5 -0
- package/dist/parsers/parse-gltf-lights.d.ts.map +1 -0
- package/dist/parsers/parse-gltf-lights.js +163 -0
- package/dist/parsers/parse-gltf-lights.js.map +1 -0
- package/dist/parsers/parse-gltf.d.ts +19 -2
- package/dist/parsers/parse-gltf.d.ts.map +1 -1
- package/dist/parsers/parse-gltf.js +101 -61
- package/dist/parsers/parse-gltf.js.map +1 -1
- package/dist/parsers/parse-pbr-material.d.ts +115 -2
- package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
- package/dist/parsers/parse-pbr-material.js +570 -54
- package/dist/parsers/parse-pbr-material.js.map +1 -1
- package/dist/pbr/pbr-environment.d.ts +10 -4
- package/dist/pbr/pbr-environment.d.ts.map +1 -1
- package/dist/pbr/pbr-environment.js +18 -15
- package/dist/pbr/pbr-environment.js.map +1 -1
- package/dist/pbr/pbr-material.d.ts +13 -3
- package/dist/pbr/pbr-material.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts +12 -1
- package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-attribute.js +3 -0
- package/dist/webgl-to-webgpu/convert-webgl-attribute.js.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +11 -5
- package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-sampler.js +16 -12
- package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +2 -9
- package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-topology.js +2 -14
- package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
- package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts +27 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts.map +1 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.js +34 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.js.map +1 -0
- package/package.json +8 -9
- package/src/gltf/animations/animations.ts +17 -5
- package/src/gltf/animations/interpolate.ts +49 -68
- package/src/gltf/create-gltf-model.ts +214 -48
- package/src/gltf/create-scenegraph-from-gltf.ts +134 -11
- package/src/gltf/gltf-animator.ts +34 -25
- package/src/gltf/gltf-extension-support.ts +214 -0
- package/src/index.ts +11 -2
- package/src/parsers/parse-gltf-animations.ts +94 -33
- package/src/parsers/parse-gltf-lights.ts +218 -0
- package/src/parsers/parse-gltf.ts +170 -90
- package/src/parsers/parse-pbr-material.ts +870 -80
- package/src/pbr/pbr-environment.ts +44 -21
- package/src/pbr/pbr-material.ts +18 -3
- package/src/webgl-to-webgpu/convert-webgl-attribute.ts +12 -1
- package/src/webgl-to-webgpu/convert-webgl-sampler.ts +38 -29
- package/src/webgl-to-webgpu/convert-webgl-topology.ts +2 -14
- package/src/webgl-to-webgpu/gltf-webgl-constants.ts +35 -0
- package/dist/utils/deep-copy.d.ts +0 -3
- package/dist/utils/deep-copy.d.ts.map +0 -1
- package/dist/utils/deep-copy.js +0 -21
- package/dist/utils/deep-copy.js.map +0 -1
- package/src/utils/deep-copy.ts +0 -22
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import type {Device,
|
|
6
|
-
import {
|
|
5
|
+
import type {Device, SamplerProps, TextureFormat, TypedArray} from '@luma.gl/core';
|
|
6
|
+
import {Texture, log, textureFormatDecoder} from '@luma.gl/core';
|
|
7
|
+
import type {GLTFPostprocessed, GLTFSampler} from '@loaders.gl/gltf';
|
|
7
8
|
|
|
8
|
-
import {log} from '@luma.gl/core';
|
|
9
9
|
import {type ParsedPBRMaterial} from '../pbr/pbr-material';
|
|
10
10
|
import {type PBREnvironment} from '../pbr/pbr-environment';
|
|
11
11
|
import {type PBRMaterialBindings} from '@luma.gl/shadertools';
|
|
12
|
+
import {GLEnum} from '../webgl-to-webgpu/gltf-webgl-constants';
|
|
12
13
|
import {convertSampler} from '../webgl-to-webgpu/convert-webgl-sampler';
|
|
13
14
|
|
|
14
15
|
// TODO - synchronize the GLTF... types with loaders.gl
|
|
@@ -18,6 +19,7 @@ import {convertSampler} from '../webgl-to-webgpu/convert-webgl-sampler';
|
|
|
18
19
|
|
|
19
20
|
type GLTFTexture = {
|
|
20
21
|
id: string;
|
|
22
|
+
index?: number;
|
|
21
23
|
texture: {source: {image: any}; sampler: {parameters: any}};
|
|
22
24
|
uniformName?: string;
|
|
23
25
|
// is this on all textures?
|
|
@@ -35,16 +37,114 @@ type GLTFPBRMetallicRoughness = {
|
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
type GLTFPBRMaterial = {
|
|
40
|
+
extensions?: GLTFMaterialExtensions;
|
|
38
41
|
unlit?: boolean;
|
|
39
42
|
pbrMetallicRoughness?: GLTFPBRMetallicRoughness;
|
|
40
43
|
normalTexture?: GLTFTexture;
|
|
41
44
|
occlusionTexture?: GLTFTexture;
|
|
42
45
|
emissiveTexture?: GLTFTexture;
|
|
43
46
|
emissiveFactor?: [number, number, number];
|
|
44
|
-
alphaMode?: 'MASK' | 'BLEND';
|
|
47
|
+
alphaMode?: 'OPAQUE' | 'MASK' | 'BLEND';
|
|
48
|
+
doubleSided?: boolean;
|
|
45
49
|
alphaCutoff?: number;
|
|
46
50
|
};
|
|
47
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
|
+
|
|
48
148
|
export type ParsePBRMaterialOptions = {
|
|
49
149
|
/** Debug PBR shader */
|
|
50
150
|
pbrDebug?: boolean;
|
|
@@ -54,6 +154,10 @@ export type ParsePBRMaterialOptions = {
|
|
|
54
154
|
useTangents?: boolean;
|
|
55
155
|
/** provide an image based (texture cube) lighting environment */
|
|
56
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;
|
|
57
161
|
};
|
|
58
162
|
|
|
59
163
|
/**
|
|
@@ -92,7 +196,8 @@ export function parsePBRMaterial(
|
|
|
92
196
|
imageBasedLightingEnvironment.diffuseEnvSampler.texture;
|
|
93
197
|
parsedMaterial.bindings.pbr_specularEnvSampler =
|
|
94
198
|
imageBasedLightingEnvironment.specularEnvSampler.texture;
|
|
95
|
-
parsedMaterial.bindings.
|
|
199
|
+
parsedMaterial.bindings.pbr_brdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
|
|
200
|
+
parsedMaterial.uniforms.IBLenabled = true;
|
|
96
201
|
parsedMaterial.uniforms.scaleIBLAmbient = [1, 1];
|
|
97
202
|
}
|
|
98
203
|
|
|
@@ -106,111 +211,224 @@ export function parsePBRMaterial(
|
|
|
106
211
|
if (attributes['NORMAL']) parsedMaterial.defines['HAS_NORMALS'] = true;
|
|
107
212
|
if (attributes['TANGENT'] && options?.useTangents) parsedMaterial.defines['HAS_TANGENTS'] = true;
|
|
108
213
|
if (attributes['TEXCOORD_0']) parsedMaterial.defines['HAS_UV'] = true;
|
|
214
|
+
if (attributes['JOINTS_0'] && attributes['WEIGHTS_0']) parsedMaterial.defines['HAS_SKIN'] = true;
|
|
215
|
+
if (attributes['COLOR_0']) parsedMaterial.defines['HAS_COLORS'] = true;
|
|
109
216
|
|
|
110
217
|
if (options?.imageBasedLightingEnvironment) parsedMaterial.defines['USE_IBL'] = true;
|
|
111
218
|
if (options?.lights) parsedMaterial.defines['USE_LIGHTS'] = true;
|
|
112
219
|
|
|
113
220
|
if (material) {
|
|
114
|
-
|
|
221
|
+
if (options.validateAttributes !== false) {
|
|
222
|
+
warnOnMissingExpectedAttributes(material, attributes);
|
|
223
|
+
}
|
|
224
|
+
parseMaterial(device, material, parsedMaterial, options.gltf);
|
|
115
225
|
}
|
|
116
226
|
|
|
117
227
|
return parsedMaterial;
|
|
118
228
|
}
|
|
119
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
|
+
|
|
120
303
|
/** Parse GLTF material record */
|
|
121
304
|
function parseMaterial(
|
|
122
305
|
device: Device,
|
|
123
306
|
material: GLTFPBRMaterial,
|
|
124
|
-
parsedMaterial: ParsedPBRMaterial
|
|
307
|
+
parsedMaterial: ParsedPBRMaterial,
|
|
308
|
+
gltf?: GLTFPostprocessed
|
|
125
309
|
): void {
|
|
126
|
-
parsedMaterial.uniforms.unlit = Boolean(
|
|
310
|
+
parsedMaterial.uniforms.unlit = Boolean(
|
|
311
|
+
material.unlit || material.extensions?.KHR_materials_unlit
|
|
312
|
+
);
|
|
127
313
|
|
|
128
314
|
if (material.pbrMetallicRoughness) {
|
|
129
|
-
parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial);
|
|
315
|
+
parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial, gltf);
|
|
130
316
|
}
|
|
131
317
|
if (material.normalTexture) {
|
|
132
|
-
addTexture(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
);
|
|
318
|
+
addTexture(device, material.normalTexture, 'pbr_normalSampler', parsedMaterial, {
|
|
319
|
+
featureOptions: {
|
|
320
|
+
define: 'HAS_NORMALMAP',
|
|
321
|
+
enabledUniformName: 'normalMapEnabled'
|
|
322
|
+
},
|
|
323
|
+
gltf
|
|
324
|
+
});
|
|
139
325
|
|
|
140
326
|
const {scale = 1} = material.normalTexture;
|
|
141
327
|
parsedMaterial.uniforms.normalScale = scale;
|
|
142
328
|
}
|
|
143
329
|
if (material.occlusionTexture) {
|
|
144
|
-
addTexture(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
);
|
|
330
|
+
addTexture(device, material.occlusionTexture, 'pbr_occlusionSampler', parsedMaterial, {
|
|
331
|
+
featureOptions: {
|
|
332
|
+
define: 'HAS_OCCLUSIONMAP',
|
|
333
|
+
enabledUniformName: 'occlusionMapEnabled'
|
|
334
|
+
},
|
|
335
|
+
gltf
|
|
336
|
+
});
|
|
151
337
|
|
|
152
338
|
const {strength = 1} = material.occlusionTexture;
|
|
153
339
|
parsedMaterial.uniforms.occlusionStrength = strength;
|
|
154
340
|
}
|
|
341
|
+
parsedMaterial.uniforms.emissiveFactor = material.emissiveFactor || [0, 0, 0];
|
|
155
342
|
if (material.emissiveTexture) {
|
|
156
|
-
addTexture(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
);
|
|
163
|
-
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
|
+
});
|
|
164
350
|
}
|
|
165
351
|
|
|
166
|
-
|
|
167
|
-
|
|
352
|
+
parseMaterialExtensions(device, material.extensions, parsedMaterial, gltf);
|
|
353
|
+
|
|
354
|
+
switch (material.alphaMode || 'OPAQUE') {
|
|
355
|
+
case 'OPAQUE':
|
|
356
|
+
break;
|
|
357
|
+
case 'MASK': {
|
|
168
358
|
const {alphaCutoff = 0.5} = material;
|
|
169
359
|
parsedMaterial.defines['ALPHA_CUTOFF'] = true;
|
|
360
|
+
parsedMaterial.uniforms.alphaCutoffEnabled = true;
|
|
170
361
|
parsedMaterial.uniforms.alphaCutoff = alphaCutoff;
|
|
171
362
|
break;
|
|
363
|
+
}
|
|
172
364
|
case 'BLEND':
|
|
173
365
|
log.warn('glTF BLEND alphaMode might not work well because it requires mesh sorting')();
|
|
366
|
+
applyAlphaBlendParameters(parsedMaterial);
|
|
174
367
|
|
|
175
|
-
|
|
176
|
-
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
177
371
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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';
|
|
181
380
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
}
|
|
185
390
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
];
|
|
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';
|
|
196
400
|
|
|
197
|
-
|
|
198
|
-
|
|
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
|
+
];
|
|
199
410
|
}
|
|
200
411
|
|
|
201
412
|
/** Parse GLTF material sub record */
|
|
202
413
|
function parsePbrMetallicRoughness(
|
|
203
414
|
device: Device,
|
|
204
415
|
pbrMetallicRoughness: GLTFPBRMetallicRoughness,
|
|
205
|
-
parsedMaterial: ParsedPBRMaterial
|
|
416
|
+
parsedMaterial: ParsedPBRMaterial,
|
|
417
|
+
gltf?: GLTFPostprocessed
|
|
206
418
|
): void {
|
|
207
419
|
if (pbrMetallicRoughness.baseColorTexture) {
|
|
208
420
|
addTexture(
|
|
209
421
|
device,
|
|
210
422
|
pbrMetallicRoughness.baseColorTexture,
|
|
211
423
|
'pbr_baseColorSampler',
|
|
212
|
-
|
|
213
|
-
|
|
424
|
+
parsedMaterial,
|
|
425
|
+
{
|
|
426
|
+
featureOptions: {
|
|
427
|
+
define: 'HAS_BASECOLORMAP',
|
|
428
|
+
enabledUniformName: 'baseColorMapEnabled'
|
|
429
|
+
},
|
|
430
|
+
gltf
|
|
431
|
+
}
|
|
214
432
|
);
|
|
215
433
|
}
|
|
216
434
|
parsedMaterial.uniforms.baseColorFactor = pbrMetallicRoughness.baseColorFactor || [1, 1, 1, 1];
|
|
@@ -220,49 +438,614 @@ function parsePbrMetallicRoughness(
|
|
|
220
438
|
device,
|
|
221
439
|
pbrMetallicRoughness.metallicRoughnessTexture,
|
|
222
440
|
'pbr_metallicRoughnessSampler',
|
|
223
|
-
|
|
224
|
-
|
|
441
|
+
parsedMaterial,
|
|
442
|
+
{
|
|
443
|
+
featureOptions: {
|
|
444
|
+
define: 'HAS_METALROUGHNESSMAP',
|
|
445
|
+
enabledUniformName: 'metallicRoughnessMapEnabled'
|
|
446
|
+
},
|
|
447
|
+
gltf
|
|
448
|
+
}
|
|
225
449
|
);
|
|
226
450
|
}
|
|
227
451
|
const {metallicFactor = 1, roughnessFactor = 1} = pbrMetallicRoughness;
|
|
228
452
|
parsedMaterial.uniforms.metallicRoughnessValues = [metallicFactor, roughnessFactor];
|
|
229
453
|
}
|
|
230
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
|
+
|
|
231
782
|
/** Create a texture from a glTF texture/sampler/image combo and add it to bindings */
|
|
232
783
|
function addTexture(
|
|
233
784
|
device: Device,
|
|
234
785
|
gltfTexture: GLTFTexture,
|
|
235
786
|
uniformName: keyof PBRMaterialBindings,
|
|
236
|
-
|
|
237
|
-
|
|
787
|
+
parsedMaterial: ParsedPBRMaterial,
|
|
788
|
+
textureParseOptions: TextureParseOptions = {}
|
|
238
789
|
): void {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
textureOptions = {data: 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;
|
|
247
797
|
}
|
|
248
798
|
|
|
249
799
|
const gltfSampler = {
|
|
250
800
|
wrapS: 10497, // default REPEAT S (U) wrapping mode.
|
|
251
801
|
wrapT: 10497, // default REPEAT T (V) wrapping mode.
|
|
252
|
-
|
|
253
|
-
|
|
802
|
+
minFilter: 9729, // default LINEAR filtering
|
|
803
|
+
magFilter: 9729, // default LINEAR filtering
|
|
804
|
+
...resolvedTextureInfo?.texture?.sampler
|
|
805
|
+
} as GLTFSampler;
|
|
254
806
|
|
|
255
|
-
const
|
|
256
|
-
id:
|
|
257
|
-
sampler: convertSampler(gltfSampler)
|
|
258
|
-
|
|
259
|
-
|
|
807
|
+
const baseOptions = {
|
|
808
|
+
id: resolvedTextureInfo.uniformName || resolvedTextureInfo.id,
|
|
809
|
+
sampler: convertSampler(gltfSampler)
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
let texture: Texture;
|
|
813
|
+
|
|
814
|
+
if (image.compressed) {
|
|
815
|
+
texture = createCompressedTexture(device, image, baseOptions);
|
|
816
|
+
} else {
|
|
817
|
+
const {width, height} = device.getExternalImageSize(image);
|
|
818
|
+
texture = device.createTexture({
|
|
819
|
+
...baseOptions,
|
|
820
|
+
width,
|
|
821
|
+
height,
|
|
822
|
+
data: image
|
|
823
|
+
});
|
|
824
|
+
}
|
|
260
825
|
|
|
261
826
|
parsedMaterial.bindings[uniformName] = texture;
|
|
262
827
|
if (define) parsedMaterial.defines[define] = true;
|
|
828
|
+
if (enabledUniformName) {
|
|
829
|
+
parsedMaterial.uniforms[enabledUniformName] = true;
|
|
830
|
+
}
|
|
263
831
|
parsedMaterial.generatedTextures.push(texture);
|
|
264
832
|
}
|
|
265
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
|
+
|
|
865
|
+
/** One mip level as produced by loaders.gl compressed texture parsers */
|
|
866
|
+
export type CompressedMipLevel = {
|
|
867
|
+
data: TypedArray;
|
|
868
|
+
width: number;
|
|
869
|
+
height: number;
|
|
870
|
+
textureFormat?: TextureFormat;
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Compressed image from current loaders.gl releases.
|
|
875
|
+
* - `mipmaps` is a boolean (true), NOT an array
|
|
876
|
+
* - `data` is an Array of TextureLevel-like objects
|
|
877
|
+
* - Per-level `textureFormat` is already a luma.gl TextureFormat
|
|
878
|
+
* - Top-level `width`/`height` may be undefined
|
|
879
|
+
*/
|
|
880
|
+
export type CompressedImageDataArray = {
|
|
881
|
+
compressed: true;
|
|
882
|
+
mipmaps?: boolean;
|
|
883
|
+
width?: number;
|
|
884
|
+
height?: number;
|
|
885
|
+
data: CompressedMipLevel[];
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Hypothetical future format where `mipmaps` is an actual array.
|
|
890
|
+
* Kept for forward compatibility.
|
|
891
|
+
*/
|
|
892
|
+
export type CompressedImageMipmapArray = {
|
|
893
|
+
compressed: true;
|
|
894
|
+
width?: number;
|
|
895
|
+
height?: number;
|
|
896
|
+
mipmaps: CompressedMipLevel[];
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
/** Union of all known loaders.gl compressed image shapes */
|
|
900
|
+
export type CompressedImage = CompressedImageDataArray | CompressedImageMipmapArray;
|
|
901
|
+
|
|
902
|
+
function createCompressedTextureFallback(
|
|
903
|
+
device: Device,
|
|
904
|
+
baseOptions: {id: string; sampler: SamplerProps}
|
|
905
|
+
): Texture {
|
|
906
|
+
return device.createTexture({
|
|
907
|
+
...baseOptions,
|
|
908
|
+
format: 'rgba8unorm',
|
|
909
|
+
width: 1,
|
|
910
|
+
height: 1,
|
|
911
|
+
mipLevels: 1
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function resolveCompressedTextureFormat(level: CompressedMipLevel): TextureFormat | undefined {
|
|
916
|
+
return level.textureFormat;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Maximum mip levels that can be filled for a compressed texture.
|
|
921
|
+
* texStorage2D allocates level i at (baseW >> i) × (baseH >> i).
|
|
922
|
+
* Compressed formats can't upload data for levels smaller than one block,
|
|
923
|
+
* so we stop before either dimension drops below the block size.
|
|
924
|
+
*/
|
|
925
|
+
function getMaxCompressedMipLevels(
|
|
926
|
+
baseWidth: number,
|
|
927
|
+
baseHeight: number,
|
|
928
|
+
format: TextureFormat
|
|
929
|
+
): number {
|
|
930
|
+
const {blockWidth = 1, blockHeight = 1} = textureFormatDecoder.getInfo(format);
|
|
931
|
+
let count = 1;
|
|
932
|
+
for (let i = 1; ; i++) {
|
|
933
|
+
const w = Math.max(1, baseWidth >> i);
|
|
934
|
+
const h = Math.max(1, baseHeight >> i);
|
|
935
|
+
if (w < blockWidth || h < blockHeight) break;
|
|
936
|
+
count++;
|
|
937
|
+
}
|
|
938
|
+
return count;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Create a texture from compressed image data produced by loaders.gl.
|
|
943
|
+
* Handles current loaders.gl compressed image layouts:
|
|
944
|
+
*
|
|
945
|
+
* current: {compressed, mipmaps: true, data: [{data, width, height, textureFormat}, ...]}
|
|
946
|
+
* forward: {compressed, mipmaps: [{data, width, height, textureFormat}, ...]}
|
|
947
|
+
*/
|
|
948
|
+
export function createCompressedTexture(
|
|
949
|
+
device: Device,
|
|
950
|
+
image: CompressedImage,
|
|
951
|
+
baseOptions: {id: string; sampler: SamplerProps}
|
|
952
|
+
): Texture {
|
|
953
|
+
// Normalize mip levels from all known loaders.gl formats
|
|
954
|
+
let levels: CompressedMipLevel[];
|
|
955
|
+
|
|
956
|
+
if (Array.isArray((image as any).data) && (image as any).data[0]?.data) {
|
|
957
|
+
// loaders.gl current format: image.data is Array of mip-level objects
|
|
958
|
+
levels = (image as CompressedImageDataArray).data;
|
|
959
|
+
} else if ('mipmaps' in image && Array.isArray((image as CompressedImageMipmapArray).mipmaps)) {
|
|
960
|
+
// Hypothetical future format: image.mipmaps is an Array
|
|
961
|
+
levels = (image as CompressedImageMipmapArray).mipmaps;
|
|
962
|
+
} else {
|
|
963
|
+
levels = [];
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (levels.length === 0 || !levels[0]?.data) {
|
|
967
|
+
log.warn(
|
|
968
|
+
'createCompressedTexture: compressed image has no valid mip levels, creating fallback'
|
|
969
|
+
)();
|
|
970
|
+
return createCompressedTextureFallback(device, baseOptions);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const baseLevel = levels[0];
|
|
974
|
+
const baseWidth = baseLevel.width ?? (image as any).width ?? 0;
|
|
975
|
+
const baseHeight = baseLevel.height ?? (image as any).height ?? 0;
|
|
976
|
+
|
|
977
|
+
if (baseWidth <= 0 || baseHeight <= 0) {
|
|
978
|
+
log.warn('createCompressedTexture: base level has invalid dimensions, creating fallback')();
|
|
979
|
+
return createCompressedTextureFallback(device, baseOptions);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const format = resolveCompressedTextureFormat(baseLevel);
|
|
983
|
+
|
|
984
|
+
if (!format) {
|
|
985
|
+
log.warn('createCompressedTexture: compressed image has no textureFormat, creating fallback')();
|
|
986
|
+
return createCompressedTextureFallback(device, baseOptions);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Validate mip levels: truncate chain at first invalid level.
|
|
990
|
+
// Levels must be contiguous, so we stop at the first level that has
|
|
991
|
+
// a format mismatch, missing data, non-positive dimensions, or
|
|
992
|
+
// dimensions that don't match what texStorage2D will allocate.
|
|
993
|
+
//
|
|
994
|
+
// For block-compressed formats (ASTC, BC, ETC2), texStorage2D allocates
|
|
995
|
+
// mip levels down to 1×1 texels, but compressed data can't be smaller
|
|
996
|
+
// than one block (e.g. 4×4 for ASTC-4x4). Cap the chain so we never
|
|
997
|
+
// try to upload data whose block-aligned size exceeds the allocated level.
|
|
998
|
+
const maxMipLevels = getMaxCompressedMipLevels(baseWidth, baseHeight, format);
|
|
999
|
+
const levelLimit = Math.min(levels.length, maxMipLevels);
|
|
1000
|
+
|
|
1001
|
+
let validLevelCount = 1;
|
|
1002
|
+
for (let i = 1; i < levelLimit; i++) {
|
|
1003
|
+
const level = levels[i];
|
|
1004
|
+
if (!level.data || level.width <= 0 || level.height <= 0) {
|
|
1005
|
+
log.warn(`createCompressedTexture: mip level ${i} has invalid data/dimensions, truncating`)();
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
const levelFormat = resolveCompressedTextureFormat(level);
|
|
1009
|
+
if (levelFormat && levelFormat !== format) {
|
|
1010
|
+
log.warn(
|
|
1011
|
+
`createCompressedTexture: mip level ${i} format '${levelFormat}' differs from base '${format}', truncating`
|
|
1012
|
+
)();
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
const expectedW = Math.max(1, baseWidth >> i);
|
|
1016
|
+
const expectedH = Math.max(1, baseHeight >> i);
|
|
1017
|
+
if (level.width !== expectedW || level.height !== expectedH) {
|
|
1018
|
+
log.warn(
|
|
1019
|
+
`createCompressedTexture: mip level ${i} dimensions ${level.width}x${level.height} ` +
|
|
1020
|
+
`don't match expected ${expectedW}x${expectedH}, truncating`
|
|
1021
|
+
)();
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
validLevelCount++;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const texture = device.createTexture({
|
|
1028
|
+
...baseOptions,
|
|
1029
|
+
format,
|
|
1030
|
+
usage: Texture.TEXTURE | Texture.COPY_DST,
|
|
1031
|
+
width: baseWidth,
|
|
1032
|
+
height: baseHeight,
|
|
1033
|
+
mipLevels: validLevelCount,
|
|
1034
|
+
data: baseLevel.data
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// Upload additional validated mip levels
|
|
1038
|
+
for (let i = 1; i < validLevelCount; i++) {
|
|
1039
|
+
texture.writeData(levels[i].data, {
|
|
1040
|
+
width: levels[i].width,
|
|
1041
|
+
height: levels[i].height,
|
|
1042
|
+
mipLevel: i
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return texture;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
266
1049
|
/*
|
|
267
1050
|
/**
|
|
268
1051
|
* Parses a GLTF material definition into uniforms and parameters for the PBR shader module
|
|
@@ -308,7 +1091,7 @@ export class PBRMaterialParser {
|
|
|
308
1091
|
if (imageBasedLightingEnvironment) {
|
|
309
1092
|
this.bindings.pbr_diffuseEnvSampler = imageBasedLightingEnvironment.getDiffuseEnvSampler();
|
|
310
1093
|
this.bindings.pbr_specularEnvSampler = imageBasedLightingEnvironment.getSpecularEnvSampler();
|
|
311
|
-
this.bindings.
|
|
1094
|
+
this.bindings.pbr_brdfLUT = imageBasedLightingEnvironment.getBrdfTexture();
|
|
312
1095
|
this.uniforms.scaleIBLAmbient = [1, 1];
|
|
313
1096
|
}
|
|
314
1097
|
|
|
@@ -322,6 +1105,7 @@ export class PBRMaterialParser {
|
|
|
322
1105
|
this.defineIfPresent(attributes.NORMAL, 'HAS_NORMALS');
|
|
323
1106
|
this.defineIfPresent(attributes.TANGENT && useTangents, 'HAS_TANGENTS');
|
|
324
1107
|
this.defineIfPresent(attributes.TEXCOORD_0, 'HAS_UV');
|
|
1108
|
+
this.defineIfPresent(attributes.COLOR_0, 'HAS_COLORS');
|
|
325
1109
|
|
|
326
1110
|
this.defineIfPresent(imageBasedLightingEnvironment, 'USE_IBL');
|
|
327
1111
|
this.defineIfPresent(lights, 'USE_LIGHTS');
|
|
@@ -377,8 +1161,13 @@ export class PBRMaterialParser {
|
|
|
377
1161
|
log.warn('BLEND alphaMode might not work well because it requires mesh sorting')();
|
|
378
1162
|
Object.assign(this.parameters, {
|
|
379
1163
|
blend: true,
|
|
380
|
-
blendEquation:
|
|
381
|
-
blendFunc: [
|
|
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
|
+
]
|
|
382
1171
|
});
|
|
383
1172
|
}
|
|
384
1173
|
}
|
|
@@ -415,7 +1204,8 @@ export class PBRMaterialParser {
|
|
|
415
1204
|
if (image.compressed) {
|
|
416
1205
|
textureOptions = image;
|
|
417
1206
|
specialTextureParameters = {
|
|
418
|
-
[
|
|
1207
|
+
[GLEnum.TEXTURE_MIN_FILTER]:
|
|
1208
|
+
image.data.length > 1 ? GLEnum.LINEAR_MIPMAP_NEAREST : GLEnum.LINEAR
|
|
419
1209
|
};
|
|
420
1210
|
} else {
|
|
421
1211
|
// Texture2D accepts a promise that returns an image as data (Async Textures)
|
|
@@ -429,7 +1219,7 @@ export class PBRMaterialParser {
|
|
|
429
1219
|
...specialTextureParameters
|
|
430
1220
|
},
|
|
431
1221
|
pixelStore: {
|
|
432
|
-
[
|
|
1222
|
+
[GLEnum.UNPACK_FLIP_Y_WEBGL]: false
|
|
433
1223
|
},
|
|
434
1224
|
...textureOptions
|
|
435
1225
|
});
|