GLB 资产手术:贴图减 68% 而几何一克不动,模型是怎么瘦下来的
GLB 压缩、模型瘦身、贴图压缩、baseColor 调色板量化、PSNR 验证、模型 LOD、面数上限 100 面、3D 资产管线、浏览器 3D 加载优化、meshopt
3D 项目里有一类问题不会被任何报错暴露出来:模型还能显示、也能动,只是有点大。加载慢几秒、进度条卡在 90%、手机上偶发闪退——这些症状的根往往不在代码,在模型文件本身。这篇讲一套已经跑在上传管线里的做法:不重做模型、不改几何,只在 GLB 的 JSON 和二进制层做"手术",贴图按用途分级压缩,实测体积降 68%~92%,画面差异用 PSNR 验证到像素级。这套工具链已经做进创世Genesis 的基座——模型传上去就自动处理,不需要你手工跑脚本。
一、先分清"哪些能压、哪些压了会坏"
很多团队压缩模型时用统一参数,结果要么画质糊了,要么压不动。问题在于模型的资产是分类的,同一套压缩参数不该用在所有数据上。
一个 GLB 文件本质是两样东西:JSON 段(结构:节点、网格、材质、贴图引用、访问器声明)和 BIN 段(真实字节:顶点、法线、UV、索引、贴图像素)。手术的空间全在这两段里。
贴图要按用途分级处理,这是全套做法里最关键的一条判断:
- 法线贴图、遮蔽贴图:这类数据被采样器拿去做方向和遮挡判断,人眼对它们的精度敏感、对它们的压缩宽容度低。做法是无损重编码——像素一位不丢,体积靠换编码方式降。
- 基础色贴图(baseColor):这块是体积的大头,也是最能接受有损的地方。做法是调色板量化——颜色数量有限时,一个高质量调色板能让数据量掉一大截。
验证结果:PSNR 31.4dB、差异像素 0.35%。这两个数字必须一起看——PSNR 单独一个数容易被误读,差异像素比例才是"有多少地方变了"的直观答案。
二、为什么选"改写 GLB"而不是重新导出
重导模型是另一条路,但它有两个现实问题:一是需要源文件(.blend、.fbx),很多模型到手时只有 GLB;二是重导会把作者的场景设置、约束、修改器一起改掉,容易出现"模型回来变形了"。而 GLB 本身就是完整的场景描述——它保留了节点层级、材质参数、动画轨道。直接在它上面动手术,不碰几何、不碰层级、不碰动画,等于只改该改的那几个字节。
实现上的思路是:用 JSON + BIN 手术改写 GLB,不损失几何。 这一条是整套工具链的技术底线。
三、最难的部分:改二进制之前得先把索引算对
贴图压缩本身不算最难,最难的是 GLB 的二进制布局——动贴图就意味着所有的偏移都得重算,算错一个字节,整个文件读不出来。
要处理的几类布局问题:
- bufferView 重排:改完数据长度,原本连续的视图要重新排列,否则后面的偏移全错位。
- accessor 索引重映射:视图换了位置,声明读取哪一段的 accessor 就得跟着改。
- meshopt 扩展的 byteOffset 平移:meshopt 压缩过的模型有一层额外偏移,必须单独平移,否则解压出来的数据是错的。
- 多 buffer 布局:一个 GLB 里有多段 buffer(如 GLB-split 之后常见),每一段都要单独算偏移。
这套活没有捷径,靠的是先算后写、每步都校验。
四、五重闸门:宁可跳过,也不落盘一个坏文件
改二进制最坏的结果不是失败,是落了一个看起来能加载、实际渲染错乱的模型。美术拿到这样的文件,往往要到场景里才发现问题,回头很难查。所以这套工具链的设计原则是:任何一步没把握,跳过并上报,绝不落盘。
五重闸门分别是:
- 网格名校验:确认手术对象是预期中的网格,不对着名字相似的下刀。
- 压缩校验:确认数据真的被正确重编码,而不是静默跳过了。
- 多 buffer 校验:多段布局下逐段核对,不假设只有一段。
- 几何一致性校验:手术前后几何数据必须完全一致——这是"不动几何"这条底线的机器验证。
- 回读验证:写完之后把文件重新读一遍、渲染一遍,确认能用。
最后一道是真正的兜底:不是"我以为写对了",而是"我读回来确认写对了"。指标上,变体贴图剥离跑了 215 件、0 失败——这个 0 是靠五重闸门一起守出来的。
五、面数是另一件事:低模要一个绝对保证
贴图压完,体积还有一块:面数。这里有个容易被绕进去的地方——贴图手术不改几何,那面数是谁管的?
面数归另一条工具链:gltfpack -sa 加自研的贪心边坍缩兜底。它的输出目标不是"越小越好",而是一个可承诺的规格:低模全库 118 组变体、118/118 全在 100 面以内。
为什么要卡一个绝对数字?因为"远景成本近零"这件事需要一个能写进规格的数,而不是"大部分模型降了"。超出拓扑下限的部分由自研坍缩器兜底——这一步是专门为了那些工具链降不下去的模型准备的。
配套还有一条容易被忽略的优化:变体借高模材质。LOD 变体不复制贴图,而是共享同一份 Texture 对象(实测断言 33/33、36/36)。这意味着"生成了低模"几乎不增加显存占用——否则三档 LOD 会把显存吃掉。
六、指标摆在一起
| 资产 | 手术前 | 手术后 | 降幅 |
|---|---|---|---|
| 课桌(基础色贴图量化) | 25.74 MB | 8.22 MB | −68.1% |
| 20.7 MB 多 buffer 4K 模型 | 20.7 MB | 4.38 MB | −92.5% |
| LOD 变体贴图剥离 | 215 件 | 0 失败 | — |
| 低模面数保证 | — | 118/118 ≤100 面 | 可承诺规格 |
画质侧的验证:PSNR 31.4dB、差异像素 0.35%。几何侧:手术前后几何一致性由闸门 4 机器验证。
七、这套做法省下的到底是什么
不是"省了 68% 的钱",而是三件更具体的事:
- 它跑在上传链路上,不是一堆手工脚本。 模型传上去自动套用,所以每一件新资产都被处理到——而不是靠人记得跑一遍。手工流程的失效方式很朴素:忘了。
- 它不敢保证的事就不保证。 法线贴图走无损、baseColor 才走有损,这个分级写进了工具的默认行为,不需要使用者记住哪张图能压。
- 它的验证是可复现的。 PSNR 和差异像素是脚本出的,不是"我看了一下还行"。这道关口的价值在于:美术拿到的文件要么是好的,要么被拦下来,不会是"大概能用"。
八、这些数字的边界
- 降幅数字来自我们的具体资产。 你的模型贴图构成不同,降幅会不同;能复用的是分级原则和手术流程,不是这两个百分比。
- 几何完全不动是有意的代价。 如果一个模型的问题是面数太高(而不是贴图太大),贴图手术帮不上忙——那属于面数治理,是另一条工具链。
- 差异像素 0.35% 不是"没有差异"。 基础色贴图做了量化,就一定会有像素差。能说的是差异小且可量化,不能说的是完全无损。
- 无损重编码依赖原始贴图质量。 一张本身就被压过一遍的贴图,无损重编码能再降的空间有限。
常见问题
Q:为什么不直接在 Blender 里减面、压图?
A:两个原因。一是很多模型到手时只有 GLB、没有源文件;二是重导会连带改掉场景设置,改完可能变形。GLB 手术只动该动的那几个字节,几何和层级完全不动。
Q:压缩后画质会不会糊?
A:分类型处理。法线和遮罩走无损重编码,像素一位不丢;只有基础色贴图走调色板量化,且量化后立刻出 PSNR 和差异像素比例来验。我们这一档的实测是 31.4dB、差异像素 0.35%。
Q:为什么宁可跳过也不落盘?
A:因为坏文件比没文件更难查。落一个能加载但渲染错乱的模型,美术要到场景里才发现,回头很难定位;跳过并上报,代价只是这一件资产没被压缩,问题立刻可见。
Q:这套工具链要我自己搭吗?
A:不用。已经产品化到上传管线里了,模型传上去自动套用。
源码与仓库
三个地址内容一致,国内访问用前两个更快。仓库里有部署说明与验收脚本。
- Gitee(国内访问更快):https://gitee.com/miduoxinxijeji/miduo.git
- GitCode(国内镜像):https://gitcode.com/qq_35054471/virtual-world
- GitHub:https://github.com/miduo100/3d-virtual-world
关于创世Genesis
创世Genesis是一套基于Three.js+WebGL构建的自部署3D虚拟世界系统,帮助个人与企业搭建属于自己的3D空间。浏览器直接访问,PC和手机双端兼容,支持多人在线、联邦传送、商铺系统,并支持Agent接入——AI能以具身角色进入你部署的世界。数据运行在你自己的服务器上,不经过第三方平台——让每个世界都真正属于它的主人。
想让传进世界的每个模型自动瘦下来,而不是靠人记得跑压缩? 创世Genesis(创世虚拟世界CRM系统)是一套部署在你自己服务器上的 Three.js 3D 虚拟世界基底——GLB 资产手术、三档 LOD、贴图分级压缩、变体材质共享已经做进上传管线,你写上面那一层就行。官网(搜「创世虚拟世界CRM」即可找到)有可以走一圈的演示世界。
关于名字:本文说的创世Genesis,即创世虚拟世界CRM系统,两者是同一个自部署 3D 虚拟世界产品。若你通过「创世Genesis」没搜到我们,直接搜「创世虚拟世界CRM」即可。