最近在对一些模型进行轻量化的处理,需要压缩模型的大小,并且要求可以较快的读取加载。原始的模型由3DMax导出的FBX获得,鉴于上述要求与FBX模型的格式就选择了与渲染时的Vertex Buffer最接近的方式来对模型进行存储,存储结构如下:
Triangle Data->Vertex Data
其中每个Triangle data存储其所对应的顶点的索引,每个顶点数据中又存储其对应的Position, Normal, Tangent, UV[n]...,每个Vertex Data均不相同(相同的条件为包含所有元素Position, Normal, Tangent均相同),首先尝试直接以这种结构对模型进行存储,但发现得到的binary模型依然较大。而且通过output发现其中的Vertex Data均不相同,但其中包含的Position, Normal, Tangent等均包含较多的重复元素,因而想到再加一层线性索引来剔除这些几何数据中的冗余元素。
首先需要考虑的是这样的再索引会不会不仅不能减小模型的大小,反倒使之增大。于是判断:用索引的Vertex Data需要存储5个UINT的索引(UV通道为2),5 X 4 = 20 Bytes, 而一个Float3的大小为3 x 4 = 12 Bytes, 直观上这样改变会带来不小的空间优势。感觉用统计之类的方法来分析有些麻烦,于是决定直接实现之再来验证。而实验的结果也符合自己的预期,模型的大小减少得还是比较可观的。用了CryTek的Sponza来进行测试(Max修改过):
三角形数目:182665
删除冗余几何数据前:40.4MB, Vertex Data: 547995
删除冗余几何数据后:18.5MB, Position:94983, Normal&Tangent: 361846, UV: 9692
模型大小控制的目的达到了,但带来了另外一个非常致命的问题:冗余顶点的搜索效率。对于上述可以达到十万级别的顶点数量,直接的搜索简直是*_*!!。不过对其也进行了测试,对于该模型处理进间甚至达到好几十分钟,若不加速则一点可行性都没有。首先想到的是GPU来加速,但是觉得对于这样的一种搜索结构不太适合。之后就想到了用空间划分来进行加速,这有些类似于用KD-Tree进行光线跟踪,BSP进行碰撞检测。
以空间顶点的Position为例,其对于某一个顶点,它不可以与旁边相跟很远的顶点有相同关系进而是冗余点,因而只需要检索判断空间位置处于其周围的那些邻居点就可以了,这样就可以将每个点待判断的点的数日大大减少。对于向量,UV进行分割加速方法与Position类似,向量的话就以大体方向把各向量归成一堆。那么现在如何确定那些点是这个点的邻居点呢?这就需要用到各种空间分割来施展它们的作用了,什么OCTree, KDTree, BVH, BSP各种,你想用那种就用那种^0^。为了简单起见这些用了OCTree,简单易行。
实现之后得到的加速效果很明显,对于上述模型进行处理只需要4,5s以内的时间(用的树还很浅,懒得深分了,这个也只是边角工作)。