3D模型由几何数据和属性数据构成,几何数据描述顶点位置与三角形连接关系,属性数据则包括法线、纹理坐标、颜色、材质参数等。未压缩的模型通常以32位浮点数存储每个坐标分量,一千万顶点的模型仅位置数据就占120MB,加上法线和UV后体积往往翻倍。因此,针对这两类数据分别设计压缩策略是降低模型体积、提升加载速度的关键。几何压缩聚焦于顶点坐标和拓扑结构,属性压缩则处理与表面特性相关的附加信息。两类压缩在算法上有明显差异,但目标一致:用尽可能少的字节表示接近原始质量的数据。

几何压缩的核心思路与典型算法
几何压缩的首要目标是减少顶点坐标的存储位数,同时将视觉误差控制在可接受范围内。最直接的方法是量化:将模型包围盒归一化后,把浮点坐标映射到固定位数的整数网格上。例如,将包围盒范围映射到0-65535的16位无符号整数,每个坐标分量只需2字节,相比32位浮点的4字节节省一半。量化步长决定了精度损失,包围盒尺寸除以65535即为最小可分辨距离,通常远小于渲染时的像素精度,因此视觉上几乎无差异。
除了量化,预测编码是几何压缩的另一个核心手段。由于三角网格中的顶点往往由相邻顶点插值而来,可以利用平行四边形预测:对于三角形的一个顶点,用对边两个顶点加上对边法向偏移来预测其位置,然后仅编码预测残差。残差的数值范围远小于原始坐标,配合熵编码(如Range编码或算术编码)可以进一步压缩。Google开源的Draco压缩库将这一流程系统化:先对网格连接性进行Edgebreaker拓扑压缩,再对顶点位置进行有损量化与预测编码,最后用熵编码打包。Draco支持10到16位量化,位宽越低压缩率越高,但精度损失也越明显。
以下是一个简单的量化示例,展示如何将浮点坐标映射到16位整数:
// 假设模型包围盒已知:minVal 和 maxVal float minVal = -1.0f, maxVal = 1.0f; float value = 0.35f; uint16_t quantized = (uint16_t)round((value - minVal) / (maxVal - minVal) * 65535.0f); // 解码时还原为浮点: float restored = minVal + (quantized / 65535.0f) * (maxVal - minVal);
另一种几何压缩思路是网格简化,即减少顶点和三角形数量。这属于有损压缩,但通过LOD(细节层次)技术可以在不同距离使用不同精度模型。简化算法如基于二次误差度量(QEM)的边折叠,能在保持轮廓的同时大幅降低几何复杂度。不过网格简化改变的是模型结构,与量化压缩互补:前者减少数据总量,后者降低每个数据的位宽。
属性压缩:从纹理坐标到材质信息的处理
属性数据具有与几何坐标不同的约束。法线是单位向量,三个分量满足x²+y²+z²=1,因此理论上只需两个分量加上符号即可表示。纹理坐标(UV)通常范围在[0,1]或某个有限区间,颜色分量也在0-1之间。这些先验知识为属性压缩提供了额外空间。属性压缩同样采用量化,但可以针对不同属性设计专门的编码。
法线压缩最常用的是八面体编码(Octahedral Encoding)。该方法将单位球映射到一个二维八角形区域,再用固定位数量化两个坐标,解码时通过反射恢复三维法线。相比直接量化3个分量,八面体编码在相同精度下可节省约1/3的存储,且计算开销极低。纹理坐标压缩则利用相邻顶点UV变化平滑的特点,采用增量编码:存储当前顶点与预测值(例如前一个顶点的UV)的差分,差值通常较小,可以用更少位表示。颜色数据如果没有特殊约束,可直接量化到8位每通道,但也可以转换到更适合压缩的色彩空间(如YCoCg)以减少相关性。
以下是一个简化的八面体法线编码函数示例:
// 输入:单位向量 (nx, ny, nz)
float absSum = fabs(nx) + fabs(ny) + fabs(nz);
float ox = nx / absSum;
float oy = ny / absSum;
if (nz < 0.0f) {
float oldX = ox;
ox = (1.0f - fabs(oy)) * (ox >= 0.0f ? 1.0f : -1.0f);
oy = (1.0f - fabs(oldX)) * (oy >= 0.0f ? 1.0f : -1.0f);
}
// 将 ox, oy 映射到 [0,1] 并量化到8位
uint8_t cx = (uint8_t)round((ox * 0.5f + 0.5f) * 255.0f);
uint8_t cy = (uint8_t)round((oy * 0.5f + 0.5f) * 255.0f);
在实际压缩库中,Draco对属性采用自适应量化:根据属性的取值范围和用户指定的精度自动选择量化位宽。例如法线通常量化到10位或更少,纹理坐标可根据模型UV展开的密集程度动态调整。Meshoptimizer库则提供了单独的属性量化接口,将浮点数组转换为指定位宽的整数数组,并可选地用熵编码进一步压缩。属性压缩的效果往往不如几何压缩显著,但累积起来仍能减少模型体积20%-40%。
实际工程中如何选择与集成压缩方案
目前主流的3D模型压缩方案有Draco、Meshoptimizer以及glTF的KHR_draco_mesh_compression扩展。Draco压缩率最高,特别适合静态模型的离线压缩,但其解码依赖较大的库,在资源受限的设备上可能带来额外开销。Meshoptimizer则更轻量,提供顶点缓存优化、量化、索引压缩等多个独立工具,适合需要精细控制压缩过程的引擎集成。glTF扩展允许在标准glTF文件中嵌入Draco压缩数据,Web端通过Three.js等框架可直接加载,无需手动处理二进制流。
压缩参数的选择需要在体积、精度和解码时间之间权衡。量化位宽是最敏感的参数:几何坐标通常用12-14位即可满足视觉无损,16位则接近无损;法线用8-10位;纹理坐标如果模型有大量拉伸区域,可适当提高位数。预测编码几乎总是有益,但会增加解码复杂度。对于实时应用(如游戏、AR),建议使用量化加轻量熵编码;对于Web展示,Draco的无损或高位宽模式能保证跨平台兼容性。
以下展示使用Meshoptimizer对顶点缓冲区进行量化的简单代码片段:
#include <meshoptimizer.h>
// 假设 vertices 是 float 数组,共 vertexCount * 3 个分量
std::vector<unsigned char> encoded(vertexCount * 3 * 2);
meshopt_encodeVertexBuffer(
encoded.data(),
encoded.size(),
vertices.data(),
vertexCount,
sizeof(float) * 3,
16 // 每个分量的目标位数
);
集成到渲染管线时,需要确保解码后的数据能够直接上传到GPU。桌面端通常离线压缩、运行时一次性解压到顶点缓冲区;Web端则借助WebAssembly解码器在浏览器内解压。注意解码过程本身消耗CPU时间,对于超大模型(超过1000万顶点)可能造成加载卡顿,因此建议将解码放在Worker线程中,避免阻塞主线程。此外,压缩后的模型文件更小,也降低了网络传输和存储成本,对于需要频繁加载模型的云端渲染服务尤其重要。
总体而言,几何压缩与属性压缩是3D内容优化的基础技术。前者通过量化、预测编码和拓扑压缩大幅削减坐标数据,后者利用法线、UV等属性的数学约束实现高效编码。合理组合这两种压缩,可以使模型体积减少80%以上,同时几乎不影响视觉表现。在实际项目中,建议先用Draco离线压缩获得高压缩率,再根据目标平台选择是否改用Meshoptimizer进行更轻量的运行时处理。

