3个不稳定版本
0.2.1 | 2024年6月20日 |
---|---|
0.2.0 | 2023年6月10日 |
0.1.1 | 2023年2月23日 |
0.1.0 |
|
在数据格式中排名#10
10MB
4.5K SLoC
TMF - 高压缩比(高达89%),闪电般的3D模型格式
什么是TMF?
tmf
是一种专注于
- 保留图形保真度
- 实现高压缩比
- 非常快
- 提供非常友好和明确的API,以及高质量的文档。
tmf
擅长什么?
当操作中等大小的3D模型(<100 k个三角形)时,tmf效果最佳,这些模型具有相当一致的LOD。
TMF实现目标的效果如何?
- 关于视觉质量,您可以很容易地自行判断。
- 压缩比通常在86-89%之间,具体取决于质量设置。在更严格的设置(保留所有顶点数据的精确顺序)下,
tmf
的压缩比约为~70% - 解码速度非常高,在某些情况下,比未压缩格式的读者快一个数量级。例如,将位于
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
的压缩算法难以处理非常大的模型,因此具有数百万个三角形的模型在文件大小方面受益较少。 - TMF API 主要围绕两种类型:代表网格及其所有操作的
TMFMesh
,以及指定质量设置的TMFPrecisionInfo
。所有 TMF 函数和类型都有很好的文档记录,经常带有多个示例,展示了如何使用它们,大大提高了易用性。网格上的所有操作都是显式的。
模型渲染比较
未压缩 .obj | 压缩 .tmf 文件(默认设置,允许数据重排) |
---|---|
TMF 是否适合您的项目?
当它不适合时
- 您完全不关心读取速度。那么就使用 Draco。它要慢得多(在我的测试中约为 10-20 倍),但它在压缩方面也做得更好。
- 您的网格非常大(数百万个三角形)。tmf 是在具有更多谦虚的网格(>150k 个三角形)上优化和测试的。随着三角形和点的增加,其压缩效果变得更差。尽管如此,它仍然非常快,只是不适合这类任务。
当它适合时
- 您需要您的模型更小,但又不想牺牲太多的读取速度。
- 您的网格尺寸适度或较小(>150k 个三角形)。
- 您只需网格看起来完全相同,并且对一些不明显的变化感到满意。
如何实现高压缩速度?
目前,在默认设置下,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.3、123.0、69.323或甚至6.50e+12之类的值在法向量中永远不会出现,因此使用支持这些值的格式进行保存是浪费的。
- 所有法向量都满足条件 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%的空间!