看渲染工程师如何解密UE5的Nanite运行过程

 

UE5抢先体验版发行了也有一段时间了,大家都玩的咋样?哈哈哈是不是感觉时代真的要变了,即使只看网上大神们的演示,还是觉得真牛啊,尤其是Nanite虚拟微多边形几何体系统,取代了传统的网格体LOD系统后,虽然测试版中偶尔还是会出现一些小Bug,但是看大家还都是一片称赞!但背后的工作原理是如何运行的呢?Emilio López Riñón在网站http://www.elopezr.com/a-macro-view-of-nanite/发表了一篇关于Nanite背后如何运行的文章。文章内容使用Renderdoc捕获Nanite技术,试图理解其工作架构,一步一步告诉你Nanite每个点的技术原理。

 

看渲染工程师如何解密UE5的Nanite运行过程

 

作者目前是Playground Games的高级渲染工程师。内容有些晦涩难懂,不过读一下也可感受感受Nanite的厉害之处,下下面一起来看下

 

 

看渲染工程师如何解密UE5的Nanite运行过程
以下内容经原作者授权翻译

原作者Emilio López Riñón

翻译整理:CC

 

去年Epic发布惊艳世人的演示和最近推出的UE5预览版之后,Nanite一时间风靡全球。我试着玩了下,想搞清楚它的原理,它是如何运行的,所以使用Renderdoc捕捉了背后的技术。非常支持Epic开放他们的技术,这让学习和挑选变得更容易;编辑器中的标记和调试信息非常实用。

 

我们来看看下面这一帧,它是古代山谷演示中的项目,展示了Nanite 和非 Nanite 几何之间的相互作用。

 

看渲染工程师如何解密UE5的Nanite运行过程

 

 

Nanite:CullRasterize

 

Nanite流程的第一阶段是就是Nanite:CullRasterize,也就是下面这张图。简而言之,这整个过程主要就是剔除工作以及对三角形进行光栅化。

 

看渲染工程师如何解密UE5的Nanite运行过程
实例剔除(INSTANCE CULLING)

 

如果将Nanite:CullRasterize再进行细分的话,实例剔除则是第一步。它看起来有点像GPU形式下的遮挡剔除和视椎体剔除。在这里先对实例数据和原始数据进行了绑定,大家知道这在一定程度上意味着什么吗?这意味着会先对实例级别进行剔除,如果成功的话才会开始对更细粒度级别的剔除工作。Nanite.Views的缓存区为视椎体剔除提供了相机信息,而HZB(hierarchical depth buffer )则用于遮挡剔除。HZB来源于前一帧并向前投影到这一帧。我不确定它是如何处理动态对象的,可能是它使用了mip(小分辨率),以至于它足够保守。

 

可见和不可见实例都会写入缓存区。对于不可见的实例,应该是采用了在标准网格管线中执行遮挡查询的方式,也就是通知CPU某个实体被遮挡,然后停止工作并进行处理,直到它变得可见为止。可见实例也会被写入候选列表中。

 

 

持续剔除(PERSISTENT CULLING

 

然后就是持续剔除,持续剔除似乎和流媒体息息相关。它是固定数量的计算线程,这也就表明它与场景的复杂性无关,而是可能需要检查某些空间结构是否存在遮挡。它是一个复杂的着色器,但根据输入和输出,我们可以看到它将每种类型(计算和传统栅格)的可见三角形的数量写入名为

MainRasterizeArgsSWHW(SW:compute,HW:raster)的缓存区中。

 

看渲染工程师如何解密UE5的Nanite运行过程
集群和LODDING

 

有一点很值得一提,那就是LOD(多细节层次),因为它在这里可能起着决定性的作用。有一位网友推测,这可能是几何图像进行连续LODding的一种方式,不过似乎没有任何迹象可以证明这一点。三角形也被分成了几组补丁,并取名叫集群,并且还以这种方式完成了一些剔除工作。集群技术已经在Ubisoft和Frostbite的论文中描述过。对于LOD来说,随着实例中细节级别的下降,集群也开始出现和消失。这里使用了一些非常神奇的方法,以确保所有集群的组合可以无缝拼接在一起,通过下面这个视频可以大致的了解一下。

 

光栅化

 

在捕捉中似乎存在两种形式的光栅化:基于计算的形式和基于绘制的传统形式。前一个缓存区包含运行这些DrawcallDrawcall是OpenGL的描绘次数的两个间接执行的参数。

 

看渲染工程师如何解密UE5的Nanite运行过程

◆渲染 3333 个实例,每个实例包含 384 个顶点◆

♦运行 34821 组compute shader♦

 

第一个Drawcall使用的是传统的基于硬件的光栅化。虽然不确定选择传统的原因是什么,不过我们可以大胆猜测一下或许和三角形的大小相对于像素的大小有关。Epic在之前也有提到过,基于计算的光栅化在特定场景中的性能可以胜过硬件,而在其他场景中,硬件也会占有一定的优势。这些场景与硬件如何阻塞非常小的三角形有关,因为它无法有效地调度它们,从而影响占用率和性能。可以通过下面的图看到几个大三角形的实例,不过光用肉眼看的话其实还是很难分辨的。

 

看渲染工程师如何解密UE5的Nanite运行过程

上面的信息也让我们深入了解集群大小(384个顶点,即128个三角形),通常会选择有效地32和64的倍数来填补波前的GPU。3333个集群是使用硬件渲染的,然后Dispatch(调用)会处理Nanite几何体的其余部分。每组是128个线程,所以我的假设是每个线程处理一个三角形(因为每个聚类是128个三角形)的话,那么这里就会有高达约500万个三角形!这些数字证实了超过 90% 的几何图形是软件光栅化的。在这里提醒大家一下,对于阴影的话,除了最后只需要输出Depth,其他的过程都是一样的。

 

对于Post Pass中的几何子集也是重复上述过程。这样做的原因似乎是Nanite创建了一个更新的HZB(在 BuildPreviousOccluderHZB 中),其中还包含这个帧的Depth信息,将其与ZPrepass信息(该阶段发生在 Nanite 开始之前)相结合,并使用这个信息执行最新的遮挡剔除工作。

 

值得注意的是,在任何情况下,光栅化阶段的输出都是一个单一的纹理,想知道为啥吗,继续往下看。

 

可见性缓冲(VISIBILITY BUFFER

 

Nanite的最出名的特点之一就是可见性缓冲了。它是一个R32G32_UINT纹理,包含每个像素的三角形和深度信息。不过这里不包含任何关于材质的信息,因此第一个32位整数是稍后访问属性所需的数据。虽然可见性缓冲并不是一个新出现的概念,但是目前还没有任何一款商业游戏附带它。如果延迟渲染将材质与灯光分离,那么几何体也会与材质分离:每个像素/三角形的材质都被精确的评估一次,并且不会访问之后被遮挡的纹理、缓冲区或资源。

 

可见性缓冲的编码如下:

 

看渲染工程师如何解密UE5的Nanite运行过程

 

看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程

这里有一个上限约 40 亿2的32次方个三角形,还有一个很有趣的发现是,这里的信息是非常有限的。其他可见性缓冲建议存储重心坐标。一切都是通过将三角形与相机光线相交、从原始缓冲区读取数据以及动态重新计算/插值顶点量来推导出来的。最后要注意的是,通过角色站立的地方的后面缝隙,我们可以看到系统的剔除效率,是不是非常了不起。

 

 

Nanite::EmitDepthTargets

 

看渲染工程师如何解密UE5的Nanite运行过程

 

这个阶段输出三个重要的量:深度、运动矢量和“材质深度”。前两个是标准量,后来用于 TAA、反射等。还有一个十分有趣的纹理叫做Nanite遮罩,它可以表明渲染Nanite几何体的位置。

 

下面这几张图就是它们的样子:

 

看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程

 

 

纹理深度

 

然而,到目前为止,这个阶段最有趣的纹理输出还是纹理深度。它本质上是将一个材质ID转换为唯一的深度值并存储在深度模板目标中。实际上,每种材质都有一个灰色阴影。接下来将会利用它为Early Z进行优化。

 

看渲染工程师如何解密UE5的Nanite运行过程

Nanite::BasePass

 

看完上面的内容,大家对几何流程多多少少了解了一些了吗?不过我还没有讲过任何关于材质的内容。这里很有意思,因为在可见性缓冲生成和现在之间,帧实际上花了很多时间来做其他事情:光栅、天空大气等,并且还会像往常一样渲染 GBuffer。这确实推动了可见性缓冲所针对的几何体和材质之间的分离。这里有两个非常重要的步骤:Classify Materials 和Emit GBuffer ,往下看。

 

看渲染工程师如何解密UE5的Nanite运行过程
材质分类(CLASSIFY MATERIALS

 

材质分类通道通过运行一个基于计算的着色器来分析全屏可见性缓冲。这对于下一个通道来说非常重要。这个过程的输出是一个20×12 (=240) 像素的R32G32_UINT纹理,也被称作Material Range,它对每个图块所代表的64×64区域中存在的材质范围进行了编码。当作为颜色纹理查看时,它看起来就像下面这样:

 

看渲染工程师如何解密UE5的Nanite运行过程
EMIT GBUFFER

 

然后就是Emit GBuffer。虚幻引擎允许用户定义任意材质的表面,那么我们究竟该如何有效地管理这种复杂性呢?下面这张图就是Emit GBuffer的样子:

 

看渲染工程师如何解密UE5的Nanite运行过程

每个材质ID都有一个看起来像是Drawcall(Drawcall是openGL的描绘次数)的东西,每个Drawcall都是以一个被切成240个在屏幕上渲染的方块的全屏四边形展现出来的。那么有人就会问了,每个材质一个全屏Drawcall?这不会太疯狂了吗?其实不完全是这样的。我们之前提到材质范围纹理是240像素,所以这个全屏Drawcall的每个四边形都有一个对应的纹理元。四边形顶点对这个纹理进行采样并检查瓷砖(Tile)是否与它们相关,即瓷砖中的任何像素是否有它们将要渲染的材质。

 

同时,这个系统是使用14位作为材质ID的,总共包含了16384个材质。Constant Buffer将材质ID发送到顶点着色器,以便它可以检查它是否在范围内。

 

最重要的是,我们要记住创建了一个材质深度纹理,其中每个材质ID都设置为一个特定的深度。这些四边形被输出到它们的材质所表示的深度,并且深度模式也被设置为相等,因此硬件可以非常快速地去除任何不相关的像素。我们可以通过下面的图片来看看反照率缓冲区:

 

看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程
看渲染工程师如何解密UE5的Nanite运行过程

你可能已经注意到那些红色的四边形,我本以为它会被顶点着色器完全丢弃,然而是一系列的材质被瓷砖方块覆盖。如果某个材质恰好位于“中间”,但如果没有一个像素显示这个材质,深度检测后会将它完全丢弃,将被视为候选材质。在任何情况下,相同显示的图像都是重复这个过程,直到所有材质都得到处理。最终的GBuffer如下图所示:

 

看渲染工程师如何解密UE5的Nanite运行过程

好了,今天关于UE5中Nanite的内容就是以上这些,当然还有大量的细节和关于它是如何工作的内容我们还没有介绍到,所以大家就期待一下以后的内容吧~

 

看渲染工程师如何解密UE5的Nanite运行过程
翻译整理自:http://www.elopezr.com/a-macro-view-of-nanite/
—全文完—

 

原创文章,作者:CG世界,如若转载,请注明出处:https://www.cgworld.wiki/40598.html

发表评论

登录后才能评论