篇目 35 · 讲座精读

NanoMesh:把 8000 万三角形搬上手机,用离线 cluster 层级与一条 LOD 距离曲线替掉手工 LOD

NanoMesh · 一条在手机上跑到 8000 万三角形的无缝集群渲染管线:目标是不再让开发者按平台手工裁剪 mesh 资源,而移动端缺 mesh shader 与 bindless、带宽与 IO 受限、还自带 tile-based 架构。讲者的答案是「离线写层级、运行时按距离选」:把 mesh 切成 cluster 后连续重组成更粗层级,加一个 merge coefficient 与手工 LOD 对齐,每个 cluster 上限 128 个三角形;运行时先做 instance 级 HZB 快剔,再叠加视锥、遮挡与法锥剔除来选 cluster,LOD 走一条专门设计的距离曲线;中间结果只存 32 位 visbuffer,材质因为缺 bindless 而按屏幕 tile 分开着色;光照索性放弃 lightmap 改做全动态 GI,用层次体素与两级 atlas 把覆盖 512 米场景的内存从 160 MB 压到 30 MB。讲者把未竟项也写在明面上:目前不支持光追,1.5 毫秒仍略重

讲者 Shun Cao(Tencent Games R&D)· 20 张原样引用(源 slide 2–32) 出处 SIGGRAPH 2024 Advances in Real-Time Rendering in Games(Part I)· 2024-07-30 许可:非 CC(全文无版权声明与 CC 声明,页脚无版权行)· 逐件登记、逐图署名 · 未修改 · 存在许可残留风险 精读执笔 · 浅墨
13 章一级章节(按拆解点切)
0 式讲义未给公式,本稿不补推导
20 张配图(原文 20 / 自绘 0)
8 条边界与反例(独立成节)

图-段配对表 · 本稿 20 张

图角色档归属支撑的论断层次状态
图 1-1E§1演示场景:手机上跑的 8000 万三角形,按投影面积与相机距离控制 cluster 粒度①②④讲义 slide 2 · 原样引用
图 1-2E§1移动端三个短板:缺 mesh shader 与 bindless、带宽与 IO、tile-based 架构①②③讲义 slide 3 · 原样引用
图 2-1E§2背景之一 cluster 渲染:切小 cluster 剔除、索引降到 8 位、按包围盒按需加载①②③讲义 slide 5 · 原样引用
图 2-2E§2背景之二 32 位 visbuffer:细三角形下四边形利用率更高、开销仅 32 位①②③讲义 slide 6 · 原样引用
图 3-1E§3管线两段:离线生成 cluster 数据,运行时流送、剔除、光栅化、着色①②③讲义 slide 8 · 原样引用
图 4-1E§4离线结构:cluster 连续重组为更粗层级,加 merge coefficient;每 cluster ≤128 三角形①②③讲义 slide 9 · 原样引用
图 5-1E§5流送:从每 instance 最粗 cluster 起步,按剔除输出按需加载①②③讲义 slide 10 · 原样引用
图 5-2E§5剔除:三个缓冲 + instance 级 HZB + 视锥/遮挡/法锥剔除,LodFactor 走距离曲线①②③讲义 slide 11 · 原样引用
图 6-1E§6光栅化:32 位 visbuffer,对象按蒙皮与遮罩分三类①②③讲义 slide 12 · 原样引用
图 7-1E§7材质着色:无 bindless ⇒ 屏幕切 tile、materialID 比对决定是否着色①②③讲义 slide 13 · 原样引用
图 8-1E§8可形变网格的问题:动画中 cluster 包围盒持续变化、预计算包围盒失准①②③讲义 slide 14 · 原样引用
图 8-2E§8解法:主骨骼、法锥与主骨骼空间保守包围盒,运行时变换回 mesh 空间①②③讲义 slide 15 · 原样引用
图 9-1E§9全动态 GI:分块体素化,BrickTexture → BrickMappingAtlas → VisFaceAtlas①②③讲义 slide 17 · 原样引用
图 9-2E§9取光照伪代码:一次 uint64 读 brickgroupbitmask 判有效 brick①②③讲义 slide 19 · 原样引用
图 10-1E§10与 lightmap 对比:lightmap 至少 160 MB、brickgi 只要 30 MB①②④讲义 slide 21 · 原样引用
图 10-2E§1030 MB 明细:覆盖 512 米范围的内存分配①②③讲义 slide 22 · 原样引用
图 10-3E§10直接光的让步:200+ draw call 对约 100 draw call,观感差异很小①②④讲义 slide 24 · 原样引用
图 11-1E§11画质:同时渲染 8000 万三角形,帧率稳定、功耗可接受、省手工 LOD①②④讲义 slide 27 · 原样引用
图 11-2E§11高端机逐阶段耗时:0.11 / 0.07 / 0.2 / 0.72 / 0.92 / 0.87 毫秒①②④讲义 slide 30 · 原样引用
图 11-3E§11功耗:10 分钟测量,无缝渲染明显低于传统 mesh 渲染①②④讲义 slide 32 · 原样引用
角色档按 20 号票的裁决:E 类(承载可核对数值或作为唯一证据)禁止自绘,故本篇 20 件证据全部引用讲义原页(原生图流:幻灯整幅位图 1199×674,未重采样);本篇 S 类图为 0,S 类配额为 0,自绘 0 张。
许可:全文检索(讲者备注与页脚)无 © / Copyright / Creative Commons / CC BY / MIT / License 任何声明(0 命中);幻灯区无文本层、页脚若为图像亦不可选,亦未给代码仓库或开源链接 → 按分区非 CC 口径处理:逐件登记、逐图署名(图号 + slide 号 + 出处 + 「未修改引用」),并标注残留风险(讲义未授权转载,条款以官方发布为准),不得写成 CC 或可自由转载。
开篇要接住的那个问题 讲者开篇(英文原样引用 · 讲义 slide 0)

"Hello everyone, I'm Shun from Tencent Games. My topic is seamless rendering on mobile."

这一篇要讲清的八件事
  • 三件事与三个短板:目标是简化开发者针对不同平台(尤其移动端)定制与裁剪 mesh 资源的工作、减少手工操作带来的错误,同时保证画质与交互体验 —— 具体是在吸引玩家注意力的区域精细渲染、且不出现动态资源加载导致的跳变与卡顿(讲义 slide 1);而移动端不支持 mesh shader 与 bindless、带宽与 IO 受限、还自带 tile-based GPU 架构(讲义 slide 3),所以整条管线都在想办法把依赖硬件与带宽的部分简化掉(讲义 slide 4)。
  • 离线改的是存储结构本身:先把 mesh 切成很多 cluster,再把它们连续重组成更粗一级的 cluster 并如此迭代(讲义 slide 9);这种做法在结构上接近 UE5 的 Nanite,但多了一个 merge coefficient 函数,用来保证资产可控、并与手工制作的 LOD 对齐;每个 cluster 上限 128 个三角形,因此索引只要 8 位。
  • 流送从最粗开始,剔除让 GPU 自己选:流送按剔除 pass 的输出按需加载;剔除先做 instance 级 HZB 快剔,再叠加视锥、遮挡与基于法锥的背面剔除;cluster 的 LOD 用类似 Nanite 的误差量,但专门设计了一条用于 LodFactor 选择的距离曲线,以模仿手工 mesh LOD 的控制(讲义 slide 10、11)。
  • 中间结果只存 32 位:为降低功耗与 GPU 占用选 32 位 visbuffer,理想情况一次 draw call 画完;对象按是否蒙皮、是否要遮罩纹理分三类处理;刻意避开软光栅(要多一遍场景深度、缺 atomic64、64 位带宽更高);按 cluster 渲染使三角形索引 7 位就够(讲义 slide 12)。
  • 缺 bindless 就用屏幕分块来补:移动端不支持 bindless,材质着色本要每材质一次 draw call;于是把屏幕切成许多小 tile、每个材质持有自己的 tile 列表,逐 tile 用 materialID 比对决定是否着色(讲义 slide 13)。
  • 蒙皮网格靠主骨骼空间兜住包围盒:动画让 cluster 的包围盒持续变化、预计算的包围盒失准,而实时按顶点算太贵;解法是离线记下每 cluster 的主骨骼、法锥与主骨骼空间里的保守包围盒(覆盖全部动画范围),运行时把保守盒变换回 mesh 空间再走常规剔除(讲义 slide 14、15)。
  • 光照改成全动态,用层次体素换内存:lightmap 的精度能否覆盖像素级三角形、内存是否划算都成问题,于是做全动态 GI;场景分块体素化,用 BrickTexture → BrickMappingAtlas → VisFaceAtlas 两级偏移定位,查询时先用一次 uint64 采样读 brickgroupbitmask 判有效 brick(讲义 slide 16–20)。同一场景下 lightmap 至少 160 MB、brickgi 只要 30 MB,而后者的 GPU 时间略高、3 毫秒对多数手游仍可接受(讲义 slide 21)。
  • 读数与让步都写在明面上:手机上同时渲染 8000 万三角形、帧率维持约 60 FPS、功耗明显低于传统 mesh 渲染、且不需要手工 LOD(讲义 slide 27、28、32);高端设备约 3 毫秒、5 年前低端设备约 20 毫秒(讲义 slide 29);逐阶段耗时里剔除占比很小、光栅化与材质着色更耗时(讲义 slide 30)。讲者同时把未竟项写明:目前不支持光追、1.5 毫秒仍略重、mesh shader 与 bindless 材质着色要等硬件(讲义 slide 34)。

1三件想同时做到的事,和移动端的三个短板

 三件想同时做到的事,和移动端的三个短板

讲者开场先自报家门,他来自腾讯游戏,题目是移动端上的 seamless rendering(无缝渲染)(讲义 slide 0)。一句自我介绍交代完身份,他随即把这条管线要同时满足的目标摆了出来(讲义 slide 1)。

目标分成三件。第一件是简化开发者针对不同平台定制与裁剪 mesh 资源的工作,尤其是移动端这一档。第二件是减少人工操作带来的错误。第三件是保证游戏画质与交互体验,具体落在两处:在吸引玩家注意力的区域做精细渲染,同时在游戏过程中不出现因动态资源加载造成的视觉跳变与卡顿。三件要同时成立,少一件这套管线就立不住。

空口讲这些容易抽象,讲者接着用一个测试场景来演示(讲义 slide 2)。那是一个能在手机上跑起来的渲染场景,三角形数量达到 8000 万。制作期他们只用高精度资产,不做预先裁剪;到渲染时,粒度控制发生在 cluster 一级,依据是投影面积与相机到目标的距离,据此把 GPU 资源投到真正需要效果的地方。这里的「cluster 一级」在本篇第一次出场,此后离线的分层、运行时的流送与剔除,都以 cluster 作为计算单位。

演示场景页
图 1-1 E 类·证据 · 讲义 slide 2 · 原样引用 怎么读 ①指认:这页配合讲者备注给出开题的演示场景,一个能在手机上跑到 8000 万三角形的渲染场景,制作期只用高精度资产,渲染时在 cluster 一级按投影面积与相机距离控制粒度。②读法(怎么读):把「8000 万」读成整篇的体量尺,第 11 章还会用它来对读画质与帧率;「按投影面积与距离控制粒度」是自适应 LOD 的入口,第 4 章的离线分层与第 5 章的流送都在为它服务。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

不过与桌面端和主机端相比,移动端的主流设备缺了不少无缝渲染必需的软硬件特性(讲义 slide 3)。这是第一个短板:渲染管线本身有限制,设备不支持 mesh shader(网格着色器)与 bindless(无绑定资源访问)。第二个短板是带宽与 IO 对性能的冲击。第三个短板来自平台自身,移动平台有一套自带的 tile-based GPU(分块式 GPU)架构,写法要顺着它的性子来。他特意把移动端与桌面端、主机端放在一起对照,说明这道落差是移动端主流设备的普遍现状。

移动端三短板页
图 1-2 E 类·证据 · 讲义 slide 3 · 原样引用 怎么读 ①指认:页面按讲者备注列出移动端的三个短板,缺 mesh shader 与 bindless 这类无缝渲染必需的软硬件特性、带宽与 IO 对性能的冲击、以及平台自带的 tile-based GPU 架构。②读法(怎么读):把这三条当成后面全部让步的出处,第 6 章的 32 位 visbuffer、第 7 章的 tile 化材质、第 12 章的未竟项都能回到这一页。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

三处短板摆在面前,应对思路也随之定下(讲义 slide 4)。他把要达成的状态叫作 seamless cluster rendering(无缝的 cluster 渲染),在此前提下的做法是求简单与高效,尽可能把那些重度依赖硬件与带宽的部分简化掉。这一句是全篇的方法论,后面每一个设计选择,都能看到它在削掉哪一块依赖。第 3 章会把这条管线切成几段,先看骨架。

本章可核对的结论有三条。讲者要同时做到的三件事是简化跨平台 mesh 资源的定制与裁剪、减少手工操作带来的错误、保证画质与交互体验,第三件落在「注意力所及的区域精细渲染、且不出现加载造成的跳变与卡顿」(讲义 slide 1)。开题的演示场景是手机上跑到 8000 万三角形,制作期只用高精度资产,渲染时在 cluster 一级按投影面积与相机距离控制粒度(讲义 slide 2)。移动端的三个短板是缺 mesh shader 与 bindless、带宽与 IO 受限、自带 tile-based GPU 架构,而整条管线的方向是尽量简化依赖硬件与带宽的部分(讲义 slide 3、4)。下一章先补两块背景,把 cluster 渲染与 32 位 visbuffer 各自解决了什么讲清。

2两块背景砖:cluster 渲染与 32 位 visbuffer

上一章把目标与限制摆完,这一章在进入技术细节之前先补两块背景(讲义 slide 5)。讲者说这两项都是已有技术,后面所有做法都建在它们之上。

第一块是 cluster-based rendering(基于 cluster 的渲染)。游戏中遮挡剔除很常见,它解决的是直接遮挡造成的过度绘制(讲义 slide 5)。可它有一处没照顾干净:大物体常常只有一部分可见,却被整块渲染。把原始 mesh 切成许多小 cluster、把剔除放到 cluster 一级之后,GPU 就能跳掉大量无效三角形。cluster 变小还带来两处附带好处。每个 cluster 的三角形少了,顶点索引的精度可以降下来,比如用 8 位;此外还能按包围盒按需加载 cluster,提高显存利用率。这里的 8 位索引与第 4 章「每个 cluster 最多 128 个三角形」是同一件事的两端,后者给出上限,前者给出结果的位宽。

cluster 渲染背景页
图 2-1 E 类·证据 · 讲义 slide 5 · 原样引用 怎么读 ①指认:页面在讲 cluster 渲染这项背景,遮挡剔除解决直接遮挡造成的过度绘制,可大物体仍可能只部分可见却整块渲染,解法是切小 cluster 并在 cluster 一级剔除,附带把索引精度降到 8 位、按包围盒按需加载。②读法(怎么读):先看它替掉的是哪一类无效三角形,第 4 章的离线重组与第 5 章的剔除都从这条思路长出来。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

第二块是 visbuffer(可见性缓冲)(讲义 slide 6)。渲染细三角形时,visbuffer 的四边形(quad)利用率高于前向管线与延迟管线,额外的开销只有 32 位。把这一项放回移动端的处境里读会更清楚:它多出来的开销只有 32 位,而这一档设备最吃紧的恰好是带宽与 IO(讲义 slide 3、4)。本篇出现的 visbuffer 一律指这个 32 位可见性缓冲。讲者在这里提了一句预告,John Hable 在同届 2024 Advances 里会进一步讲 visbuffer,本篇只把它当作已知背景使用。

visbuffer 背景页
图 2-2 E 类·证据 · 讲义 slide 6 · 原样引用 怎么读 ①指认:页面交代第二项背景 visbuffer,讲者备注说渲染细三角形时它的四边形利用率高于前向与延迟管线,额外开销只有 32 位,并预告 John Hable 的同届演讲会进一步展开。②读法(怎么读):把「32 位」当成第 6 章的引子,那里把中间结果只存进 32 位 visbuffer,是同一个取舍的落地。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

背景讲完,他停在一页致谢与出处上(讲义 slide 7)。他感谢 Drobot 在 Advances 里的演讲、Brian Karis 的分享、Wolfgang Engel 那份 visbuffer 的原始讲义,以及 Sebastian 在 SIGGRAPH 2015 的演讲,说从这些材料里能拿到关于这两项技术的更多细节。John Hable 的同届演讲在这一页被再次提到,可见这条技术线在该届 Advances 里另有更完整的交代。这一页列出的他人演讲不在本篇的讲述范围,本篇只记出处与人名,不展开他们各自讲了什么。

本章可核对的结论有三条。cluster 渲染的动机是遮挡剔除没能照顾到「大物体只部分可见却整块渲染」这件事,做法是切小 cluster 并在 cluster 一级剔除,附带把顶点索引精度降到 8 位、支持按包围盒按需加载 cluster(讲义 slide 5)。32 位 visbuffer 在渲染细三角形时给出高于前向与延迟管线的四边形利用率,额外开销只有 32 位(讲义 slide 6)。这两项都属于讲者复述的已有技术,出处集中在讲义 slide 7 那一页。下一章给出整条管线的骨架,看它分成几段、每段管什么。

3管线骨架:离线一段、运行时分四步

背景交代完,整条管线的架构随之登场(讲义 slide 8)。上一章那两项技术各自解决了剔除与可见性表达的一部分问题,这一章把它们安放进一条完整流水线。

讲者把管线切成两部分:离线与运行时。离线这一段负责导入并生成自定义的 cluster 数据;运行时这一段包含四步,依次是流送、剔除、光栅化、着色,对应的英文分别是 streaming、culling、rasterization 与 shading。他特地补了一句,分批列出的顺序就是执行顺序。这一页备注只交代了分段与四步的名目,四步之间更细的衔接与各步产物,讲义未给。他在这里只用一页把骨架立起来,细节留到后面各段去铺,因此这一页在本篇里更像一份目录。离线只有一段,运行时占了四步,本篇接下来的几章也照这四步的次序安排。

管线骨架页
图 3-1 E 类·证据 · 讲义 slide 8 · 原样引用 怎么读 ①指认:页面给出管线架构,讲者备注把它分成离线与运行时两部分,离线导入并生成自定义的 cluster 数据,运行时含流送、剔除、光栅化、着色四步,并说明分批顺序即执行顺序。②读法(怎么读):把这一页当成本篇的地图,第 4 章走离线段,第 5 章到第 8 章走几何四步,第 9 章到第 11 章走光照与读数。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

本章可核对的结论有两条。整条管线分成离线与运行时两部分,离线导入并生成自定义的 cluster 数据,运行时含流送、剔除、光栅化、着色四步(讲义 slide 8)。备注明确分批顺序即执行顺序。下一章从离线段开始,看那份自定义 cluster 数据的存储结构被改成了什么样。

4离线:把 cluster 连续重组成更粗的层级

离线要做的事在上一章只有一句「导入并生成自定义的 cluster 数据」。这一章把那句展开(讲义 slide 9)。按讲者的措辞,离线部分动手改的第一件事就是 mesh 的存储结构自身,先于其他任何处理。

改法有六件事要一起看,它们决定的是数据结构长什么样,以及它能否撑住后面的剔除与流送。

头一件是重组方式。他没有沿用传统的多级 LOD,也没有停在把 mesh 切成一堆 cluster 这一步,做法是先把 mesh 切成很多 cluster,再连续地把这些 cluster 重组成新的、更粗一级的 cluster,如此迭代下去,得到一层层更粗的 cluster。

第二件是可控性。这种连续重组在做法上类似 UE5 的 Nanite,但他在此之上加了一个 merge coefficient 函数,用途有两个:保证资产的可控性,以及让结果与手工制作的 mesh LOD 对齐。讲者自己用的是「类似」这个说法,本篇按原口径写,不等于与 Nanite 等价。

第三件是这样做的收益。多级 cluster 数据因此更小,也支撑得起后续剔除与流送所需要的数据结构。把存储结构放在最前面,与这一条收益有关,数据结构不先定下来,后面的剔除与流送就没有可依附的底子。

第四件是每个 cluster 上挂的信息。他为每个 cluster 存下包围盒与法锥(normal cone),包围盒用于高效的遮挡剔除,法锥用于背面剔除。

第五件是索引精度。每个 cluster 最多 128 个三角形,在这个上限下索引用 8 位就能表示,从而省下空间。

第六件是非叶节点的记录。对非叶节点,他记下合并带来的误差,这个误差量与运行时 cluster 的层次选择相接,第 5 章会用到它(讲义 slide 9)。

离线重组页
图 4-1 E 类·证据 · 讲义 slide 9 · 原样引用 怎么读 ①指认:页面在讲离线如何改 mesh 的存储结构,讲者备注说先切成很多 cluster 再连续重组为更粗一级并迭代,做法类似 UE5 的 Nanite 但多了一个 merge coefficient 函数,每 cluster 存包围盒与法锥、上限 128 个三角形故索引 8 位、非叶节点记合并误差。②读法(怎么读):把「连续重组」与「merge coefficient」分两处读,前者决定数据结构,后者决定资产可控与和手工 LOD 对齐;包围盒与法锥会在第 5 章与第 8 章再次出场。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

本章可核对的结论围绕那份存储结构展开。离线动的是 mesh 的存储结构,先把 mesh 切成很多 cluster,再连续重组成更粗一级的 cluster 并迭代(讲义 slide 9)。这套做法讲者自陈类似 UE5 的 Nanite,差别在于多了一个 merge coefficient 函数,用来保证资产可控并与手工 mesh LOD 对齐。每个 cluster 存包围盒与法锥,上限 128 个三角形,因此索引只需 8 位,非叶节点则记录合并误差。就讲义 slide 9 这一页备注所能给出的范围,本章已按原值覆盖;存储布局的细节与误差函数的具体形式,讲义未给。下一章接上运行时的第一步流送,看 cluster buffer 与三个缓冲如何配合。

5流送与剔除:让 GPU 决定要哪一级

上一章停在离线那段最要紧的一句话上:mesh 被切成很多 cluster 之后还会被连续重组成更粗的层级,每个 cluster 最多 128 个三角形,非叶节点则记下合并带来的误差(讲义 slide 9)。这套存储结构要被用起来,得看运行时那四步,也就是流送、剔除、光栅化、着色(讲义 slide 8)。本章看前两步,它们是整条管线里最先对数据量动手的地方。

讲者对流送的交代很短(讲义 slide 10)。流送从每个 instance 最粗的那一级 cluster 开始,先把能撑住整个画面的最粗版本拉进来;再往后,加载什么由剔除 pass 的输出决定,也就是按需。这两句要连起来读。顺序是先给一个粗底,然后剔除每帧报出真正要用的那批 cluster,流送照着这份名单去补。屏幕上出现什么决定了要搬多少数据,最粗一级保底不空,细节按可见程度补上。这条按需的路子接着第 1 章讲的移动端短板:带宽与 IO 对性能的冲击摆在那里,一口气全量铺开在手机上不划算,加载量于是交给可见性来定(讲义 slide 3)。

流送页
图 5-1 E 类·证据 · 讲义 slide 10 · 原样引用 怎么读 ①指认:页面讲流送这一步的做法,起手取每个 instance 最粗的 cluster,之后按剔除 pass 的输出按需加载。②读法(怎么读):这一页要和第 11 页连着读,流送能按需,靠的是剔除先把「这一帧要哪些 cluster」报出来;单看这一页只看到加载的一半,闭环的另一半在下一张图上。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

剔除这一步是讲者反复强调的地方(讲义 slide 11)。他说这件事要让 GPU 来做 cluster 的控制与选择。落到数据结构上是三个缓冲的分工:对象放在 instance buffer,cluster 放在 cluster buffer,剔除成功的那些 ID 写进 visible cluster buffer。三者的身份并不一样,前两个是待筛的输入,第三个是筛完的名单。与它们配套的还有一句关键的话,cluster buffer 是按 GPU 的选择动态加载的。这句和上一页的流送接在一起,流送能按需,是因为选择权在 GPU 手上。

剔除本身按从粗到细排开(讲义 slide 11)。先是 instance 一级的快速剔除,用 HZB(层次 Z 缓冲)打底,先把整个对象判掉,代价低。滤下来的再叠三类更细的判定:视锥剔除、遮挡剔除,以及基于法锥的背面剔除。法锥(normal cone)在离线阶段就已经算好,它让背面判定在 cluster 一级也能做,不必等到三角形一级。

cluster 自己的 LOD 选择还有一层(讲义 slide 11)。讲者说这里用了类似 UE5 Nanite 的 LOD error(层次误差)来判断该取哪一级,同时他另外设计了一条用于 LodFactor 选择的距离曲线。这条曲线的目标很直接,近处要保证渲染精度足够,远处可以换成更粗的三角形。他给这条曲线的定位是,在模仿手工制作 mesh LOD 时那套 factor 的控制方式。这一处照他的原口径记:讲者说的是类似 Nanite,并在这之上加了属于这套管线的东西,不能读成与 Nanite 等价。

剔除页
图 5-2 E 类·证据 · 讲义 slide 11 · 原样引用 怎么读 ①指认:页面讲剔除这一步,对象、cluster 与存活 ID 分放三个缓冲,cluster buffer 按 GPU 的选择动态加载;判定先做 instance 级 HZB 快剔,再叠加视锥、遮挡与法锥背面三类剔除;cluster 的 LOD 走一条用于 LodFactor 的距离曲线。②读法(怎么读):把这页读成第 5 章的主页,流送与剔除的闭环在它上面合拢;末段那条距离曲线与第 9 页记下的合并误差前后照应。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

把这两页合起来,本章能核对的结论有三条。流送从每个 instance 最粗的 cluster 起步,之后按剔除 pass 的输出按需加载(讲义 slide 10)。剔除把对象、cluster 与存活 ID 分放在 instance buffer、cluster buffer 与 visible cluster buffer 里,其中 cluster buffer 由 GPU 的选择动态加载(讲义 slide 11)。判定顺序是 instance 级 HZB 快剔打底,再叠加视锥、遮挡与法锥背面三类剔除;cluster 的 LOD 用类似 Nanite 的 LOD error 来选,LodFactor 走一条讲者自己设计的距离曲线,用来模仿手工 mesh LOD 的 factor 控制(讲义 slide 11)。下一章看被选中的三角形怎么写进一份只有 32 位的中间结果。

6光栅化:32 位 visbuffer 与三类对象

上一章把可见 cluster 的名单交了出来,这一章是运行时的第三步,把这些三角形写进中间结果(讲义 slide 12)。

讲者先交代这么做的前提:为了降低功耗与 GPU 占用,中间渲染结果存进 32 位 visbuffer(可见性缓冲)。这一层只留可见性,不在此时算颜色。跟着是按他的场景给出的判断,因为所有三角形都在 cluster 里,理想情况一次 draw call(绘制调用)就能把它们全画到 visbuffer 里。「理想情况」这四个字要一起带走,它说的是这套设计想达到的形态,并未承诺每一帧都如此。这里的前提来自第 4 章的存储结构:三角形被组织进 cluster,绘制时才可能把数量极大的三角形压到很少几次调用里。

对象按并行需要分成三类(讲义 slide 12)。type 1 在顶点着色器里把 cluster 与三角形 ID 记进 visbuffer,跳过像素着色器。type 2 是蒙皮网格这类,在顶点着色器里用骨骼信息重算顶点位置,skinning(蒙皮)这一步就落在这里。type 3 是带遮罩纹理的植被这类,在像素着色器里按遮罩纹理过滤 visbuffer 的生成。三类的分法对着并行这个问题:有的对象只需要登记身份,有的要在顶点阶段改位置,有的必须到像素阶段才判得出该不该留下。type 1 省掉了整段像素着色;type 2 把重算位置放在顶点阶段,正好承接后面第 8 章要讲的蒙皮网格;type 3 的遮罩纹理要到像素这一级才有意义。这样一分,同一类的对象走同一条路径,GPU 在并行处理时不必为它们准备不同的分支。

光栅化页
图 6-1 E 类·证据 · 讲义 slide 12 · 原样引用 怎么读 ①指认:页面讲光栅化这一步,中间结果存进 32 位 visbuffer,理想情况一次 draw call 画完;对象分成三类,分别对应在顶点着色器里记 ID 并跳过像素着色器、用骨骼信息重算顶点位置、以及在像素着色器里按遮罩纹理过滤生成。②读法(怎么读):把三类对象读成分工表,三类各管一段,第 8 章会把其中的蒙皮网格单独展开;页末那段索引位宽与第 4 章的 128 个三角形一脉相承。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

软光栅是被刻意避开的一条路(讲义 slide 12)。讲者给了三条理由:要多一遍场景深度 pass,缺 atomic64 支持,64 位 visbuffer 的带宽更高。三条各有对应的条件:多一遍场景深度 pass 意味着额外的时间与带宽,atomic64 是移动端缺的一项能力,64 位 visbuffer 又比 32 位更吃带宽。所以这一步的选择是权衡的结果,讲者在这一页把它记成刻意的取舍。

把中间结果压到 32 位、把绘制尽量并成少数几次 draw call、把对象按各自最省的阶段处理,这三件事指向同一处:降功耗与省 GPU 占用(讲义 slide 12)。讲者在这一页的每一句都挂在这个目标上,也解释了他为什么愿意接受一份精度不高的中间结果。

索引位宽也在这里交代(讲义 slide 12)。他说因为渲染的单位是 cluster,三角形索引 7 位就够,余下的位留给 cluster。这和第 4 章那句连得上:每个 cluster 最多 128 个三角形,索引可以用很短的字长装下(讲义 slide 9)。一条 32 位的记录里因此同时塞得下身份与位置,中间结果的体积被压到很低。这份中间结果只记身份,颜色要留到下一章的着色阶段再算。

本章可核对的结论有三条。为降低功耗与 GPU 占用,中间结果存进 32 位 visbuffer,理想情况一次 draw call 把所有三角形画进去(讲义 slide 12)。对象分三类:type 1 在顶点着色器里记 cluster 与三角形 ID 并跳过像素着色器,type 2 如蒙皮网格在顶点着色器里用骨骼信息重算顶点位置,type 3 如带遮罩纹理的植被在像素着色器里按遮罩纹理过滤 visbuffer 的生成(讲义 slide 12)。软光栅被刻意避开,理由是多一遍场景深度 pass、缺 atomic64 支持、64 位 visbuffer 带宽更高;因为渲染按 cluster 进行,三角形索引 7 位就够(讲义 slide 12)。这里出现的三个位宽可以并排看一眼,它们量的是两样东西:背景与离线那两页的 8 位是顶点索引的位宽,边界同样来自「每个 cluster 不超过 128 个三角形」这条上限;本页的 7 位是三角形索引,受同一条上限约束 —— 128 个三角形恰好落在 7 位能表达的范围内,省下的位就留给 cluster 标识。两个数字各管一路,读的时候不要把它们的来路混在一起(讲义 slide 5、9、12)。下一章看 visbuffer 里存下的身份怎么换来真正的颜色。

7没有 bindless,就按 tile 分开着色

上一章结束时,visbuffer 里只有 cluster 与三角形的身份,颜色还没有出现。这一章是运行时的第四步,着色(讲义 slide 13)。

讲者先把限制摆明:移动设备上还不支持 bindless(无绑定资源访问)。缺了它,要做到材质着色,就得每个材质单独用一次 draw call(绘制调用)来渲染。draw call 是着色这一步的边界,一次调用带一套材质,材质之间的切换只能靠它来完成。这笔账听起来很重,场景里材质越多,光这一项就是成倍的开销。

他没有停在这笔账上,接着说了一个观察:单个材质铺满整屏的情况很少。做法于是在屏幕这一侧展开,把屏幕切成很多小 tile,每个材质持有自己的一份 tile 列表。渲染时不必让每个材质跑满整屏,每个材质只碰它列表里的那些 tile。屏幕分块把「每材质一次 draw call」这件事的覆盖面缩到了它真正出现的地方,每个材质的实际工作量从整屏降到它自己那些 tile 上。

材质着色页
图 7-1 E 类·证据 · 讲义 slide 13 · 原样引用 怎么读 ①指认:页面讲移动端缺 bindless 时的材质着色做法,屏幕被切成很多小 tile、每个材质持有自己的 tile 列表,tile 渲染时从 visbuffer 的 cluster 信息里取 materialID 与当前材质比对,相等才继续着色。②读法(怎么读):把这一页读成对缺 bindless 的补偿方案,它把逐材质的 draw call 代价限制在材质实际覆盖的屏幕上;materialID 的来源要回上一章的 32 位 visbuffer 去找。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

每个 tile 渲染时怎么决定要不要着色,这一步也讲清了(讲义 slide 13):从 visbuffer 的 cluster 信息里取出 materialID,与当前正在着色的那个材质的 materialID 比较,相等才继续着色。materialID 的比对就是这道门,不相等就跳过。这道门是逐 tile 过的,同一次材质着色里,只有取到相同 materialID 的 tile 会接着算下去,余下的都被省掉。

这段设计与前面几章是咬合的。visbuffer 里存下来的是 cluster 信息,materialID 要到着色阶段才被反查出来,这道比对是拿上一章那份中间结果回过头去查材质身份;tile 列表则把每个材质一次 draw call 的代价限制在它实际覆盖的那块屏幕上。缺 bindless 带来的额外 draw call 没有消失,只是被屏幕分块摊薄了,代价从整屏缩到了实际覆盖的那几块。这样安排之后,材质数量增加时多出来的是 draw call 的次数,而单次调用要碰的像素量仍被 tile 列表管住。

把两头接起来看,屏幕分块是对缺 bindless 的一处补位:材质着色仍按材质逐个推进,每次只覆盖它自己的那些 tile(讲义 slide 13)。屏幕切得越细,材质列表覆盖的范围与它实际出现的位置就越贴合。

本章可核对的结论有两条。移动端尚不支持 bindless,因此每个材质要靠单独一次 draw call 完成材质着色(讲义 slide 13)。由于单个材质铺满整屏的情况很少,做法是把屏幕切成很多小 tile,每个材质持有自己的一份 tile 列表;每个 tile 渲染时从 visbuffer 的 cluster 信息里取 materialID,与当前着色材质的 materialID 相等才继续着色(讲义 slide 13)。下一章回到几何那一段,处理一类离线盒子不管用的网格。

8蒙皮网格:在主骨骼空间里算保守包围盒

第 6 章把蒙皮网格列为光栅化里的第二类对象,只在顶点着色器里用骨骼信息重算位置(讲义 slide 12)。这一章回答更早的一问:这类网格在剔除阶段怎么才不出错(讲义 slide 14)。

讲者说游戏里可形变网格不可避免,他拿蒙皮网格(skinned mesh)当例子。动画过程中每个 cluster 的包围盒持续变化,于是离线按静止姿态算好的那两套盒子就不再准确,一套是 LOD 用的,一套是剔除用的。另一条路他也堵上了:实时按顶点去算包围盒太耗时。两侧都走不通,所以这类网格需要特殊处理,做法是在主骨骼空间里按 cluster 做动态包围盒计算与剔除。

可形变网格问题页
图 8-1 E 类·证据 · 讲义 slide 14 · 原样引用 怎么读 ①指认:页面讲可形变网格带来的麻烦,动画过程中每个 cluster 的包围盒持续变化,使预先算好的 LOD 与剔除包围盒不再准确,而实时按顶点算包围盒又太耗时。②读法(怎么读):把这页读成问题的提出,两侧都被堵住之后才轮到下一页那套主骨骼空间里的做法;它与第 5 章的剔除链是同一件事的两端。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

离线阶段要算的东西有三样(讲义 slide 15)。主骨骼(main bone)是 cluster 内顶点权重最高的那根骨骼。法锥(normal cone)是 cluster 内所有三角形法线的平均,锥角要能覆盖住全部三角形。第三样是主骨骼空间里的保守包围盒,它覆盖该 cluster 在所有动画中的最大范围,因为把全部动画都罩了进去,所以它是保守的。三样东西分工清楚:主骨骼决定这套盒子跟着谁动,法锥给出朝向上的界,保守盒给出空间上的界。三样都在离线期定下来,运行时要动的只是那一次空间变换。选主骨骼作参照,是因为它在这个 cluster 里权重最高,最能代表这批顶点随动画一起走的样子。

解法页
图 8-2 E 类·证据 · 讲义 slide 15 · 原样引用 怎么读 ①指认:页面讲解法,离线为每个 cluster 算出主骨骼、法锥与主骨骼空间里的保守包围盒,保守盒覆盖该 cluster 在所有动画中的最大范围;运行时 CPU 把骨骼变换送给 GPU,剔除时读这份变换把保守盒变换到 mesh 空间,再走常规的 cluster 剔除。②读法(怎么读):把这三样读成一组缺一不可的量,主骨骼提供跟随对象、法锥用于背面判定、保守盒覆盖动画全程;它接的是上一页那个问题的答案。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

运行时这一段也交代了(讲义 slide 15)。CPU 把骨骼变换数据送到 GPU;cluster 剔除时,GPU 读这份变换,把保守盒变换回 mesh 空间,然后照常规的 cluster 剔除往下走。盒子先随主骨骼动起来,再被搬回 mesh 空间,之后第 5 章那条剔除链就能原样用在它身上。这一步的变换是必须的,因为第 5 章的判定都是在 mesh 空间里做的。蒙皮那一步的光栅化则按常规骨骼动画的算法算,不需要在光栅化里另开一套;这套特殊处理动的是包围盒与剔除,蒙皮本身仍走原有算法。

这样一来,第 5 章的剔除链对静态网格与蒙皮网格就合到了一处:前者的盒子本来就在 mesh 空间里算好,后者的盒子靠主骨骼空间里的保守设计把这份可信度补回来。

本章可核对的结论有三条。蒙皮网格在动画中每个 cluster 的包围盒持续变化,预计算的 LOD 与剔除包围盒因此失准,而实时按顶点算又太耗时,所以要特殊处理(讲义 slide 14)。离线为每个 cluster 算出主骨骼、法锥与主骨骼空间里的保守包围盒,其中主骨骼是 cluster 内顶点权重最高的骨骼,法锥是 cluster 内所有三角形法线的平均且锥角覆盖全部,保守盒覆盖该 cluster 在所有动画中的最大范围(讲义 slide 15)。运行时由 CPU 把骨骼变换送到 GPU,剔除时读这份变换把保守盒变换回 mesh 空间,再走常规的 cluster 剔除;蒙皮光栅化照常规骨骼动画的算法算(讲义 slide 15)。

9全动态 GI:层次体素与两级 atlas

上一章把可形变网格的剔除交给主骨骼空间,几何那一段到此收束,讲者把话题转进光照(讲义 slide 16)。按他的交代,无缝渲染因为自身固有的性质,在光照计算上要用特殊的实现方式与优化手段。他举的例子是算全局光照时的两个前置问题,一个是能不能用 lightmap,另一个是 lightmap 的精度能不能覆盖到像素级的三角形。这两件事对内存与存储的影响都很大。按他的说法,这套渲染方式本身就让 lightmap 能不能用、精度够不够成了要先问过的问题。他给出的结论,是改用一套全动态 GI(fully dynamic GI)。

这套 GI 走层次体素(hierarchical voxel)这条路(讲义 slide 17)。流程他讲得不长:先把场景分块(block),对每一块做体素化(voxelize),再借这些块与细化之后的体素做快速的 raymarching(光线步进)。存储上分两级偏移,第一级是 BrickTexture 存 BrickData 在 BrickMappingAtlas 里的偏移,第二级是 BrickMappingAtlas 存体素在 VisFaceAtlas 里的偏移。换成 brick(块)与 voxel(体素)这两个词来看,问一份 BrickData 落在哪,先查 BrickTexture 拿到它在 BrickMappingAtlas 的位置;问一个体素落在哪,再到 BrickMappingAtlas 里查它在 VisFaceAtlas 的位置。两级都落在 atlas(图集)之内,靠偏移一层层串起来。

层次体素页
图 9-1 E 类·证据 · 讲义 slide 17 · 原样引用 怎么读 ①指认:页面画出层次体素的做法,先把场景分成块、逐块体素化,再用这些块与细化后的体素做快速 raymarching,并给出 BrickTexture 存 BrickData 在 BrickMappingAtlas 的偏移、BrickMappingAtlas 再存体素在 VisFaceAtlas 的偏移这两级关系。②读法(怎么读):把这一页读成光照段的地址图,先记住两级偏移的先后,再接到下一张伪代码页逐句对;下一章与 lightmap 的内存对照就落在这套结构上。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

这套结构里,group、brick 与 voxel 是三层由大到小的单位。一个 group 里装若干 brick,所以判断「这个 group 里有没有东西」要看 brickgroupbitmask 里被置上的位;一个 brick 里再装若干 voxel,定位到具体 voxel 要靠 brickbitmask 与采样位置一起算。两级的掩码都在查询一开始就先划出范围,后面的偏移换算只在这个范围里做,场景因此同时有了粗一层的块与细一层的体素两档粒度。

讲者在这里还留了一页配置(讲义 slide 18)。这一页的讲者备注只有一句,说的是有一份默认配置变量。页内的变量名与取值范围都在幻灯区的位图里,本篇不做读取,因此这一页只记「存在一份默认配置」这件事,不带任何具体名字与数值,讲义也没有给出这些变量的完整口径。

取光照数据的那段伪代码是本章最实的一段(讲义 slide 19)。它的第一步是一次 uint64 采样,用这一次采样去读 brickgroupbitmask,先判断一个 group 里哪些 brick 是有效的。掩码不为零,说明当前这个 brick group 里含有有效 brick;再看掩码里哪几位被置上,就知道具体是哪几个 brick 有效。定位 brickindex 用的是掩码与采样位置的组合;拿到这个索引之后把它换算成 bricktexcoord,就能到 bricktexture 里查这个 brick 实际存下的 bias。接着用 brickbitmask 配采样位置继续做快速 raymarching,把 brick 内有效的 voxelcoord 定出来。最后一步是到 brickmappingatlas 里查这个体素在 visfaceatlas 里的偏移,把对应的照明数据读出来。

这条次序为什么省,看它的第一跳就清楚:判断有效 brick 只花一次 uint64 采样,掩码非零才继续往里走,等于用一次很轻的读先把大范围里没有东西的部分切掉;后面的每一步都只是查偏移、换算坐标、再读数据。讲者把这段的入口也讲明了,它要取的是 brick 与 visfacelightingatlas 两处的照明数据。整段读下来,只有进门的第一次采样相对重,其余步骤都是索引与坐标的换算。这套结构省下来的内存,下一章会用一组对照给出。光照这一段在讲义里从 slide 16 铺到 slide 25,这一章先处理结构与查询,取舍留给第 10 章。

取光照伪代码页
图 9-2 E 类·证据 · 讲义 slide 19 · 原样引用 怎么读 ①指认:页面是取照明数据的伪代码,第一步用一次 uint64 采样读 brickgroupbitmask,判断一个 group 里哪些 brick 有效,掩码非零才继续,随后定位 brickindex、换算 bricktexcoord 取 bias、用 brickbitmask 与采样位置快速 raymarching 定位 voxelcoord,最后到 brickmappingatlas 查体素在 visfaceatlas 的偏移读出照明数据。②读法(怎么读):按页内次序读,把每一次读操作与上一页的两级偏移对上,就能看出这条查询为什么第一步只花一次很轻的采样。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

同样属于伪代码的还有一页(讲义 slide 20),收的是另一个方向的问题:怎么从 brickmask 里取出有效的 brick。备注给出的用途只有这一句,讲者没有在这页把逐步逻辑展开,本篇也不代他补写。它与上一页合起来,构成取光照这条路上的两个入口。

本章可核对的结论有三条。无缝渲染的光照要特殊处理,起因是要问 lightmap 能不能用、它的精度能不能覆盖像素级三角形,这两点对内存与存储影响很大,讲者据此做了全动态 GI(讲义 slide 16)。这套 GI 用层次体素:场景分块、逐块体素化、再用块与细化后的体素做快速 raymarching,存储走 BrickTexture 到 BrickMappingAtlas 再到 VisFaceAtlas 的两级偏移(讲义 slide 17)。取光照的伪代码第一步是一次 uint64 采样读 brickgroupbitmask,掩码非零再按采样位置定位 brickindex、换算 bricktexcoord 取 bias、用 brickbitmask 与采样位置快速 raymarching 定位 voxelcoord、最后到 brickmappingatlas 查体素在 visfaceatlas 的偏移读出照明数据(讲义 slide 19);另有一页伪代码只交代了从 brickmask 取有效 brick 这个用途(讲义 slide 20),默认配置那页只有一句备注(讲义 slide 18)。下一章看这套光照在内存与画质上的账。

10直接光的让步:粗一点就够

上一章把光照换成全动态 GI,用层次体素与两级 atlas 定位。这一章看它在内存上划不划算、在画质上要不要紧,答案由讲者一组自家的对照给出(讲义 slide 21)。测试条件是加了 100 多个物体、每个物体的 lightmap 分辨率 1024×1024、场景范围 512×512 米。在这个条件下,lightmap 至少要 160 MB 的内存开销,brickgi 只要 30 MB。GPU 时间上 brickgi 比 lightmap 略高,讲者给的说法是 3 毫秒对多数手游仍然是可以接受的延迟。这一句是他按自己的场景与目标下的判断,不能读成通用结论。两条账里,内存那一头是量级之差,GPU 时间那一头的差距小得多。

与 lightmap 对比页
图 10-1 E 类·证据 · 讲义 slide 21 · 原样引用 怎么读 ①指认:页面把 brickgi 与 lightmap 在移动端的对照排在一页里,一侧是内存开销、一侧是 GPU 时间。②读法(怎么读):这一页要与上一段的条件放在一起读,两个内存数离开那组设定就不再成立。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

这组对照出自讲者自家的内部测试,参与比较的只有 brickgi 与 lightmap 两项。本篇不把它与别的引擎或方案横向比较,也不把两个数换算成比例,读的时候把条件与结论成对记住即可。

那 30 MB 的构成单独占一页表(讲义 slide 22)。备注说这张表给的是覆盖 512 米范围内那 30 MB 的分配明细。页内表的行列对应未经人工核对,本篇只引备注明确交代的这个总量与它覆盖的范围,不逐行取数。

30MB 明细页
图 10-2 E 类·证据 · 讲义 slide 22 · 原样引用 怎么读 ①指认:页面那张表给的是覆盖 512 米范围所需 30 MB 的分配明细,同页备注另给出一条结论。②读法(怎么读):这一页只用来交代 30 MB 花在哪里,页内行列对应未经人工核对,正文不逐行取数;同页备注给出的那条结论是体素化不需要高精度 cluster,它由下一段承接。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

体素化这一头,讲者的判断是它不需要高精度 cluster(讲义 slide 22、23)。这句话在两页备注里都出现过,前一页把它与 30 MB 的明细放在一起,后一页把理由补全:体素化本身精度就有限,低精度的 cluster 反而更容易通过光栅化完成体素化。两条合起来,整体体素化的成本就能压得住。这处让步与下面直接光的处理是同一个路子,先在这里记下。

回到直接光(讲义 slide 24)。直接光与前面几页的 GI 是两处不同的计算,让步的方向一致。讲者的测试结论是生成 shadow depth 同样不需要高精度 cluster。他用的是更粗的近似,对画质的影响很小,性能提升却明显。这里换掉的精度只针对生成 shadow depth 那一步,画质上的差值由他来判定。例子他给了两个数:上面那张图用了 200 多次绘制调用(draw call),下面那张图用约 100 次绘制调用就得到相近的结果。

直接光让步页
图 10-3 E 类·证据 · 讲义 slide 24 · 原样引用 怎么读 ①指认:页面上下两张图是同一场景的直接光结果,上图用 200 多次绘制调用,下图用约 100 次绘制调用,备注说生成 shadow depth 不需要高精度 cluster、更粗的近似对画质影响很小。②读法(怎么读):把这一页读成性能与画质的交换,其中「影响很小」是讲者按自己这组画面下的判断;下一页会用同一组对照再确认一次。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

同一组对照在下一页又说了一遍(讲义 slide 25):上图是 200 次 draw call 的效果,下图是用 100 次 draw call 得到的 shadowmap,一张是更细的渲染、一张是阴影贴图,放在一起看,两者的差异几乎看不出。这一句同样挂在讲者自己那组画面上。他在这页还点出一个办法,最有效的手段之一是 shadow caching(阴影缓存)。

本章可核对的结论有四条。同一测试条件下(100 多个物体、每物体 1024×1024 lightmap、512×512 米场景),lightmap 至少要 160 MB、brickgi 只要 30 MB,而 brickgi 的 GPU 时间略高、讲者认为 3 毫秒对多数手游可接受(讲义 slide 21)。那 30 MB 的明细在另一页表上,本篇只引总量与覆盖范围(讲义 slide 22)。体素化不需要高精度 cluster,因为体素化本身精度有限,低精度 cluster 反而更容易经光栅化完成体素化(讲义 slide 23)。直接光也不需要高精度 cluster 来生成 shadow depth,更粗的近似对画质影响很小、对性能帮助明显,200 多次 draw call 与约 100 次 draw call 能给出相近结果,而最有效的手段之一是 shadow caching(讲义 slide 24、25)。下一章把这些读数与未竟项一起交代。

11端到端读数,和还没做完的部分

上一章交代了光照侧的让步,这一章把整条管线的账摊开,讲者从五个维度来对比(讲义 slide 26):效果、帧率、功耗、包体大小、制作效率。前两项落在玩家直接感到的画质与流畅度上,功耗与包体大小落在运行和分发的成本上,制作效率则回到开发那一侧。

画质这一页他给的口径是能同时渲染 8000 万三角形(讲义 slide 27)。他补了一句,这个量级在移动端、尤其是在零售设备上难以想象。在这个量级下帧率保持稳定,功耗也在可接受的范围内;包体上还省了一些;此外不需要手工做 LOD,只准备 LOD0 资产就够。这一页标题是画质,备注把帧率、功耗、包体与制作效率一并带了出来,四条都指向同一个三角形量级。

画质页
图 11-1 E 类·证据 · 讲义 slide 27 · 原样引用 怎么读 ①指认:页面给的是画质口径,能同时渲染 8000 万三角形,这个量级在移动端尤其零售设备上难以想象,该量级下帧率稳定、功耗可接受、包体更省,且不需要手工 LOD、只准备 LOD0 资产。②读法(怎么读):把这一页读成整篇的收益总述,各条分别由后面的帧率、功耗与分档位页支撑,此处不单独当作性能结论。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

帧率单独有一页曲线(讲义 slide 28)。备注说的是移动端的帧率,蓝绿两条线是启用无缝移动渲染之后的效果,其中蓝线维持在约 60 FPS、波动很小。备注只逐点说了蓝线,绿线仅交代它同属启用后的效果,没有给出读数;备注也没有给蓝线的具体波动幅度,本篇不补。

分档位的读数在下一页(讲义 slide 29):高端设备上只花约 3 毫秒 GPU 时间,5 年前的低端移动设备上约 20 毫秒。这一高一低两档设备的对照不能外推。

高端机的逐阶段耗时另有一页表(讲义 slide 30)。讲者按启用顺序一步步测:启用 instance 剔除之后是 0.11 毫秒;再打开 cluster 剔除是 0.07 毫秒;再打开 binning pass(分箱阶段)是 0.2 毫秒;接着光栅化这一遍是 0.72 毫秒;material classify 这一遍是 0.92 毫秒;最后一遍 material shading 是 0.87 毫秒。他给的可核对结论是剔除的耗时占比不显著,在这个场景里光栅化与材质着色才是更耗时的部分。同一页下面还有一张 IO Streaming 的表,他只点到 GPU 的 IO 表现也可以接受,本篇不引该表数值。这条序列是累加出来的,每打开一个阶段就多测一段,每一个数对应的是在前一步之上再加一个阶段之后的耗时。

高端机逐阶段耗时页
图 11-2 E 类·证据 · 讲义 slide 30 · 原样引用 怎么读 ①指认:页面是一张计时汇总页,上表把各渲染阶段的 GPU 耗时逐行排开,下表对应 IO Streaming。②读法(怎么读):自上而下一行一个阶段地读,把它当成预算分解表看;本页读数与两条结论的限定条件已写在上一段正文,此处不另起口径,下表亦不引数值。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

低端机那一档的读数也在(讲义 slide 31)。这是 5 年前那档设备上的表,结论与高端机有一处共同点,剔除的耗时同样占比不显著;差别落在最后一头,那个场景里材质着色是最耗时的部分。把两档的表放在一起看,高端机上更耗时的是光栅化与材质着色两块,低端机上最突出的是材质着色。

功耗是 10 分钟的测量(讲义 slide 32)。备注说无缝渲染在移动端表现良好,启用之后功耗明显低于传统 mesh 渲染模式。

功耗页
图 11-3 E 类·证据 · 讲义 slide 32 · 原样引用 怎么读 ①指认:页面是移动端 10 分钟的功耗测量,蓝线为启用无缝渲染之后的结果。②读法(怎么读):读这一页注意测量时长与设备口径,结论是启用之后功耗明显低于传统 mesh 渲染模式,属讲者自家管线与其传统模式的对照。 来源:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline · 讲者 Shun Cao(Tencent Games R&D)· 未修改引用

讲者没有把话说到满(讲义 slide 33)。他把这场的定位交代得很清楚,这些是研究与验证的结果,还有很大的改进空间,眼下只是开始。

后续要做的事他也列了(讲义 slide 34)。性能与功能都会跟着渲染技术与硬件能力的发展继续优化:性能侧他点到内存占用、software VSR with buffer 与 IO 优化,并自陈 1.5 毫秒仍然略重;功能侧最明确的一条是目前不支持光追,另外两项 mesh shader 与 bindless 材质着色系在移动硬件能力上,等硬件到位再加入。这一场以致谢页收尾(讲义 slide 35),他在那一页留了一句话,说时间给到这里,之后若还有问题欢迎继续找他。

本章可核对的结论有五条。讲者按五个维度对比:效果、帧率、功耗、包体大小、制作效率(讲义 slide 26)。画质上他给出的口径是手机同时渲染 8000 万三角形、帧率稳定、功耗可接受、省包体,并且不需要手工做 LOD、只准备 LOD0 资产(讲义 slide 27);帧率曲线上蓝线维持在约 60 FPS、波动很小(讲义 slide 28)。分档位是高端约 3 毫秒对 5 年前低端约 20 毫秒(讲义 slide 29);高端机逐阶段耗时按启用顺序为 0.11 / 0.07 / 0.2 / 0.72 / 0.92 / 0.87 毫秒,剔除占比不显著,光栅化与材质着色更耗时(讲义 slide 30),低端机上材质着色最耗时(讲义 slide 31)。功耗上 10 分钟测量显示启用后明显低于传统 mesh 渲染(讲义 slide 32)。未竟项包括目前不支持光追、1.5 毫秒仍略重、mesh shader 与 bindless 材质着色待硬件,以及这只是开始、改进空间很大(讲义 slide 33、34)。这些读数的适用边界见下。

边界 1:读数全部绑定机型与场景,不可外推。 「高端约 3 毫秒、5 年前低端约 20 毫秒」是一高一低两档设备的对照(讲义 slide 29);逐阶段耗时(instance 剔除 0.11、cluster 剔除 0.07、binning pass 0.2、光栅化 0.72、material classify 0.92、material shading 0.87 毫秒)是具体机型、指定场景按启用顺序逐步测得(讲义 slide 30);「160 MB 对 30 MB」绑定「100 多个物体、每物体 1024×1024 lightmap、512×512 米场景」这一条件(讲义 slide 21)。

边界 2:幻灯区无文本层。 页内标题、数值表与伪代码都不可作可选文本引用,本篇事实与读数只能以讲者备注为准;讲义 slide 21、22、29、30、31 等页内表格的行列对应关系未经人工核对,正文只引备注明确给出的数值。

边界 3:「类似 UE5 Nanite」是讲者自己的说法。 讲义 slide 9 的存储结构与 slide 11 的 LOD error 两处,本篇保留「类似、但加了 merge coefficient」的原口径,不等价于 Nanite。

边界 4:未竟项一并保留。 讲者自陈目前不支持光追、1.5 毫秒仍略重、mesh shader 与 bindless 材质着色要随硬件发展再加入(讲义 slide 34),并自陈这只是开始、还有很大改进空间(讲义 slide 33)。

边界 5:避开软光栅出于功耗与带宽的权衡。 理由是额外一遍场景深度 pass、缺 atomic64 支持、64 位 visbuffer 带宽更高(讲义 slide 12)。

边界 6:两组对照都是讲者自家内部测试。 lightmap 对 brickgi、200 对 100 次 draw call 两组对照,以及自家管线对传统 mesh 渲染模式的功耗对比,都属讲者自家内部测试,本篇不与任何商用方案横比。

边界 7:slide 7 的致谢页列出的他人演讲不在本篇范围。 该页点到 Drobot、Brian Karis、Wolfgang Engel、Sebastian 与 John Hable,本篇只记出处,不展开他们各自讲了什么。

边界 8:本篇未给数学推导。 GI 部分只有两级 atlas 的地址换算与两段伪代码,正文不替讲者补推导。

12贡献块

这一篇的贡献(精读视角)

三条可搬走的做法与站内互读

一场只有一个多小时的移动端讲座,能留下的东西大多是几处把「理论上可行」落成「资产管线里可交付」的判断。这一篇留下的三样分别是:如何让「不手工做 LOD」这件事在离线阶段真的可控;如何在缺两项关键硬件特性的平台上把 GPU 驱动的剔除与着色跑通;以及如何用「粗一点就够」把光照的内存账按量级压下来。三样都伴随明确的让步,讲者把它们一并写在讲义里 —— 本篇因此更适合当作一份移动端无缝渲染的工程路线记录来读,读的时候不必指望它给出普适的性能结论。

贡献 1:让自适应 LOD 在资产管线里可控。 离线这一段并不止于生成多级 LOD:它先把 mesh 切成许多 cluster、再连续地把它们重组成更粗一级的 cluster 并迭代(讲义 slide 9);关键在于加了一个 merge coefficient 函数,用它保证资产可控、并与手工制作的 mesh LOD 对齐。这一步解决的是「自动 LOD 会不会脱离美术预期」的老问题:讲者在读数段给出的收益也对应这一点,不需要手工做 LOD、只准备 LOD0 资产就够(讲义 slide 27)。可搬的是这条「离线可写、且能与手工结果对齐」的链路设计,具体用哪个误差度量并不关键。

贡献 2:在缺 mesh shader 与 bindless 的平台上把 GPU 驱动的管线跑通。 剔除侧让 GPU 自己决定要哪一级:对象、cluster 与可见 cluster 分处三个缓冲,先 instance 级 HZB 快剔,再叠加视锥、遮挡与法锥背面剔除,LOD 的选择走一条专门设计的 LodFactor 距离曲线(讲义 slide 11)。着色侧则用两个互相配合的让步换回并行度:中间结果只存 32 位、对象按是否蒙皮与是否要遮罩纹理分三类处理,并刻意避开软光栅(理由是额外场景深度、缺 atomic64、64 位带宽更高,讲义 slide 12);材质因为缺 bindless 而无法一次画完,就把屏幕切成 tile、每材质持一份 tile 列表,用 materialID 比对决定该块是否着色(讲义 slide 13)。

贡献 3:用「粗一点就够」把光照的账压下来。 讲者放弃了 lightmap 路线(精度能否覆盖像素级三角形、内存是否划算都成问题),改成全动态 GI:场景分块体素化,用 BrickTexture → BrickMappingAtlas → VisFaceAtlas 的两级偏移做快速 raymarching 与查询(讲义 slide 17、19);同一测试条件下 lightmap 至少 160 MB、brickgi 只要 30 MB(讲义 slide 21)。支撑这个量级差的是两处递进的让步:体素化不需要高精度 cluster(体素化本身精度有限,低精度 cluster 反而更容易经光栅化完成体素化,讲义 slide 23),生成 shadow depth 也不需要高精度 cluster(讲义 slide 24)。

站内可以接着读的几篇:篇目 34(Neural Light Grid)与本篇恰好是光照账本上的两条反向路线 —— 前者把预计算光照最贵的辐照度体积现代化,用一组学出来的权重函数替掉固定三线性核;本篇索性放弃预计算、改做全动态 GI,用层次体素把运行时开销压住,两篇的取舍可以对着看。篇目 31(SLIM)同样在为一大片内容做三角形预算与缩放,两篇的「按屏幕与距离分配算力」是同一类工程纪律。篇目 11(DDGI)与本篇的层次体素都属「用低阶的体积表示换可查询性」,代价都落在精度上。篇目 28(7DGS)与篇目 30(Adaptive Tessellation)也都在按误差与距离决定表示粒度。本篇讲义全篇没有给出代码仓库或开源链接,唯一的文献线索是讲者点到的几处他人演讲(讲义 slide 7)。

13原文出处块

原文出处

原文出处与引用口径

出处:SIGGRAPH 2024 · Advances in Real-Time Rendering in Games(Part I)· 2024-07-30 · 讲者 Shun Cao(Tencent Games R&D)· 官方讲义《Seamless Rendering on Mobile: The Magic of Adaptive LOD Pipeline》(NanoMesh),36 页,540×720 pt(竖版),来源 PDF 2026-siggraph-advances-nanomesh-lod.pdf

材料:官方讲义 36 页,36 页全部带讲者备注(合计约 1.11 万字符,是本篇唯一的文字来源;备注为自动转写文本,人名与专名偶有拼写出入,本篇按讲义原文保留、不作订正);幻灯区是单张整幅位图 1199×674、无文本层(逐页 clip 取词为 0),页内标题、数值表与伪代码都不可作可选文本引用;本篇事实与读数取自讲者备注;页码为 PDF 页序(从 0 起),引用写作「讲义 slide N」

许可:未检到页脚版权行(幻灯区无文本层,页脚若为图像亦不可选),也未检到 © / Copyright / Creative Commons / CC BY / MIT / License 任何声明;全篇未给代码仓库或开源链接。故 20 件图按分区非 CC 口径逐件署名(图号 + slide 号 + 出处 + 「未修改引用」),并在构建记录标注残留风险(讲义未授权转载,条款以官方发布为准),不得写成 CC 或可自由转载

引用口径:所有读数绑定机型与场景,不可外推 —— 「高端约 3 毫秒 / 5 年前低端约 20 毫秒」是两档设备对照(讲义 slide 29);逐阶段耗时(instance 剔除 0.11、cluster 剔除 0.07、binning 0.2、光栅化 0.72、材质分类 0.92、材质着色 0.87 毫秒)是具体机型、指定场景按启用顺序测得(讲义 slide 30),同页还有一张 IO Streaming 表,本篇只引其结论、未取图元亦未补数值;160 MB 对 30 MB 绑定「100 多个物体、每物体 1024×1024 lightmap、场景 512×512 米」这一条件(讲义 slide 21);「3 毫秒对多数手游可接受」「差异几乎看不出」「理想情况一次 draw call」都是讲者按自己场景与目标下的判断。讲义 slide 9 与 slide 11 的「类似 UE5 Nanite」是讲者自己的说法,本篇引用时保留「类似、但加了 merge coefficient」的原口径,不等价于 Nanite。讲者自陈的未竟项一并保留:目前不支持光追、1.5 毫秒仍略重、mesh shader 与 bindless 材质着色待硬件(讲义 slide 34),以及「这只是开始、还有很大改进空间」(讲义 slide 33)。本篇不与任何商用方案横比:两处对照(lightmap 对 brickgi、200 对 100 draw call)与功耗对照都是讲者自家内部测试。篇幅说明:本篇源材料量为本批最少(36 页、备注约 1.11 万字符、幻灯区无文本层,一点页内文字都借不到),正文按「照实覆盖全部 36 页、不注水」的口径写,最终落在分区正文下限附近。写作阶段另补了两处说明性文字:一是把三处索引位宽(顶点索引 8 位、三角形索引 7 位)的来路并排交代清楚,二是说明备注为自动转写文本、专名按原文保留;两处内容均出自讲义备注原文,未以扩写或复述图注补足篇幅。