#3d #压缩 #图形 #有损 #游戏开发

tmf

TMF是一种3D模型压缩格式,压缩比在4.2到1.95之间,读取速度非常快,压缩和解压缩模型之间没有视觉差异

3个不稳定版本

0.2.1 2024年6月20日
0.2.0 2023年6月10日
0.1.1 2023年2月23日
0.1.0 2023年2月23日

数据格式中排名#10

MIT许可证

10MB
4.5K SLoC

TMF - 高压缩比(高达89%),闪电般的3D模型格式

什么是TMF?

tmf是一种专注于

  1. 保留图形保真度
  2. 实现高压缩比
  3. 非常快
  4. 提供非常友好和明确的API,以及高质量的文档。

tmf擅长什么?

当操作中等大小的3D模型(<100 k个三角形)时,tmf效果最佳,这些模型具有相当一致的LOD。

TMF实现目标的效果如何?

  1. 关于视觉质量,您可以很容易地自行判断
  2. 压缩比通常在86-89%之间,具体取决于质量设置。在更严格的设置(保留所有顶点数据的精确顺序)下,tmf的压缩比约为~70%
  3. 解码速度非常高,在某些情况下,比未压缩格式的读者快一个数量级。例如,将位于tests目录中的blender测试猴子(Suzanne)文件(15.7k个三角形,8.2k个点)细分2次解码仅需< strong>678.18 µs(0.67818 ms)! 由于内置tokio集成,解码可以自动在多个线程之间分割,从而进一步提高解码速度。在单线程上解码包含尼菲蒂蒂半身像的网格(一个大约有200万个三角形的3D模型),需要< strong>220-240 ms,在8个线程上只需要< strong>84 ms,在一个8核系统(4个物理核心)上。这意味着tmf非常快。但是,请注意,tmf的压缩算法难以处理非常大的模型,因此具有数百万个三角形的模型在文件大小方面受益较少。
  4. TMF API 主要围绕两种类型:代表网格及其所有操作的 TMFMesh,以及指定质量设置的 TMFPrecisionInfo。所有 TMF 函数和类型都有很好的文档记录,经常带有多个示例,展示了如何使用它们,大大提高了易用性。网格上的所有操作都是显式的。

模型渲染比较

未压缩 .obj 压缩 .tmf 文件(默认设置,允许数据重排)

TMF 是否适合您的项目?

当它不适合时

  1. 您完全不关心读取速度。那么就使用 Draco。它要慢得多(在我的测试中约为 10-20 倍),但它在压缩方面也做得更好。
  2. 您的网格非常大(数百万个三角形)。tmf 是在具有更多谦虚的网格(>150k 个三角形)上优化和测试的。随着三角形和点的增加,其压缩效果变得更差。尽管如此,它仍然非常快,只是不适合这类任务。

当它适合时

  1. 您需要您的模型更小,但又不想牺牲太多的读取速度。
  2. 您的网格尺寸适度或较小(>150k 个三角形)。
  3. 您只需网格看起来完全相同,并且对一些不明显的变化感到满意。

如何实现高压缩速度?

目前,在默认设置下,TMF 使用位操作(位移和或)来读取数据,这使得它能够以非常高的速度读取数据。此外,TMF 是线程安全的,并具有内置的可选多线程功能,允许多个核心同时解码模型的多部分,从而进一步提高速度。

它是如何工作的?

虽然我在传统意义上将 tmf 标记为“有损压缩格式”,但实际上它并没有压缩任何东西(至少目前如此)。大部分的空间节省来自于将模型数据存储在不同的数据结构中,这些数据结构更好地反映了它们存储的数据,并且仅保存所需精确度的数据(例如,9 或 23 位数据类型)。

比较

测试中使用的模型是 Blender 猴子(Suzzane)。TMF 文件使用默认设置保存(TMFPrecisionInfo::default())。

文件大小比较

格式 大小
.obj 1.3 MB
zip(deflate)压缩 .obj 367.7 kB
.fbx 651.0 kB
zip(deflate)压缩 .fbx 600.6 kB
.gltf 476.5 kB
zip(deflate)压缩 .gltf 302.1 kB
.glb 356.6 kB
zip(deflate)压缩 .glb 267.5 kB
.tmf 308.3 kB
应用了预编码优化后的 .tmf 161.9 kB
应用了预编码优化和手动选择的品质设置后的 .tmf 142.4 kB
zip(deflate)压缩 .tmf 307.9 kB
应用了预编码优化后的 zip(deflate)压缩 .tmf 160.2 kB
应用了预编码优化、手动选择品质设置后的 zip(deflate)压缩 .tmf 141.0 kB
最大压缩设置下的 Draco ~22 kB

TMF 与 Draco 的比较。

Draco 在压缩方面比 TMF 明显更好。如果您只想减少文件大小,那么只需使用 Draco。但如果您需要高压缩和快速读取,tmf 可以是一个可行的替代方案。

一些优缺点比较

注意:当给出压缩比率/百分比时,所有格式(例如,tmf、draco、fbx)都与未压缩的 .obj 基础进行比较.

类别 Draco TMF
压缩比率 Draco通常在压缩数据方面表现更优,根据压缩设置,压缩率可以在~80%-98%之间。 TMF可以将文件压缩到约87.3%。
3D模型(Suzanne)的读取时间 7-10毫秒 ~0.6毫秒
压缩对读取时间的影响 读取时间随着压缩级别的增加而增加 对于大多数设置,读取时间随着压缩级别的增加而减少
3D模型(Suzanne)的写入时间 10-18毫秒 ~7毫秒
语言 C++ Rust
官方Rust支持 本地
构建依赖 C++编译器、cmake、make 仅标准rust工具链
Rust项目中使用 需要手动链接 使用cargo自动安装和链接

什么因素可能导致特定网格的压缩效率降低?

LOD差异很大:保存系统会动态调整网格的LOD。例如,一个低多边形城堡网格可能以10厘米的精度保存,而草莓模型的精度可能为1毫米。将这两个对象保存在一个网格(而非文件!)中,将迫使城堡网格以更高的精度保存,浪费空间。因为大多数网格自然会具有一致的LOD,而那些不一致的网格几乎总会引起其他地方的问题,所以这种情况很少遇到。

示例

网格加载

加载单个网格

use tmf::TMFMesh;
use std::fs::File;
let input = File::open("suzanne.tmf").expect("Could not open .tmf file!");
let (mesh,name) = TMFMesh::read_tmf_one(&mut input).expect("Could not read TMF file!");
// Geting mesh data
let vertices = mesh.get_vertices().expect("No vertices!");
let vertex_triangles = mesh.get_vertex_triangles().expect("No vertiex triangle array!");
let normals = mesh.get_normals().expect("No normals!");
let normal_triangles = mesh.get_normal_triangles().expect("No normal triangle array!");
let uvs = mesh.get_uvs().expect("No uvs!");
let uv_triangles = mesh.get_uv_triangles().expect("No uv triangle array!");
// Can provide arrays laid out like OpenGL buffers for ease of use when developing games!
let buff_vert_array = mesh.get_vertex_buffer();
let buff_norm_array = mesh.get_normal_buffer();
let buff_uv_array = mesh.get_uv_buffer();

加载多个网格

use tmf::TMFMesh;
use std::fs::File;
let input = File::open("suzanne.tmf").expect("Could not open .tmf file!");
let meshes = TMFMesh::read_tmf_one(&mut input).expect("Could not open TMF file!");
for (mesh,name) in meshes{
    do_something(mesh,name);
}

网格保存

保存单个网格

use tmf::TMFMesh;
use std::fs::File;
let output = File::create("suzanne.tmf").expect("Could not create output file!");
let settings = TMFPrecisionInfo::default();

// Change TMF mesh to have better laid out data. This can save significant ammounts of space.
mesh.unify_index_data();

mesh.write_tmf_one(&mut output,&settings,name).expect("Could not save TMF mesh!");

保存多个网格

use tmf::TMFMesh;
use std::fs::File;
let output = File::open("suzanne.tmf").expect("Could not create .tmf file!");
TMFMesh::write_tmf(meshes,&mut input,&settings).expect("Could not write TMF mesh!");

功能

0.1(当前版本)

  • 导出.obj
  • 导入.obj
  • 导入未三角化的.obj模型 - 实验性,仅支持凸多边形
  • 写入/读取.tmf文件
  • 点位置
  • 点法线
  • 点Uv坐标
  • 网格三角形
  • 点云
  • 一个文件中的多个网格
  • 完全可定制的保存精度设置
  • 完整文档
  • 每个功能的示例
  • 切线数据
  • 自定义网格数据
  • 支持RGBA顶点颜色(使用浮点属性支持灰度),以及浮点/整数顶点属性。

计划中的功能

  • 顶点组
  • 材质 已经完成了一些初步工作

关于压缩的更深入解释

基于数学的节省

许多用于保存3D模型的格式惊人地浪费。即使在无损压缩的情况下,也有很多机会减少文件大小。例如,许多模型格式将表面法向量视为任何其他向量。但它们并不像其他向量!它们有一些特殊的属性,可以利用这些属性更有效地保存它们。具体来说

  1. 法向量中的所有组件都落在范围 <-1,1> 之内。这意味着诸如1.3、123.0、69.323或甚至6.50e+12之类的值在法向量中永远不会出现,因此使用支持这些值的格式进行保存是浪费的。
  2. 所有法向量都满足条件 x^2 + y^2 + z^2 = 1。这意味着有大量的向量所有组件都在范围 <-1,1> 之内,但不是有效的表面法线。如果支持保存这些无效值,这意味着有浪费的空间。因此,通过考虑法线的这些属性,可以将法线以这种方式保存,即每个保存的位组合对应于不同的法线,不浪费任何空间!

对模型数据的每个元素都采取类似的方法,进一步减少大小。

基于位与字节的节省。

使用字节对齐的数据类型的一个缺点是在保存数据时缺乏精度粒度。一个很好的例子可能是UV坐标,它应该表示一个位于1024像素纹理上的点,精度为.25像素。进行一些简单的计算,可以确定所需的精度为log2(1024/.25) = log2(4096) = 12位。但可用的数据类型要么太小(u8)要么太大(u16,25%的磁盘空间将被浪费!)。解决方案是放弃字节对齐。这会带来轻微的性能损失,需要执行位移动操作,并且无法使用预构建的压缩算法(它们假设字节对齐),但具有巨大的优势,即使用恰好足够保存所需数据的类型而无需更宽的类型。数据以我所说的UBA(非对齐二进制数组)的形式排列。UBA中的数据由一系列任意二进制大小的数据组成,连续数据可能跨越字节边界,可以从字节的任何位置开始或结束,且没有填充。元素的大小通常在UBA本身之前指定。对于某些宽度,如9位,使用UBA可以节省多达44%的空间!

依赖关系