31.19MB 压到 732.7KB:Web 端 3D 模型优化的完整命令清单
原作者:Axel Cuevas 原文链接:https://www.axl-devhub.me/en/blog/optimizing-3d-models
结论先行
这篇文章的定位很实用:不讲原理,只给能直接跑的命令和前后对比数据。
核心流程四步:
- 统一转 GLB(二进制 glTF)——单文件、单次请求,比多文件格式省 20–40%;
gltf-transform optimize一把梭——--compress draco压几何、--texture-compress webp(或 avif / ktx2)压纹理、--texture-resize 1024限制纹理尺寸;- 自托管 Draco 解码器——把
draco_decoder.js / .wasm / draco_wasm_wrapper.js下到项目里,而不是依赖 CDN; - 加载端接 DRACOLoader(Three.js 原生,或 R3F 里
useGLTF.setDecoderPath("/draco/"))。
实测结果:31.19MB → 732.7KB,减少 97%;Lighthouse 性能分 39 → 97;LCP 10.3s → 1.0s,视觉质量几乎无差别。
本站判断:这是三篇文章里唯一一篇带真实前后对照数据的,所以可信度最高。但要注意它的「97%」是一个特定模型的结果,不是通用承诺——原文自己也把「预期结果」写成 80–90%。真正的启发是那条容易被忽略的信息:纹理常占模型体积的 80%。也就是说,如果只压几何不压纹理,你最多解决 20% 的问题。优化顺序应该是先看纹理、再压几何。
原文出处
作者:Axel Cuevas 原文:Optimizing 3D Models for the Web using Draco and other tools 发布:2025-05-13 链接:https://www.axl-devhub.me/en/blog/optimizing-3d-models
要点解读
1. 先量化问题:不优化会付什么代价
原文列出的四条后果:
- 加载时间增加 300–500%;
- 消耗过多带宽(未优化模型可达 20MB+);
- 移动端掉帧到 30 FPS 以下;
- 因 Core Web Vitals 拖累 SEO 评分。
性能基准目标:
| 指标 | 目标 |
|---|---|
| 文件体积 | 移动端 低于 2MB |
| 帧率 | 中端设备 60 FPS |
| 首屏渲染 | 少于 3 秒 |
「移动端低于 2MB」这条基准值得单独记。 它意味着前面那个 31MB 的模型即使压到 732KB 也仍有优化余地(或者说,它已经从「完全不可用」变成「舒适区」)。做 Web3D 交付时,这个 2MB 是可以写进验收标准的硬指标。
2. 第一步永远是 GLB
原文把 GLB 称为「Web 交付的优越格式」,理由有四条:
- 单二进制文件:几何、纹理、动画全部内嵌;
- 加载最快:只需要一次 HTTP 请求;
- 体积最小:二进制格式压缩效率更好;
- 没有缺失依赖:所有资源打包在一起。
转换命令(先 npm install -g @gltf-transform/cli):
# 各种格式统一转 GLB
gltf-transform copy model.fbx model.glb
gltf-transform copy model.obj model.glb
gltf-transform copy model.gltf model.glb
gltf-transform copy model.dae model.glb
Three.js 加载端:
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
loader.load("/models/model.glb", (gltf) => {
// 一次请求加载全部:几何、材质、纹理
scene.add(gltf.scene);
});
GLB 相对多文件格式的价值:把多个 HTTP 请求合并成 1 个、消除文件依赖问题、立即获得 20–40% 体积下降,并为后续压缩算法铺路。
本站判断:这里「一次请求」的意义在 HTTP/2 时代容易被低估——不是因为请求数本身贵,而是因为多文件格式下任何一个小文件 404 或超时,整个场景就白屏。GLB 把这种失败模式从「概率性」变成「原子性」:要么全到,要么全不到,便于做兜底和重试。
3. 核心命令:gltf-transform optimize
原文给出的两条主命令:
# 优化已有 GLB
gltf-transform optimize input.glb output.glb \
--compress draco \
--texture-compress webp \
--texture-resize 1024
# 从其他格式一步到位转换 + 优化
gltf-transform optimize model.gltf optimized.glb \
--format glb \
--compress draco \
--texture-compress webp
各参数含义:
| 参数 | 作用 |
|---|---|
--compress draco | 用 Google Draco 算法压缩几何 |
--texture-compress webp | 纹理转 WebP,体积更小 |
--texture-resize 1024 | 纹理最大重设为 1024×1024 |
--format glb | 确保输出为 GLB 二进制格式 |
两个压缩对象的量级完全不同:
- Draco(几何):可把网格体积减少 90% 或更多,压缩顶点位置、法线、纹理坐标,同时保持视觉质量;
- 纹理:常占 3D 模型体积的 80%,转 WebP 相比传统 JPEG/PNG 可减 50–70%。
这就是「先压纹理」的算术依据:纹理占 80% 体积、能减 50–70%,即整体可减 40–56%;几何占 20%、能减 90%,即整体可减 18%。两者相加才逼近原文那个 97% 的实测值。 只做其中一项,效果都会让你失望。
4. 纹理格式怎么选
# WebP:兼容性好,比 JPEG 小 70%
gltf-transform optimize model.glb optimized.glb --texture-compress webp
# AVIF:比 WebP 更小
gltf-transform optimize model.glb optimized.glb --texture-compress avif
# KTX2 / Basis Universal:压缩最强,且省 VRAM
gltf-transform optimize model.glb optimized.glb --texture-compress ktx2
单独重设纹理尺寸:
gltf-transform resize input.glb output.glb --width 1024 --height 1024
选型建议(本站整理):
| 格式 | 体积 | 兼容性 | 显存占用 | 适用 |
|---|---|---|---|---|
| WebP | 小 | 最好 | 需解码为位图 | 默认选择 |
| AVIF | 更小 | 较新浏览器 | 需解码为位图 | 追求极限体积 |
| KTX2 | 最小 | 需加载转码器 | 最低(GPU 原生) | 移动端、大场景 |
KTX2 的独特价值在于「省 VRAM」而不只是省带宽。 普通 WebP/AVIF 解码后仍以位图形式占显存,KTX2 是 GPU 原生压缩格式,可以直接驻留显存。当场景里纹理多、显存吃紧(尤其是移动端)时,KTX2 是唯一能同时解决带宽和显存两项的方案——代价是要额外加载转码器。
5. Draco 解码器必须自托管
原文强调这一步是「为了更好的性能」:
# 创建解码器目录
mkdir -p public/draco
# 下载 Draco 解码器文件
curl -o public/draco/draco_decoder.js \
https://www.gstatic.com/draco/versioned/decoders/1.5.6/draco_decoder.js
curl -o public/draco/draco_decoder.wasm \
https://www.gstatic.com/draco/versioned/decoders/1.5.6/draco_decoder.wasm
curl -o public/draco/draco_wasm_wrapper.js \
https://www.gstatic.com/draco/versioned/decoders/1.5.6/draco_wasm_wrapper.js
本站判断:这一步经常被跳过,但它是个「省小钱亏大钱」的典型。Draco 压缩不是免费的——解码器本身要先下载下来才能解压模型。 如果从 Google CDN 拉,就多了一次跨域 DNS + TLS + 往返,在国内网络环境下这段延迟可能比模型本身还长。自托管后解码器和你同源同 CDN,才真正兑现 Draco 的收益。
(路径前缀随框架而定:原文用 public/draco,如果站点用其他静态目录约定,换成对应前缀即可,加载端的 setDecoderPath 要跟着改。)
6. 加载端接入
Three.js 原生写法:
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/"); // 指向自托管的解码器目录
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
gltfLoader.load("/models/optimized.glb", (gltf) => {
scene.add(gltf.scene);
});
React Three Fiber + Drei:
import { useGLTF } from "@react-three/drei";
// 全局设置解码器路径
useGLTF.setDecoderPath("/draco/");
// 预加载优化后的模型
useGLTF.preload("/models/scene-optimized.glb");
function Model() {
const { scene } = useGLTF("/models/scene-optimized.glb");
return <primitive object={scene} />;
}
注意 R3F 的写法是「全局设置一次」(useGLTF.setDecoderPath 在模块顶层调用),不是每个组件设一次。这是 Drei 把 DRACOLoader 单例化后的结果——写错位置会导致解码器被重复初始化。
7. 实测数据:这篇文章最硬的部分
| 指标 | 优化前 | 优化后 | 变化 |
|---|---|---|---|
| 文件体积 | 31.19 MB | 732.7 KB | -97% |
| Lighthouse 性能分 | 39 | 97 | +148% |
| LCP(最大内容绘制) | 10.3 s | 1.0 s | -90% |
| 视觉质量 | — | — | 「几乎一致」 |
配置为:gltf-transform + Draco 几何压缩 + 纹理优化。
LCP 从 10.3s 到 1.0s 这一项值得单独看。 对 Web3D 页面来说,LCP 通常就是那个画布或首帧——它是 Core Web Vitals 里权重最高的指标之一,直接关系到搜索排名。10.3s 是「用户基本走光」的区间,1.0s 是「绿灯」区间。 换句话说,这一项优化的商业价值不只是体验,还有 SEO。
同时注意原文自己的措辞:在「预期结果」里写的是 80–90% 体积下降、50–100% 加载性能提升、无明显质量损失。97% 是单个案例的实测极值,不是通用承诺。
8. 生产环境参数与内存管理
Canvas 参数:
const canvasProps = {
shadows: true,
gl: {
antialias: true,
alpha: false,
powerPreference: "high-performance",
stencil: false,
depth: true,
},
dpr: Math.min(window.devicePixelRatio, 2), // 限制设备像素比
performance: { min: 0.5 }, // 掉帧时自动降质量
};
内存释放:
useEffect(() => {
return () => {
scene.traverse((child) => {
if (child.geometry) child.geometry.dispose();
if (child.material) {
if (Array.isArray(child.material)) {
child.material.forEach((material) => material.dispose());
} else {
child.material.dispose();
}
}
});
};
}, []);
三个参数最容易踩坑(本站补充说明):
dpr: Math.min(window.devicePixelRatio, 2)——高分屏上devicePixelRatio可达 3,像素量按平方增长(9 倍 vs 4 倍),这是移动端掉帧最常见的原因。封顶到 2 是性价比最高的画质/性能折中。stencil: false——默认开启的模板缓冲在多数 3D 场景里用不到,关掉能省显存和带宽。performance: { min: 0.5 }——这是 R3F 的自动降级机制,FPS 掉下去时自动降低渲染分辨率,而不是直接卡死。
dispose() 那段必须写。 在 SPA 里切换路由时,如果只从场景树移除对象而不 dispose,几何体和材质的显存不会被回收——切几次场景就会 OOM。这是 Web3D 应用最常见的内存泄漏来源。
代码与验证
原文的命令可直接复现。完整流程串起来:
# 0. 安装 CLI
npm install -g @gltf-transform/cli
# 1. 任意格式 → GLB(可选,optimize 也能直接吃其他格式)
gltf-transform copy model.fbx model.glb
# 2. 一步优化:几何 Draco + 纹理 WebP + 纹理限 1024
gltf-transform optimize model.glb optimized.glb \
--compress draco \
--texture-compress webp \
--texture-resize 1024
# 3. 查看结果
ls -lh model.glb optimized.glb
# 4. 自托管 Draco 解码器
mkdir -p public/draco
curl -o public/draco/draco_decoder.js \
https://www.gstatic.com/draco/versioned/decoders/1.5.6/draco_decoder.js
curl -o public/draco/draco_decoder.wasm \
https://www.gstatic.com/draco/versioned/decoders/1.5.6/draco_decoder.wasm
curl -o public/draco/draco_wasm_wrapper.js \
https://www.gstatic.com/draco/versioned/decoders/1.5.6/draco_wasm_wrapper.js
验证清单(本站整理,原文给的是结果不是方法):
ls -lh对比前后体积,先确认几何和纹理各自降了多少——如果只降了不到 20%,说明纹理没压到;- 浏览器 DevTools → Network,确认
.glb体积、以及 draco 解码器是从自己域名加载的; - DevTools → Performance 跑一次,确认 LCP 落在 1s 量级、帧率稳定在 60 FPS;
- 用
chrome://gpu或 WebGL 渲染器信息确认 KTX2(如使用)走的是 GPU 原生路径; - 在真机(尤其低端安卓)上跑一遍——桌面端 Lighthouse 97 不代表移动端没问题。
适用场景与坑
该用:任何要在浏览器里加载外部 3D 模型的场景,尤其是移动端、大场景、以及有 SEO 要求的页面。这个流程几乎是无脑收益——原文实测视觉质量「几乎一致」,没有明显的画质代价。
别用:需要运行时动态改网格拓扑的场景(Draco 是解码即用,不适合频繁编辑);以及已经在用 KTX2 + meshopt 等更激进方案的管线(再叠 Draco 收益递减)。
已知的坑:
- 97% 是单个案例的实测值,不是承诺。原文自己的「预期结果」写的是 80–90% 体积下降。用 80–90% 做规划,用实测做验收。
- 纹理才是大头,占体积 80%。只压几何最多解决 20% 的问题——这是最容易被搞反的优化顺序。
- Draco 解码器必须自托管。依赖 CDN 会在国内网络下引入额外往返延迟,抵消压缩收益;且离线/内网部署会直接失败。
--texture-resize 1024是无差别缩放。对需要近距离观察的贴图(如产品细节、铭牌文字)可能明显糊掉,这类纹理要单独保留原尺寸。- 忘记
dispose()会内存泄漏。SPA 里切换场景几次就可能 OOM,这是最隐蔽也最常见的问题。 dpr不封顶是移动端掉帧的头号原因。高分屏上像素量按平方增长。- 桌面端跑分不代表移动端。原文的目标基准(移动端低于 2MB、中端 60 FPS)必须在真机上验证。
- WebP 只是省带宽,不省显存。纹理解码后仍以位图驻留显存,显存吃紧要上 KTX2。
