zeux/meshoptimizer

GitHub Trending (daily) 工具

摘要

meshoptimizer 是一个库,提供优化三角形网格以用于GPU渲染的算法,提升顶点和索引数据的效率,同时降低存储开销。

网格优化库,使网格更小、渲染更快。
查看原文
查看缓存全文

缓存时间: 2026/07/10 12:08

zeux/meshoptimizer 源代码:https://github.com/zeux/meshoptimizer # 🐇 meshoptimizer 操作状态 (https://github.com/zeux/meshoptimizer/actions) codecov.io (https://codecov.io/github/zeux/meshoptimizer?branch=master) MIT GitHub (https://github.com/zeux/meshoptimizer) ## 目的 当 GPU 渲染三角形网格时,GPU 管线的各个阶段需要处理顶点和索引数据。这些阶段的效率取决于您提供的数据;该库提供了一些算法,用于优化这些阶段的网格,以及降低网格复杂性和存储开销的算法。 该库为所有算法提供了 C 和 C++ 接口;您可以从 C/C++ 或通过 FFI(如 P/Invoke)从其他语言使用它。如果您想从 Rust 使用此库,应使用 meshopt crate (https://crates.io/crates/meshopt)。某些算法的 JavaScript 接口可通过 meshoptimizer.js (https://www.npmjs.com/package/meshoptimizer) 获得。 两个配套项目与该库一起开发和分发:gltfpack,一个自动优化 glTF 文件的命令行工具,以及 clusterlod.h,一个用于使用聚类简化的连续 LOD 的单头文件 C/C++ 库。 ## 安装 meshoptimizer 托管在 GitHub 上;您可以使用 git 下载最新版本: git clone -b v1.2 https://github.com/zeux/meshoptimizer.git 或者,您可以从 GitHub (https://github.com/zeux/meshoptimizer/archive/v1.2.zip) 下载 .zip 存档。 该库也可作为多个发行版中的 Linux 包使用(ArchLinux (https://aur.archlinux.org/packages/meshoptimizer/)、Debian (https://packages.debian.org/libmeshoptimizer)、FreeBSD (https://www.freshports.org/misc/meshoptimizer/)、Nix (https://mynixos.com/nixpkgs/package/meshoptimizer)、Ubuntu (https://packages.ubuntu.com/libmeshoptimizer)),以及 Vcpkg 端口 (https://github.com/microsoft/vcpkg/tree/master/ports/meshoptimizer)(参见安装说明 (https://learn.microsoft.com/en-us/vcpkg/get_started/get-started))和 Conan 包 (https://conan.io/center/recipes/meshoptimizer)。 gltfpack 可在 Releases 页面 (https://github.com/zeux/meshoptimizer/releases) 上以预构建二进制形式获得,或通过 npm 包 (https://www.npmjs.com/package/gltfpack) 获得。建议使用原生二进制文件,因为它们更高效且支持纹理压缩。 ## 构建 meshoptimizer 以 C/C++ 头文件 (src/meshoptimizer.h) 和一组 C++ 源文件 (src/*.cpp) 的形式分发。要将其包含到您的项目中,您可以使用以下两种选项之一: * 使用 CMake 构建库(作为独立项目或作为项目的一部分) * 将源文件添加到项目的构建系统中 源文件的组织方式使您无需更改构建系统设置,并且只需为您使用的算法添加源文件即可。它们应在所有主要编译器上无需警告或特殊编译选项即可构建。如果您更喜欢合并构建,还可以将源文件连接成一个 .cpp 文件,然后构建该文件。 要使用 meshoptimizer 函数,只需 #include 头文件 meshoptimizer.h;库源代码是 C++,但头文件与 C 兼容。 ## 核心管线 优化网格时,为了最大化渲染效率,通常应将其通过一系列优化(顺序很重要!): 1. 索引化 2. 顶点缓存优化 3. (可选)过度绘制优化 4. 顶点提取优化 5. 顶点量化 6. 索引过滤 7. (可选)阴影索引 ### 索引化 该库中的大多数算法假设网格具有顶点缓冲区和索引缓冲区。为了使算法良好运行以及 GPU 高效渲染网格,顶点缓冲区不能有冗余顶点;您可以从未索引的顶点缓冲区生成索引缓冲区,或重新索引现有的(可能冗余的)索引缓冲区,如下所示: > 注意:meshoptimizer 通常使用 32 位(unsigned int)索引,但使用 C++ API 时,您可以通过提供的模板重载为索引数据使用任何整数类型。按照惯例,重映射表始终使用 unsigned int。 首先,从您现有的顶点(以及可选地,索引)数据生成重映射表: c++ size_t index_count = face_count * 3; size_t unindexed_vertex_count = face_count * 3; std::vector remap(unindexed_vertex_count); // 临时重映射表 size_t vertex_count = meshopt_generateVertexRemap(&remap[0], NULL, index_count, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex)); 注意,在这种情况下,我们只有一个未索引的顶点缓冲区;当输入网格具有索引缓冲区时,需要将其传递给 meshopt_generateVertexRemap 而不是 NULL,并附上正确的源顶点数量。 无论哪种情况,重映射表都是基于输入顶点的二进制等价性生成的,因此生成的网格将以相同方式渲染。二进制等价性考虑所有输入字节,包括如果顶点结构存在间隙则应初始化为零的填充。 生成重映射表后,您可以为目标顶点缓冲区(vertex_count 个元素)和索引缓冲区(index_count 个元素)分配空间并生成它们: c++ meshopt_remapIndexBuffer(indices, NULL, index_count, &remap[0]); meshopt_remapVertexBuffer(vertices, &unindexed_vertices[0], unindexed_vertex_count, sizeof(Vertex), &remap[0]); 然后,您可以通过对它们原地调用其他函数来进一步优化生成的缓冲区。 meshopt_generateVertexRemap 使用顶点数据的二进制等价性,这通常是一个合理的默认值;但是,在某些情况下,某些属性可能存在浮点漂移,导致生成额外的顶点。对于这种情况,可能需要在生成重映射之前量化某些属性(最重要的是法线和切线),或使用 meshopt_generateVertexRemapCustom 算法,该算法允许通过提供自定义比较函数来比较具有容差的各个属性: c++ size_t vertex_count = meshopt_generateVertexRemapCustom(&remap[0], NULL, index_count, &unindexed_vertices[0].px, unindexed_vertex_count, sizeof(Vertex), [&](unsigned int lhs, unsigned int rhs) -> bool { const Vertex& lv = unindexed_vertices[lhs]; const Vertex& rv = unindexed_vertices[rhs]; return fabsf(lv.tx - rv.tx) < 1e-3f && fabsf(lv.ty - rv.ty) < 1e-3f; }); ### 顶点缓存优化 当 GPU 渲染网格时,它会为每个顶点运行顶点着色器。历史上,GPU 使用固定大小的小型变换后缓存(16-32 个顶点)和不同的替换策略来存储着色器输出并避免冗余的着色器调用。现代 GPU 仍然进行顶点重用,但机制大不相同:顶点调用根据输入索引批处理成线程组,实际重用取决于顶点着色器输出和光栅化器吞吐量等因素。 为了最大化重用顶点引用的局部性,您需要像这样重新排序三角形: c++ meshopt_optimizeVertexCache(indices, indices, index_count, vertex_count); 顶点重用的细节在不同 GPU 架构之间有所不同,因此顶点缓存优化使用一种自适应算法,该算法生成具有良好局部性的三角形序列,适用于不同 GPU。 或者,您可以使用专门针对固定大小 FIFO 缓存进行优化的算法:meshopt_optimizeVertexCacheFifo(建议缓存大小为 16)。虽然它在大多数 GPU 上通常产生较低性能的结果,但运行速度快约 2 倍,这可能有利于快速内容迭代。 ### 过度绘制优化 变换顶点后,GPU 将三角形发送到光栅化,这通常会产生像素,这些像素通常首先通过深度测试,通过的像素则执行像素着色器以生成最终颜色。随着像素着色器变得越来越昂贵,减少过度绘制变得越来越重要。 虽然通常改善过度绘制需要与视图相关的操作,但该库提供了一种算法来重新排序三角形,以最小化来自所有方向的过度绘制,您可以在顶点缓存优化之后像这样运行它: c++ meshopt_optimizeOverdraw(indices, indices, index_count, &vertices[0].x, vertex_count, sizeof(Vertex), 1.05f); 过度绘制优化器需要从顶点读取 float3 顶点位置;上面的代码片段假设顶点将位置存储为 float x, y, z。在执行过度绘制优化时,您必须指定一个浮点数阈值参数。该算法试图在顶点缓存效率和过度绘制之间保持平衡;阈值决定了算法可以妥协顶点缓存命中率的程度,1.05 意味着结果比率应比优化前最多差 5%。 请注意,根据渲染器结构和目标硬件,优化可能有益或没有益处;例如,具有分块延迟渲染(PowerVR、Apple)的移动 GPU 不会从此优化中受益。对于顶点密集的场景,建议测量性能影响,以确保减少的过度绘制超过了降低的顶点缓存效率。 ### 顶点提取优化 在最终三角形顺序确定后,我们仍然可以优化顶点缓冲区的内存效率。在运行顶点着色器之前,GPU 必须从顶点缓冲区提取顶点属性;提取通常由内存缓存支持,因此优化数据的内存访问局部性非常重要。 您可以通过运行以下代码来实现: c++ meshopt_optimizeVertexFetch(vertices, indices, index_count, vertices, vertex_count, sizeof(Vertex)); 这将重新排序顶点缓冲区中的顶点,以改善引用的局部性,并原地重写索引以匹配;如果顶点数据使用多个流存储,则应改用 meshopt_optimizeVertexFetchRemap。 此优化必须在最终的索引缓冲区上执行,因为最佳顶点顺序取决于三角形顺序。请注意,该算法不会精确模拟缓存替换,而是按使用顺序对顶点进行排序,这通常产生接近最优的结果。 ### 顶点量化 为了进一步优化提取顶点数据时的内存带宽,并减少存储网格所需的内存,通常有益于将顶点属性量化为更小的类型。虽然此优化在技术上的管线任何部分都可以运行(有时将量化作为第一步可以通过合并几乎相同的顶点来改善索引化),但通常在所有其他优化之后运行更容易,因为其中一些优化需要访问 float3 位置。 量化通常是领域特定的;通常使用 3 个 8 位整数量化法线,但您可以使用更高精度的量化(例如在 10_10_10_2 格式中使用每分量 10 位),或不同的编码来仅使用 2 个分量。对于位置和纹理坐标数据,两种最常见的存储格式是半精度浮点数和 16 位归一化整数,它们将位置编码为相对于网格 AABB 或 UV 边界矩形的偏移。 这里的可能组合非常多,但此库确实提供了基本构建块,特别是将浮点值量化为归一化整数以及半精度浮点数的函数。例如,以下是如何使用 10-10-10 SNORM 编码量化法线: c++ unsigned int normal = ((meshopt_quantizeSnorm(v.nx, 10) & 1023) << 20) | ((meshopt_quantizeSnorm(v.ny, 10) & 1023) << 10) | (meshopt_quantizeSnorm(v.nz, 10) & 1023); 以及以下是如何使用半精度浮点数量化位置: c++ unsigned short px = meshopt_quantizeHalf(v.x); unsigned short py = meshopt_quantizeHalf(v.y); unsigned short pz = meshopt_quantizeHalf(v.z); 由于量化的顶点属性通常需要保持紧凑表示以便高效传输和存储,因此在顶点处理期间通常通过正确配置 GPU 顶点输入以期望归一化整数或半精度浮点数来解量化,这通常不需要或仅需最小修改着色器代码。当需要 CPU 解量化时,可以使用 meshopt_dequantizeHalf 将半精度值转换回单精度;对于归一化整数格式,解量化只需除以 2^N-1(unorm)和 2^(N-1)-1(snorm 变体)。例如,手动反转 meshopt_quantizeUnorm(v, 10) 可以通过除以 1023 完成。 ### 索引过滤 某些网格可能包含在渲染期间处理但不贡献于渲染结果的三角形。如果三角形的任意两个顶点在顶点着色器后导致相同位置,则该三角形是退化的,将被光栅化器跳过。某些三角形也可能是之前具有相同变换后位置和缠绕顺序的三角形的副本,此时只有一个三角形根据深度测试设置可见(假设禁用混合)。 无论哪种情况,这些三角形都需要额外处理,移除它们可能会提高光栅化或光线追踪性能;该库提供了一种从索引缓冲区中移除这些三角形的算法: c++ indices.resize(meshopt_filterIndexBuffer(&indices[0], &indices[0], indices.size(), &vertices[0].x, vertices.size(), sizeof(float) * 3, sizeof(Vertex))); 请注意,上面的示例假设只有位置与变换顶点相关,但对于可变形网格,可能需要将蒙皮数据添加到用作键的顶点部分;如果相关数据不连续,meshopt_filterIndexBufferMulti 对这些情况可能有用。 量化后进行过滤是方便的,因为如果三角形在量化前具有相似但不相同的位置,量化可能会增加冗余三角形的数量。然而,一旦索引缓冲区可用,可以在管线的任何点进行过滤;您也可以在过滤后运行顶点提取优化,因为它自然会过滤掉任何在冗余三角形被消除后可能变得未使用的顶点,从而可能节省额外内存。 ### 阴影索引 许多渲染管线要求除了颜色/G-buffer 目标外,还要将网格渲染到深度仅目标,例如阴影贴图或深度预通道。虽然在这两种情况下使用相同的几何数据是可能的,但减少深度仅渲染的唯一顶点数量可能是有益的,尤其是当源几何由于分面着色或光照贴图纹理接缝而具有许多属性接缝时。 为了实现这一点,该库提供了 meshopt_generateShadowIndexBuffer 算法,该算法生成一个可以与原始顶点数据一起使用的第二个(阴影)索引缓冲区: c++ std::vector shadow_indices(index_count); // 注意:这假设 Vertex 以 float3 位置开头,对于量化位置应相应调整 meshopt_generateShadowIndexBuffer(&shadow_indices[0], indices, index_count, &vertices[0].x, vertex_count, sizeof(float) * 3, sizeof(Vertex)); 由于顶点数据是共享的,阴影索引应在顶点/索引数据的其他优化之后进行。但是,可以(并且建议)优化生成的阴影索引缓冲区的顶点缓存: c++ meshopt_optimizeVertexCache(&shadow_indices[0], &shadow_indices[0], index_count, vertex_count); 在某些情况下,将顶点位置拆分到一个单独的缓冲区可能是有益的。

相似文章

数十亿三角形再现

Hacker News Top

zeux 详细介绍了 meshoptimizer 的分层级联 LOD 与 glTF 压缩扩展(EXT/KHR_meshopt_compression)的改进,这些改进是在 NVIDIA 更新了 vk_lod_clusters 示例及其包含 16 亿三角形的 Zorah 场景之后展开的,重点展示了场景数据体积的大幅缩减以及路径追踪性能的提升。

MeshFlow: 基于等变流匹配的网格生成

Hugging Face Daily Papers

MeshFlow 引入了一种等变最优传输流匹配模型,用于直接生成三角形网格,在达到最先进质量的同时,相比自回归方法提供了约18倍的推理加速。

MESH:用于混合专家训练的内存高效Sinkhorn优化

arXiv cs.LG

本文介绍了MESH,一种用于混合专家(MoE)训练的内存高效Sinkhorn优化器,它在不存储完整优化器状态的情况下恢复时间动量,相比AdamW将内存减少62.5%,同时保持有竞争力的评估损失。

huxingyi/autoremesher

GitHub Trending (daily)

AutoRemesher 是一款跨平台的自动四边形重新网格化工具,可将高多边形网格转换为干净的四边形拓扑。它是开源的,并基于多个库构建。