续篇:Draco压缩+贴图外链!模型34MB→4MB的终极优化
书接上回,我们通过「格式修复→GPU实例化→Feature ID绑定→Mesh合并」四步优化,将BIM模型的Draw Call从1388骤降到26,实现了丝滑渲染和精准拾取。
但优化后34MB的模型体积仍有优化空间,且实际部署时贴图与模型绑定的方式不够灵活。
本篇将聚焦「Draco几何压缩+贴图外联」方案,解决部署痛点的同时,让模型体积再降88%,最终从34MB压缩至4MB!
一、直接压缩的“坑”:gltf-pipeline -d -t 报错实录
在上一篇的Step4后,我们得到了34MB的output_merged.glb(含Feature ID和顶点颜色),为了进一步压缩体积并实现贴图独立部署,直接执行了标准压缩命令:
gltf-pipeline -i output_merged.glb -o O_dec.glb -d -t
其中:
-d:启用Draco网格压缩(KHR_draco_mesh_compression),核心用于压缩顶点、索引等几何数据-t:启用贴图外联(separateTextures),将模型内嵌入的贴图提取为独立文件(如Steelplt.png)
执行后直接报错,无法加载
Cesium控制台抛出两个关键信息:
-
核心报错:
TypeError: Cannot read properties of undefined (reading 'count')- 报错位置:
InstancingPipelineStage.process(Cesium3DTileset渲染管线) - 直接导致模型加载失败,无法渲染
- 报错位置:
-
警告信息:
Accessor xxx for instanced attribute "_FEATURE_ID_0" is not defined in the glTF file. This attribute will be skipped.- 提示实例化属性(TRANSLATION/ROTATION/_FEATURE_ID_0)对应的Accessor未定义
- 原因指向gltf-pipeline压缩时误删了“未使用”的Accessor
问题本质:索引更新遗漏
经过排查,发现并非Accessor被误删,而是gltf-pipeline的核心逻辑缺陷:
- Draco压缩会在
accessors数组末尾追加新的元数据Accessor,随后删除原始未使用的Accessor - 删除过程中,会对
primitive.attributes、animation.samplers等常规字段的Accessor索引做前移更新(如删除索引2后,索引3及以上均减1) - 关键遗漏:
node.extensions.EXT_mesh_gpu_instancing.attributes中的索引未同步更新(包含TRANSLATION/ROTATION/_FEATURE_ID_0对应的Accessor索引) - 最终导致模型中引用的索引指向错位或不存在的Accessor,Cesium访问
accessor.count时拿到undefined报错
二、修复方案:补充索引更新逻辑
需修改gltf-pipeline的removeUnusedElements.js文件,在删除Accessor时同步维护实例化属性的索引。
修改文件路径
lib/removeUnusedElements.js
修改函数
Remove.accessor(在现有更新animation索引的逻辑后、函数结尾前新增)
新增代码
// Update accessor ids for EXT_mesh_gpu_instancing (instanced attributes)
if (usesExtension(gltf, "EXT_mesh_gpu_instancing")) {
ForEach.node(gltf, function (node) {
if (
defined(node.extensions) &&
defined(node.extensions.EXT_mesh_gpu_instancing)
) {
const attrs = node.extensions.EXT_mesh_gpu_instancing.attributes;
Object.keys(attrs).forEach(function (key) {
if (attrs[key] > accessorId) {
attrs[key]--;
}
});
}
});
}
修复逻辑说明
- 当模型使用
EXT_mesh_gpu_instancing扩展时,遍历所有节点 - 对每个节点的实例化属性(TRANSLATION/ROTATION/_FEATURE_ID_0等)的Accessor索引进行检查
- 若索引值大于被删除的Accessor ID,则索引减1,与
accessors数组的splice操作保持同步 - 确保Cesium渲染时能正确找到实例化属性对应的Accessor,报错彻底解决
三、新问题:贴图外联的“命名乱象”
修复索引问题后,执行压缩命令能成功导出独立贴图,但发现新问题:同名贴图会自动追加序号。
- 例如模型中有3个同名的“Steelplt.png”贴图,导出后会生成
Steelplt.png、Steelplt_1.png、Steelplt_2.png - 实际业务中,这些同名贴图往往是重复资源或需要统一替换的材质,多份文件既占用存储空间,也增加部署复杂度
优化方案:新增同名贴图替换选项
通过扩展gltf-pipeline的配置项,新增replaceSameNameTextures参数,支持同名贴图覆盖替换。
修改涉及3个核心文件
1. lib/writeResources.js
-
新增参数说明:
* @param {boolean} [options.replaceSameNameTextures=false] When saving separate textures, overwrite existing file when name conflicts instead of appending _1, _2. -
初始化参数:
options.replaceSameNameTextures = options.replaceSameNameTextures ?? false; -
修改同名处理逻辑:
const name = getName(gltf, object, index, extension, options); relativePath = name + extension; // For textures: optionally overwrite same name; otherwise append a number const isImage = extension !== ".bin" && extension !== ".glsl"; if ( !(isImage && options.replaceSameNameTextures) && defined(options.separateResources[relativePath]) ) { let number = 1; while (defined(options.separateResources[relativePath])) { relativePath = `${name}_${number}${extension}`; number++; } } return relativePath;
2. lib/processGltf.js
- 新增参数说明和默认配置:
* @param {boolean} [options.replaceSameNameTextures = false] When using separate textures, overwrite same-named files instead of appending _1, _2.
......
options.replaceSameNameTextures = options.replaceSameNameTextures ?? defaults.replaceSameNameTextures;
......
processGltf.defaults = {
separateTextures: false,
/**
* When using separate textures, overwrite same-named image files instead of appending _1, _2.
* @type Boolean
* @default false
*/
replaceSameNameTextures: false,
};
3. bin/gltf-pipeline.js
- 新增命令行选项:
replaceSameNameTextures: {
describe: "When using -t, overwrite same-named texture files instead of appending _1, _2.",
type: "boolean",
default: defaults.replaceSameNameTextures,
},
- 传递参数:
replaceSameNameTextures: argv.replaceSameNameTextures,
最终可用命令
gltf-pipeline -i output_merged.glb -o final.glb -d -t --replaceSameNameTextures
- 效果:同名贴图不再生成序号后缀,后写入的贴图直接覆盖前序文件,最终只保留一份同名贴图
- 优势:减少冗余文件,贴图部署时可直接替换同名文件实现批量更新
四、终极效果:34MB → 4MB的质变
经过上述修复和优化,模型实现了“功能无损+体积暴降”的双重目标:
| 阶段 | 核心操作 | 体积 | 关键变化 |
|---|---|---|---|
| 优化后中间态 | Mesh合并+Feature ID | 34.86MB | Draw Call=26,支持精准拾取 |
| 最终优化态 | Draco压缩+贴图外联+同名替换 | 4.12MB | 体积压缩88%,贴图独立部署 |
关键优化亮点
- 体积优化:34MB→4MB,主要得益于Draco对几何数据的高效压缩(顶点、索引压缩率超70%)
- 部署灵活:贴图独立导出为PNG/JPG,可单独进行CDN加速、格式优化(如转为KTX2)
- 功能无损:实例化渲染、Feature ID拾取功能完全保留,Cesium加载无报错,帧率稳定在30+FPS
- 资源精简:同名贴图自动合并,避免冗余,部署包体积进一步减小
总结
从1388 Draw Call到26,再从34MB到4MB,BIM模型的优化完成了“性能+体积”的双重闭环:
- 前序四步优化解决“渲染卡顿”和“无法拾取”的核心痛点
- 本篇 Draco压缩+贴图外联解决“体积过大”和“部署不便” 的落地问题
最终交付的4MB模型,既满足了Web端快速加载的需求,又保留了BIM模型的业务交互能力(构件拾取、属性查询),完全适配Cesium等3D地理信息平台的生产环境使用。
下面是最终效果,能够实现构件拾取,交互帧率基本恒定在58~60FPS。