From 6c84ef5b289594c684d7bad783c44a3d078bd685 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Thu, 28 Nov 2024 17:37:24 +0000 Subject: [PATCH 1/3] Add option to GltfExporter to strip unused attributes --- src/extras/exporters/gltf-exporter.js | 65 ++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/src/extras/exporters/gltf-exporter.js b/src/extras/exporters/gltf-exporter.js index 374c972cfb6..c8d175d810b 100644 --- a/src/extras/exporters/gltf-exporter.js +++ b/src/extras/exporters/gltf-exporter.js @@ -487,21 +487,20 @@ class GltfExporter extends CoreExporter { } } - writeMeshes(resources, json) { + writeMeshes(resources, json, options) { if (resources.entityMeshInstances.length > 0) { json.accessors = []; json.meshes = []; resources.entityMeshInstances.forEach((entityMeshInstances) => { - const mesh = { primitives: [] }; - // all mesh instances of a single node are stores as a single gltf mesh with multiple primitives + // all mesh instances of a single node are stored as a single gltf mesh with multiple primitives const meshInstances = entityMeshInstances.meshInstances; meshInstances.forEach((meshInstance) => { - const primitive = GltfExporter.createPrimitive(resources, json, meshInstance.mesh); + const primitive = GltfExporter.createPrimitive(resources, json, meshInstance.mesh, options); primitive.material = resources.materials.indexOf(meshInstance.material); @@ -513,7 +512,7 @@ class GltfExporter extends CoreExporter { } } - static createPrimitive(resources, json, mesh) { + static createPrimitive(resources, json, mesh, options = {}) { const primitive = { attributes: {} }; @@ -524,6 +523,44 @@ class GltfExporter extends CoreExporter { const { interleaved, elements } = format; const numVertices = vertexBuffer.getNumVertices(); elements.forEach((element, elementIndex) => { + const semantic = getSemantic(element.name); + + // Skip unused attributes if stripping is enabled + if (options.stripUnusedAttributes) { + let isUsed = true; + + // Check texture coordinates + if (semantic.startsWith('TEXCOORD_')) { + const texCoordIndex = parseInt(semantic.split('_')[1]); + isUsed = resources.materials.some(material => { + return textureSemantics.some(texSemantic => { + const texture = material[texSemantic]; + // Most materials use UV0 by default, so keep TEXCOORD_0 unless explicitly using a different UV set + return texture && (texCoordIndex === 0 || material[`${texSemantic}Tiling`]?.uv === texCoordIndex); + }); + }); + } + + // Check vertex colors + if (semantic === 'COLOR_0') { + isUsed = resources.materials.some(material => material.vertexColors); + } + + // Check tangents + if (semantic === 'TANGENT') { + isUsed = resources.materials.some(material => material.normalMap); + } + + // Check skinning attributes + if (semantic === 'JOINTS_0' || semantic === 'WEIGHTS_0') { + isUsed = resources.entityMeshInstances.some(emi => + emi.meshInstances.some(mi => mi.mesh.skin)); + } + + if (!isUsed) { + return; // Skip this attribute + } + } let bufferView = resources.bufferViewMap.get(vertexBuffer); if (!bufferView) { @@ -543,13 +580,10 @@ class GltfExporter extends CoreExporter { }; const idx = json.accessors.push(accessor) - 1; - primitive.attributes[getSemantic(element.name)] = idx; + primitive.attributes[semantic] = idx; // Position accessor also requires min and max properties if (element.name === SEMANTIC_POSITION) { - - // compute min and max from positions, as the BoundingBox stores center and extents, - // and we get precision warnings from gltf validator const positions = []; mesh.getPositions(positions); const min = new Vec3(); @@ -723,7 +757,7 @@ class GltfExporter extends CoreExporter { this.writeBufferViews(resources, json); this.writeCameras(resources, json); - this.writeMeshes(resources, json); + this.writeMeshes(resources, json, options); this.writeMaterials(resources, json); this.writeNodes(resources, json, options); await this.writeTextures(resources, textureCanvases, json, options); @@ -742,8 +776,15 @@ class GltfExporter extends CoreExporter { * * @param {Entity} entity - The root of the entity hierarchy to convert. * @param {object} options - Object for passing optional arguments. - * @param {number} [options.maxTextureSize] - Maximum texture size. Texture is resized if over - * the size. + * @param {number} [options.maxTextureSize] - Maximum texture size. Texture is resized if over the size. + * @param {boolean} [options.stripUnusedAttributes] - If true, removes unused vertex attributes: + * + * - Texture coordinates not referenced by materials + * - Vertex colors if not used by materials + * - Tangents if no normal maps are used + * - Skinning data if no skinned meshes exist + * + * Defaults to false. * @returns {Promise} - The GLB file content. */ build(entity, options = {}) { From 67e74b8799a69d1e3564a2b3409f92dd29afb4f1 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Thu, 28 Nov 2024 17:45:12 +0000 Subject: [PATCH 2/3] Lint fixes --- src/extras/exporters/gltf-exporter.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/extras/exporters/gltf-exporter.js b/src/extras/exporters/gltf-exporter.js index c8d175d810b..cbb11c72818 100644 --- a/src/extras/exporters/gltf-exporter.js +++ b/src/extras/exporters/gltf-exporter.js @@ -531,9 +531,9 @@ class GltfExporter extends CoreExporter { // Check texture coordinates if (semantic.startsWith('TEXCOORD_')) { - const texCoordIndex = parseInt(semantic.split('_')[1]); - isUsed = resources.materials.some(material => { - return textureSemantics.some(texSemantic => { + const texCoordIndex = parseInt(semantic.split('_')[1], 10); + isUsed = resources.materials.some((material) => { + return textureSemantics.some((texSemantic) => { const texture = material[texSemantic]; // Most materials use UV0 by default, so keep TEXCOORD_0 unless explicitly using a different UV set return texture && (texCoordIndex === 0 || material[`${texSemantic}Tiling`]?.uv === texCoordIndex); @@ -553,8 +553,7 @@ class GltfExporter extends CoreExporter { // Check skinning attributes if (semantic === 'JOINTS_0' || semantic === 'WEIGHTS_0') { - isUsed = resources.entityMeshInstances.some(emi => - emi.meshInstances.some(mi => mi.mesh.skin)); + isUsed = resources.entityMeshInstances.some(emi => emi.meshInstances.some(mi => mi.mesh.skin)); } if (!isUsed) { From aa735d848b70b40fb9967ce0a05b4cc4e904d821 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Thu, 28 Nov 2024 17:50:55 +0000 Subject: [PATCH 3/3] Restore comment --- src/extras/exporters/gltf-exporter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/extras/exporters/gltf-exporter.js b/src/extras/exporters/gltf-exporter.js index cbb11c72818..4f75620002a 100644 --- a/src/extras/exporters/gltf-exporter.js +++ b/src/extras/exporters/gltf-exporter.js @@ -583,6 +583,8 @@ class GltfExporter extends CoreExporter { // Position accessor also requires min and max properties if (element.name === SEMANTIC_POSITION) { + // compute min and max from positions, as the BoundingBox stores center and extents, + // and we get precision warnings from gltf validator const positions = []; mesh.getPositions(positions); const min = new Vec3();