十分钟看懂虚幻引擎5都有啥黑科技(Nanite上篇-文字稿与参考文献)

20205月,Epic发布了第一个关于虚幻引擎5的视频。他们在视频中演示了虚幻引擎5中看起来最牛逼的两个系统。渲染实时全局光照的Lumen,和实时渲染海量多边形的Nanite。刀客儿虽然之前曾经做过一期关于虚幻引擎5的节目,不过当时做那期节目的时候,刀客儿手中的信息比较有限。关于Nanite的实现方案的推测是错误的。Nanite并不仅仅是一个简单的渲染海量多边形的渲染管线,它其实包括了从导入影视级别3D模型开始直到渲染出游戏画面的一整套流程以及相应的工具链。今天这期节目刀客儿就来给大家详细聊聊Nanite是怎么高效的渲染海量多边形场景的。

随着电脑和游戏机硬件性能越来越强,游戏的场景也变得越来越复杂。场景变得复杂就意味着模型的个数和三角面数都变的越来越高。这种情况下,把摄像机能看见的所有模型都按照原样提交给GPU绘制,会造成性能的极大浪费,因为远处的那些模型,在屏幕上看起来都非常非常的小。在有限的分辨率下,银眼啊根本看不出什么细节。所以大部分游戏都会把远处的模型替换成一个看起来长得差不多但是三角形数特别少的一个简模去渲染,而近处的模型会使用三角面数很高的高模去渲染。这样既保证了近处的模型有足够的细节,也保证了远处不会有绘制高模而导致的性能浪费。这种方法在英文中叫做Level of Detail, 简称LOD,翻译成中文是细节层次或者细节等级的意思。不过我们平时都不会用细节层次这个词,因为这四个字太长了,读它有点浪费时间,所以我们都是叫它LOD的。而Nanite本质上也可以看作是一个LOD系统,和其他游戏中的LOD系统一样,他会使用较少的三角面数来绘制远处的模型,使用较多的三角面数来绘制近处的模型。人类迄今为止已经发明了很多实现LOD的方法,但是目前为止在游戏中最常用的方法仍然是给高模整体生成几个不同细节等级的低模,细节等级越低的低模,面数也就越少。然后根据模型与摄像机的距离或者模型在屏幕上投影出来的大小来决定显示哪个细节等级的模型。这种方法所需的算力比较低,所以性能表现就比较好。但这种方法的问题就是,在切换不同细节等级的模型的时候玩家很容易就会发现,哎,刚才那个模型咔嚓一下就变精细了,好神奇啊。但Nanite厉害的地方在于,玩家根本感觉不到远处的模型是什么时候切换的细节等级,整个场景看起来好像一直显示的就是最精细的模型。那么Nanite是怎么做到细节等级可以像无级变速一样柔和切换的呢?要想回答这个问题,首先我们要从影视级的高模导入虚幻引擎5的那一刻说起。在虚幻引擎5中,当你最初导入一个3D模型的时候,它也会像其他游戏引擎那样,将这个模型原样导入到游戏引擎当中。模型的顶点和三角面的数量都不会有任何的变化。但当你为这个模型打开Nanite开关之后,引擎就开始了一个漫长的导入Nanite的流程。首先引擎会将3D模型的所有三角面分成一个一个的簇,英文叫cluster。每簇三角形里面最多有128个三角面。如何去把模型分成这些簇也是有讲究的,它需要尽可能的让簇和簇之间的共享边界越少越好。这个问题呢就相当于是切割一个边带权重的图,图的每个节点就是每个三角形,节点之间的边上权重代表三角形之间共享边的个数,然后让被切掉的这些边上的权重加起来越小越好。这个问题是一个NP困难的问题,用人话说,就是嗯。。。很难很难的问题。不过幸好这个问题过去就已经被研究了好多好多年了,有现成的解决方案,所以Nanite这边使用了METIS这个库来完成给模型分簇的工作。

把模型的三角形都分好簇之后,就可以开始减面的操作了。大家可能会想,这簇都分好了,那咱们直接在每一个簇的内部每次减掉一半的面数就可以生成一个更低等级的LOD,然后在渲染的时候根据距离或者屏幕投影大小之类的参数为每一个簇选择一下显示这个簇的第几个LOD不就完事了嘛。这样LOD切换就是以簇为单位的切换,不会出现整个模型跨擦一下就整个全换了的情况,而且每个簇原本有128个面理论上能生成多达8LOD,那不就实现了模型的不同部分可以逐渐切换LOD的操作咯。这岂不是美滋滋了?。。。emm,这么做虽然逻辑简单清晰,但是会有一个问题,那就是三角形簇在减面之后,必须要让这个簇与其他簇之间的边界保持不变,如果不锁住这个边界的话,一旦两个相邻的三角形簇选择了不同的LOD,那么这两个簇之间边界就无法完全贴合从而出现穿帮的现象。而锁住了边缘之后,由于要保持簇内部和边缘的连接状态,导致无论你怎么努力最后都减不了太多的面,而且在被锁住边缘的附近还会出现一大堆细小的三角形的情况,这些细小的三角形不仅数据量很大,还很容易造成法线看起来不连续的情况。所以为了避免刚才提到的这些问题,Nanite在减面的时候,会首先把几个临近的三角形簇放到一起形成一个簇组,英文叫cluster group,直接翻译成中文就是簇嗯。。簇组~,每一个簇组和其他簇组之间的边界是锁定的。然后减面的时候会把每个簇组内几个簇包含的三角形数量减去一半,之后再把剩下的一半数量的三角形,重新以128个三角面为单位生成新的簇,然后用这些新的簇重新生成新的簇组再锁定这些新簇组之间的边界,再根据与前面相同的规则再把三角面数减少一半。每一轮操作下来,三角面数和三角形簇的个数都会被减少一半。重复这个过程,直到只剩下一个三角形簇的时候,就完成了减面的操作。后续在渲染的时候,会给同一拨三角形簇形成的簇组统一选择同一个LOD来进行渲染,簇组和簇组之间的边界只在每两级LOD之间锁定,这样只要保证簇组和簇组之间的LOD最多只有一级的差距,模型整体上就不会出现穿帮的现象了。

每个三角形簇和它对应的更精细版本的三角形簇之间会形成一个有向无环图,英文叫做Directed Acyclic Graph, or DAG。这种图会为联通的节点之间的边标记一个流动的方向,这个就叫做有向,而且你只要顺着它标记的方向走一定不会回到之前走过的节点上,这个叫做无环。把这两种特性结合起来,就得到了有向无环图。这个图将在实际渲染nanite模型的时候发挥重要的作用。后面我们在讲渲染系统的时候,还会深入的讲这个图到底是怎么发挥作用的。

不过现在咱们再回过头来看一下,另一个刚才提到但是没有详细讲的一项工作,那就是Nanite具体是怎么给模型进行减面的。给模型减面有很多种方法,既可以安排一个艺术家手动减面,也可以使用一些算法来自动减面。不过即使是用脚后跟想肯定也知道,Nanite肯定不会让一群艺术家在电脑前面坐着,把你发给他的模型减完面再给你发回来的,那个效率可太低了。所以Nanite不出意外的使用了一种算法对模型进行减面,这种算法的名字叫做《二次曲面误差度量》,英语叫做Quadric Error Metrics,简称QEM。顺便说一句这个二次曲面误差度量的名字是我自己翻译的,我实在是妹找到这玩意中文到底叫啥,只能按照自己的理解翻了一下,如果有人知道这玩意正规的翻译叫啥欢迎再评论里面告诉刀客儿。这种方法本质上是用顶点合并的方式,给三角形减面的。他会预先为三角形的每一个顶点和与之相连的各个顶点之间都计算出一个最优的融合点,如果两个顶点在这个最优融合点的位置融合,会让模型的新顶点到原模型的相关的各个平面距离最小,从而让模型在减面后外观尽可能与妹减面之前的样子看起来差不多,这个差别程度会被量化成一个误差值。这个误差值的具体计算的过程涉及到一点点简单的微积分和线性代数的内容,想详细了解的朋友可以截图仔细看一下,我这边就不详细讲了,免得大家说我又不说人话了。。。另外,Nanite对这个算法也进行了一些修改来适应一些特定的需求,例如在减面的时候需要锁住簇组之间的那些边缘,不能让那些边缘也被优化掉。另外,不仅顶点的坐标需要误差最小化,顶点的属性,例如纹理坐标这种数据也需要误差最小化,来尽可能让减面之后的模型不会出现贴图扭曲的情形。但让顶点坐标误差最小化的点并不一定能让UV坐标的扭曲误差也最小化,所以他们在误差计算的时候为不同的顶点属性引入了不同的误差权重,用来平衡顶点的各种属性对减面误差计算结果的影响。

好了,经过上面那一波猛如虎的操作之后,现在我们手里拿着分好LOD的模型数据,以及用这些数据摆出来的场景数据,终于就可以开始进行渲染游戏画面的工作了。不过在把模型数据提交给GPU之前,首先要确定的就是如何根据摄像机的位置与视线的方向去决定哪一个LOD等级是需要显示的,这样才能忽略其他的那些LOD,只把模型的这个LOD数据提交给GPU进行绘制。

前面提到过,多数游戏都是使用模型与摄像机的距离,或者模型在当前屏幕中投影的面积来决定使用模型的哪个LOD来进行绘制的。不过,无论是使用距离还是投影面积来进行LOD的切换,始终很难避免LOD切换的时候给玩家带来的哪一种跳变的感觉。这是因为无论是距离还是投影面积,都是对模型的不同LOD等级反映到屏幕观感变化程度的一种间接评估。这种间接评估不太容易真实的反应LOD切换时,玩家能感知到的画面变化。因为这些方法只考虑了摄像机和模型之间的距离关系,而没有考虑个头差不多大,但是长得不一样的那些模型之间的区别。

举个例子,咱们有两个个头大小差不多的模型,一个是球体,另一个是正方体。球体因为需要很多三角面才能近似形成球的形状,所以它在减面之后外轮廓就会出现比较大的变化,所以要让球体在切换LOD的时候跳变感不那么明显的话,就需要在更远的距离或者球体体积更小的时候去切换,换句话说也就是球体在屏幕上覆盖的面积比较小的时候才能进行切换。而正方体由于它的形状比较方正,它减面之后和减面之前几乎看不出轮廓有什么变化。所以正方体在距离摄像机很近的位置或者体积很大的时候就可以去切换LOD,换句话说也就是在它覆盖屏幕面积还比较大的时候就可以去切换成低等级细节的LOD了。

所以,想要尽可能在切换LOD的同时让玩家无感,不仅需要考虑距离或者覆盖面积这样的参数,还需要考虑模型的形状对于切换LOD所造成的观感变化才行。那这个形状对观感的影响。。。这玩意儿咋算啊?这玩意好像是个玄学啊!没错,这东西由于跟人的感觉有些关系,所以的确是有一点玄学的成分在里面。但是呢,咱们从数学与几何的角度去考虑,还是有办法对这个形状的误差做出一定程度的估计。那么怎么去估计两个相邻LOD之间的形状误差呢?细心的同学可能已经发现,此前在给模型减面的时候,咱们曾经使用了《二次曲面误差度量》的方法计算过两个相邻LOD之间的形态误差,这个误差投影到屏幕空间上,就可以被用来估计模型LOD之间在屏幕中形态变化的程度啦。有了这个形态误差之后,我们就可以计算出模型与摄像机的距离在多远的时候,切换LOD导致的模型变化在屏幕上是几乎不可见的。计算出这个距离之后,只要在这个距离以外去切换成低等级的LOD,就可以让玩家几乎感受不到远处模型细节等级的降低了。而这,也就是Nanite可以丝滑切换细节等级的方法。

不过Nanite实际切换LOD的逻辑比我前面讲的还要复杂,他的LOD切换并不是以整个模型为单位的,而是以之前提到的三角形簇组为单位去选择LOD的。每一个簇组中所有的簇都会同步使用相同的LOD,这样簇组内部所有簇之间就不会出现模型穿帮的现象。簇组之间会保证最多只有一级LOD的差距,从而保证簇组之间的边界是牢牢锁住的,也不会出现穿帮的效果。

虽然我们现在知道了要用什么标准来衡量模型LOD之间,外观变化的程度,并根据这种变化的程度计算出一个合适的时机来切换LOD。但游戏场景中可能会放置大量甚至海量的模型,对所有的模型的所有簇组去选择LOD显然是一个不可能完成的任务。那么Nanite是如何把场景中实际需要显示的那些数据提取出来的呢?这些数据提取出来之后又是怎么提交给GPU最终渲染出游戏画面的呢?下一期,刀客儿将继续给大家介绍Nanite是如何高效的渲染出海量多边形的复杂场景的。

如果您喜欢我的视频,可以帮忙点赞收藏转发投币。您也可以订阅我的频道,第一时间收看我的视频。如果您对我的节目有什么意见和建议也欢迎在私信或者评论中告诉刀客儿。那么感谢大家的收看,咱们下期再见咯,拜拜~

参考文献

Nanite – A Deep Dive – SIGGRAPH 2021

https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf

Surface Simplification Using Quadric Error Metrics

http://www.cs.cmu.edu/~garland/Papers/quadrics.pdf

Mesh Simplification – Stanford Computer Graphics Laboratory

https://graphics.stanford.edu/courses/cs468-10-fall/LectureSlides/08_Simplification.pdf

Geometry images

http://hhoppe.com/proj/gim/

Virtual Geometry Images

http://graphicrants.blogspot.com/2009/01/virtual-geometry-images.html

Unreal Engine 5 Revealed! | Next-Gen Real-Time Demo Running on PlayStation 5

https://www.youtube.com/watch?v=qC5KtatMcUw

Nanite Virtualized Geometry

https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/

importing high poly models in unreal engine 5

THE MATRIX AWAKENS: AN UNREAL ENGINE 5 EXPERIENCE Walkthrough Gameplay Part 1 – INTRO (PS5)

Unreal Engine 5 Gameplay: Valley Of The Ancients Early Access Demo

Checking Out Valley Of The Ancient – Unreal Engine 5

https://www.youtube.com/watch?v=7Kyz38HaAYM

What is Level of Detail (LOD)

https://www.youtube.com/watch?v=pzuJztVAeQ0

Level of Detail – LOD

https://developer.arm.com/documentation/102496/0100/Level-of-Detail—LOD

Top 20 Best single player AAA Games 2017-2022

https://www.youtube.com/watch?v=VWinjYJguHE

LOD Explained (Unity Tutorial)

Level of Detail in Unity Using LODGroups, Dithering, and Crossfade! ✔️ 2020.3 | Game Dev Tutorial

资源下载: