共计 2410 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点:为什么需要浏览器端 GLB 压缩?
在 WebGL 项目中,GLB 作为 glTF 的二进制格式,虽然比 OBJ/FBX 等传统格式更轻量,但复杂模型仍可能达到数十 MB。我们曾遇到一个家具展示项目:

- 原始 GLB 文件:48MB
- 首次加载时间:12 秒(4G 网络)
- 移动端崩溃率:23%(低端 Android 设备)
传统服务端压缩方案存在两个致命缺陷:
- 上传 - 压缩 - 下载的往返延迟(即使 1MB 模型也要增加 1.5 秒以上等待)
- 需要维护额外的压缩服务器(AWS Lambda 成本约 $0.02/ 千次调用)
技术选型:为什么是 Draco+WebAssembly?
我们对比了三种主流算法:
| 算法 | 压缩率 | 解码速度 | 浏览器支持 |
|---|---|---|---|
| Draco | 75% | 快 | 需 WASM |
| Meshopt | 50% | 极快 | 纯 JS |
| BasisU | 80% | 慢 | 需扩展 |
选择 Draco 的核心原因:
- three.js 官方维护 DRACOLoader
- Google 长期支持(Android ARCore 底层使用)
- 支持顶点属性分离压缩(可只压缩法线 /UV)
核心实现:零服务端依赖方案
1. three.js 集成要点
import {DRACOLoader} from 'three/examples/jsm/loaders/DRACOLoader.js';
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
loader.setDRACOLoader(dracoLoader);
关键配置项:
decoderPath必须指向正确版本(v1.5.6 后 API 有变动)- 建议预加载 decoder:
dracoLoader.preload()
2. WASM 内存优化
实测发现:iPhone 12 上超过 50MB 的模型解码会崩溃。解决方案:
const decoderConfig = {
type: 'js',
wasmUrl: 'draco_decoder.wasm',
memoryInitializerPrefixURL: 'decoder/'
};
dracoLoader.getDecoderModule().then(module => {module.dracoDecoder.setWorkerLimit(1); // iOS 限制线程数
module.dracoDecoder.setFreeUnusedMemory(true); // 主动释放内存
});
3. 压缩参数调优
const encoder = new DracoEncoderModule.DracoEncoder();
encoder.SetSpeedOptions(5, 5); // 压缩速度 vs 质量(0-10)encoder.SetAttributeQuantization(DracoEncoderModule.POSITION, 14 // 14 位精度 =0.01mm 级误差);
完整代码示例
<!DOCTYPE html>
<script type="module">
import {GLTFLoader} from 'https://cdn.jsdelivr.net/npm/three@0.132.2/examples/jsm/loaders/GLTFLoader.js';
import {DRACOLoader} from 'https://cdn.jsdelivr.net/npm/three@0.132.2/examples/jsm/loaders/DRACOLoader.js';
const modelInput = document.getElementById('model-upload');
modelInput.addEventListener('change', async (e) => {const file = e.target.files[0];
const arrayBuffer = await file.arrayBuffer();
// 初始化压缩器
const encoderModule = await new Promise(resolve => {DracoEncoderModule().then(module => resolve(module));
});
const encoder = new encoderModule.DracoEncoder();
encoder.SetSpeedOptions(7, 7);
// 执行压缩(伪代码,实际需处理几何数据)const compressedData = encoder.EncodeMeshToDracoBuffer(
geometry.vertices,
geometry.indices
);
// 生成新 GLB
rebuildGLB(compressedData).then(glb => {console.log(` 压缩完成:${file.size}→${glb.size} bytes`);
});
});
</script>
性能实测数据
测试设备:MacBook Pro M1 vs Redmi Note 10
| 模型面数 | 原始大小 | 压缩耗时 | M1 解码 FPS | 红米解码 FPS |
|---|---|---|---|---|
| 50K | 4.2MB | 380ms | 120 | 45 |
| 200K | 18MB | 1.2s | 90 | 28 |
| 1M | 86MB | 6.8s | 32 | 崩溃 |
避坑指南
- 多线程冲突:
- Chrome 默认启动 4 个 WebWorker 解码
-
解决方法:
dracoLoader.setWorkerLimit(1) -
iOS 内存限制:
- 超过 50MB 建议分块处理
-
使用
OffscreenCanvas降低主线程压力 -
量化误差累积:
- 避免对同一模型多次压缩
- UV 坐标建议保持 16 位精度
延伸应用:glTF 流水线整合
这套方案可扩展至:
- 与 Blender 插件结合,实现导出即压缩
- 对接 CDN 服务,自动生成压缩版本
- 构建 Webpack 插件,打包阶段自动优化
我们正在开发开源工具链,欢迎关注 GitHub 仓库参与贡献。
正文完
发表至: 未分类
近三天内
