工具大全
开发者工具作者:Coocon2026年8月24日3 次阅读约 16 分钟阅读

36GB 的模型,192GB 内存都扛不住:meshoptimizer 把十几亿三角形的处理时间压进了几分钟

36GB 的模型,192GB 内存都扛不住:meshoptimizer 把十几亿三角形的处理时间压进了几分钟

先感受一下这个场景有多大:一个 glTF 文件,36GB,纯几何数据,16.4 亿个三角形(带实例化后是 189 亿)。

把它丢进 Blender,导入要 10 分钟,然后内存爆掉。丢给 Unreal Engine,更惨,不到 5 分钟就崩溃。NVIDIA 自己的处理代码用 16 线程跑,直接 OOM;降到 8 线程,吃掉 180+GB 内存,吭哧吭哧跑 30 分钟才完事。192GB 内存的机器,在这文件面前也就勉强喘口气。

这是 NVIDIA Zorah demo 的场景——用来展示 RTX Mega Geometry 集群光追的那套资产。渲染层面它很惊艳,但它的几何处理管线是个灾难。

meshoptimizer 的作者 zeux 看不下去了。他用这个文件当试金石,把库的 clustered LOD 支持从「能跑」优化到「几分钟跑完 16 亿三角形」。这篇博客把整个优化过程摊开来讲。做游戏引擎、3D 工具链、大规模网格处理的人,值得逐段拆着看。

这套技术到底在干什么:把网格切成乐高块

先说背景。这个场景用的是一套类似 Nanite 的层级 LOD 方案,meshoptimizer 从 2024 年开始支持。

思路是这样的:把一个大网格切成一堆小 cluster(每个最多 128 个三角形),相邻 cluster 合并成组,每组独立简化。简化完再切、再合并、再简化,直到没有可再降的级别。最后形成一个 cluster 的 DAG(有向无环图),运行时根据视角流式加载合适的细节级别——只有视觉误差小于 1 像素时,才允许换用更粗糙的 cluster。

你可以把它想成乐高:同一个建筑,按距离从「每块砖都可见」到「一整面墙一块砖」,逐级简化。渲染时只拼当前需要的部分。

整个管线分三段:生成层级结构、压缩结果方便流式传输、实时渲染。zeux 这篇文章只讲第一段——生成。而生成恰恰是最容易被人忽略、又最吃计算量的部分。

基线:不是慢,是根本跑不动

meshoptimizer 提供生成所需的三件套:clusterization(切块)、partitioning(分组)、simplification(简化)。但最初的示例代码是为研究算法写的,不是给真实场景用的。

zeux 做了几件基础工作:把代码重构成可复用的接口、用 memory mapping 加载 36GB 文件(还给 cgltf 提了个 PR)、给网格重新索引。最后这点很关键——源文件里有个网格,9000 万顶点对应 3000 万三角形,顶点数是三角形数的三倍,索引效率烂到离谱。

折腾完这些,16 线程跑一遍:raster 优化版 9 分 20 秒,54.6GB 内存;新的 raytracing 优化版 7 分 10 秒,57.6GB 内存

比 30 分钟强多了,但 zeux 的结论是「还不够快,一杯咖啡都等不完」。接下来才是正文。

三个优化,从 9 分钟到 3 分半

第一个坑:为 3000 万个顶点做无用功

上 profiler(Superluminal),最扎眼的热点居然是 memset

clusterizer 内部用一个按顶点索引的数组,记录顶点是否已被分配给当前 meshlet。每次切一个子网格都要把它初始化一遍:

memset(used, -1, vertex_count * sizeof(short));

小网格上这行代码无所谓。但这里是要反复切 3000 万三角形的子网格,vertex_count 是百万级,每次都全量清零,纯属浪费。simplifier 里也有类似的坑:一个按顶点位数组的初始化,memset(filter, 0, (vertex_count + 7) / 8)

修法很朴素:检测到稀疏访问(index_count < vertex_count)时,只初始化索引缓冲区实际引用到的那些条目,而不是全表清零。

结果:raster 版从 9 分 20 秒降到 3 分 31 秒,raytrace 版从 7 分 10 秒降到 3 分 57 秒。一个 memset 的改动,性能翻了两倍多。

第二个坑:16 线程,实际只用了 12 核

/usr/bin/time -v 看 CPU 占用率:1240–1260%。听起来很满?16 线程的理论上限是 1600%。换句话说,有四个线程的算力在空转。

问题出在负载均衡。整个场景由几百个大小不一的网格组成,任务粒度不均匀,线程跑完自己的活就干等。这不是靠调参能解决的,得改任务调度逻辑。zeux 在博客里详细拆了这块,核心结论是:profile 报告的总耗时分布会骗人,墙钟时间取决于最慢的那条线程

第三个优化:raster 和 raytracing 的 clusterizer 是两套

meshoptimizer 现在有两套 clusterization 算法:一套为 rasterization/mesh shader 优化(八年迭代的产物),一套专为集群光追开发(NVIDIA 发布 RTX Mega Geometry 之后写的)。

为什么需要两套?因为光追对 cluster 边界的位置极其敏感——边界切得好,每个 cluster 能独立建 micro-BVH,最后统一建一棵大 BVH 遍历,光追效率天差地别。做光追,cluster 切在哪,直接决定运行时的命中率。

别急着欢呼:这篇博客的测量有保留

说句公道话,这个「几分钟」有前提,不能直接当产品数据用。

测试代码不把结果写盘,只测内存里的处理——真实管线要序列化输出,时间和内存都得另算。36GB 也只是几何文件,完整 Zorah 场景还有 62GB 的渲染缓存没算进去。还有一点:meshoptimizer 是 C++ 库,接入现有管线得动手改造,不是下载即用。

但方向是明确的:单 CPU 能处理的网格规模,上了一个数量级。几年前这种场景属于「别想了,用离线农场吧」,现在一台开发机就能跑。

你可以从这三件事开始

  1. 别信「先优化再说」,先 profile。 这次最大的加速来自一个 memset——没有 Superluminal,谁也想不到热点在这。先测,再改,别猜。
  2. 检查你自己的稀疏场景。 如果你在循环里对超大数据集做全量初始化,试试只碰索引实际用到的部分。这个模式在索引、贴图、缓存清理代码里到处都是。
  3. 跑一下最新版 meshoptimizer 的 clustered LOD 示例。 如果你的构建管线因为「太慢」跳过了批量 LOD 生成,现在是重新评估成本的时候——成本已经变了。

至于 zorah 这种 16 亿三角形的资产,以前是「老板,这活得上渲染农场」。现在你可以说:「等一下,让我先冲杯咖啡。」


本文基于 zeux 的博客《Billions of triangles in minutes》和当日码农早餐头条整理,数据均引自原文。

✨ 本文由 DeepSeek 生成初稿,Claude 审核润色。