使用BufferGeometryUtils合并GLB模型几何体与原模型不符的问题
解决方案
1. 修复对齐偏差问题
首先要确保模型所有节点的世界矩阵是最新的,否则直接应用matrixWorld会导致坐标计算错误:
// 遍历前强制更新整个模型的世界矩阵层级 model.updateMatrixWorld(true); let geometries = []; model.traverse( c => { if (c.isMesh){ let clonedGeometry = c.geometry.clone(); clonedGeometry.applyMatrix4( c.matrixWorld ); // 后续代码不变 } });
如果仍有对齐问题,可以替换矩阵应用方式,直接对顶点做坐标转换:
// 替换 clonedGeometry.applyMatrix4( c.matrixWorld ); 的代码 const positionAttr = clonedGeometry.attributes.position; const tempVec = new THREE.Vector3(); for (let i = 0; i < positionAttr.count; i++) { tempVec.fromBufferAttribute(positionAttr, i); tempVec.applyMatrix4(c.matrixWorld); positionAttr.setXYZ(i, tempVec.x, tempVec.y, tempVec.z); } positionAttr.needsUpdate = true;
2. 修复几何体过度简化问题
保留索引属性
原模型的复杂几何体(比如圆形窗户)通常依赖index属性复用顶点、维持形状,克隆时要确保索引被正确复制:
let clonedGeometry = c.geometry.clone(); // 显式保留索引(若原几何体存在) if (c.geometry.index) { clonedGeometry.index = c.geometry.index.clone(); }
谨慎删除非核心属性
不要盲目删除position和normal之外的所有属性,部分几何体的形状维持依赖UV、切线等属性。仅删除明确不需要的自定义属性:
// 示例:只删除指定的自定义属性 const unwantedAttrs = ['customTag', 'extraData']; for (const key in clonedGeometry.attributes) { if (unwantedAttrs.includes(key)) { clonedGeometry.deleteAttribute(key); } }
合并时启用分组(按需)
如果需要保留原模型的多材质区分,合并时启用useGroups参数,避免因材质合并导致的形状异常:
// 第二个参数设为true,保留原几何体的分组信息 let mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries, true );
处理蒙皮网格(SkinnedMesh)
若模型包含蒙皮动画网格,直接克隆会导致形状失真,需先将顶点转换为最终世界坐标:
if (c.isSkinnedMesh) { const tempGeometry = c.geometry.clone(); tempGeometry.applyMatrix4(c.matrixWorld); const skeleton = c.skeleton; const skinIndex = tempGeometry.attributes.skinIndex; const skinWeight = tempGeometry.attributes.skinWeight; const tempVec = new THREE.Vector3(); const tempMat = new THREE.Matrix4(); for (let i = 0; i < tempGeometry.attributes.position.count; i++) { tempVec.fromBufferAttribute(tempGeometry.attributes.position, i); tempMat.identity(); // 计算骨骼加权变换矩阵 for (let j = 0; j < 4; j++) { const boneIdx = skinIndex.getX(i + j); const weight = skinWeight.getX(i + j); if (weight === 0) continue; tempMat.add(skeleton.bones[boneIdx].matrixWorld.multiplyScalar(weight)); } tempVec.applyMatrix4(tempMat); tempGeometry.attributes.position.setXYZ(i, tempVec.x, tempVec.y, tempVec.z); } tempGeometry.attributes.position.needsUpdate = true; // 删除蒙皮相关属性 tempGeometry.deleteAttribute('skinIndex'); tempGeometry.deleteAttribute('skinWeight'); geometries.push(tempGeometry); } else if (c.isMesh) { // 普通网格处理逻辑 }
3. 验证合并结果
可以通过以下方式确认效果:
- 打印顶点数对比:
console.log('原总顶点数:', geometries.reduce((sum, g) => sum + g.attributes.position.count, 0))和console.log('合并后顶点数:', mergedGeometry.attributes.position.count),确保顶点数匹配(无意外合并)。 - 用
MeshBasicMaterial开启wireframe: true渲染,直观检查形状是否与原模型一致。
内容的提问来源于stack exchange,提问作者Luca Guida
相关产品推荐
相关产品推荐

