3D 模型为什么这么大?
你的模型在偷偷长胖
你导出了一个 GLB 文件,10MB,感觉还行。传到手机上一打开——白屏、卡顿,甚至直接崩了。
在电脑上没问题,到了手机上就爆炸。这不是代码的锅,而是 3D 模型有一个不太直觉的特性:它在磁盘上和显存里,完全不是一个大小。
一张 JPG 图片在磁盘上可能只有 200KB。但 GPU 不认识 JPG,它只认识原始像素。所以上传到显存之前,这张图会被完全解压。一张 2048x2048 的纹理,解压后要吃掉约 22MB 显存。如果你用了 6 张贴图(albedo、normal、roughness、metallic、AO、emissive),光一个材质就要 132MB。
手机总共可能也就 2-4GB 显存,一个模型的纹理就吃了 3-6%。场景里有 10 个模型呢?
拆开看看,体积都花在哪了
一个典型的 GLB 模型主要由三部分组成:顶点数据、纹理贴图,以及元数据和动画。
拿一个真实的 PBR 模型来看:
| 组成部分 | 具体内容 | 典型占比 | 说明 |
|---|---|---|---|
| 纹理贴图 | albedo、normal、roughness、metallic、AO 等 | 70-85% | 几乎永远是体积大户 |
| 顶点数据 | position、normal、UV、tangent、颜色 | 10-20% | 取决于模型复杂度 |
| 动画数据 | 骨骼、蒙皮、关键帧 | 0-15% | 有动画才有 |
| 其他 | 材质定义、场景结构、相机 | < 2% | 可忽略 |
纹理贴图占了 80% 左右。很多时候你以为要优化的是顶点,但真正占地方的是贴图。
"磁盘小"不等于"显存小"
这可能是理解 3D 性能最关键的一个点。
PNG 和 JPG 是为网络传输设计的——磁盘上很小,下载快。但 GPU 不能直接用它们,必须先完全解压成原始像素。计算方法:
显存占用 = 宽度 * 高度 * 4字节(RGBA) * 1.333(含mipmap)
一张 4096x4096 的 RGBA 纹理:
| 指标 | 数值 |
|---|---|
| PNG 文件大小 | ~8MB |
| JPG 文件大小 | ~1.5MB |
| 显存占用(含 mipmap) | ~87MB |
1.5MB 的 JPG,到了显存里变成 87MB。
Mipmap 是什么? GPU 会为纹理生成一系列逐级缩小的版本,从原始大小到 1x1 像素,每个级别是上一级的一半。这让远处的物体渲染更快更清晰,但要多占约 33% 的显存。几乎所有的 3D 应用都会用 mipmap,所以这个开销基本是标配。
所以 PNG/JPG 就像旅行压缩袋——压缩之后小小的方便带,到了目的地得全部撑开。下载快了,但显存一点没省。
显存不够会怎样
不会弹个"显存不足"的提示框。实际情况更糟糕:
- 移动端:页面白屏,或者系统直接杀掉标签页
- VR 头显:掉帧。VR 里掉帧不是"有点卡",是会引起眩晕的
- 桌面端:纹理闪烁、降级、渲染变慢
Reddit 上有个开发者做 WebXR 画廊,往 Quest 上塞了 60 张立体图。一开始正常,后来越来越不稳定直到崩溃。他花了几天查代码,最后发现从来没认真想过 VRAM 这回事——就是一直在往 GPU 里塞 JPG。
压缩的两条路
3D 模型压缩主要分两个方向:
顶点压缩——把顶点坐标、法线、UV 等几何数据用更紧凑的方式存储。比如把 32 位 浮点数换成 16 位整数(这叫 quantization,量化)。代表性方案:Draco、MeshOpt、KHR_mesh_quantization。
纹理压缩——让贴图在显存里也保持压缩状态。GPU 在采样时实时解码单个像素,几乎没有性能开销。代表性方案:KTX2 + Basis Universal。
| 顶点压缩 | 纹理压缩 | |
|---|---|---|
| 减什么 | 几何数据 | 贴图 |
| 典型效果 | 减小 50-90% | 磁盘减 50-70%,显存减 75% |
| 有损吗 | 是,精度下降 | 是,画质下降 |
| 适用场景 | 顶点密集的模型 | 几乎所有 PBR 模型 |
| 详见 | 第 2 篇 | 第 3、4 篇 |
一个常见误区:用了 Draco 压缩顶点就觉得万事大吉。但纹理占了模型 80% 的体积,你把顶点砍一半,整体可能只瘦了 10%。两头都得管。
没有一种方法通吃所有场景
这是整个系列的核心观点:
不同平台、不同设备、不同使用方式,需要不同的压缩策略。
| 场景 | 首要瓶颈 | 重点关注 |
|---|---|---|
| 桌面 Web 展示 | 下载速度 | 文件大小 |
| 移动端浏览器 | 显存 | 纹理压缩 |
| VR 头显 | 显存 + 帧率 | 纹理压缩 + 顶点简化 |
| 微信小程序 | 包大小 + 兼容性 | 轻量方案(MeshOpt) |
| 大型场景 | 显存 + draw call | 全方位压缩 + LOD |
后面的每一篇不会只说"用 X 就对了",而是会讲清楚:X 适合什么场景,什么时候反而帮倒忙,什么情况下该用 Y。
下一步
这篇搞清楚了问题。下一篇动手——认识顶点压缩三把工具:量化、MeshOpt 和 Draco。