当前位置: 华文星空 > 知识

UE5的nanite和DX12U的meshshader有什么不同?

2021-05-29知识

不,mesh shader更加先进。nanite则是在不能对硬件动刀的前提下,采用软件针对特定应用的一种特定的实现。

从概念的范畴来说,nanite是UE5当中,针对高面数模型在引擎当中的使用,给出的一种「纯软件」的实现。当然,它对硬件有一定的要求,但是并非对硬件提出了专用的定制化要求,而只是需要支持Compute Shader的GPU,以及满足一定带宽需求的硬件环境。

而mesh shader是硬件升级本身带来的产物,其本身改变了硬件的设计,改变了GPU渲染管道的流程,可以看作是CS的一种特化,是一个软硬结合的方案,用途也并非仅仅是针对高面数模型,且其自身也并不包括数据的组织方式和加载(串流)方式。

但是在解决高面数模型的渲染问题方面,nanite和mesh shader的确是有异曲同工之妙。这是因为,据我所知,这两个技术其实应该都是来自一个共同的起源:用CS实现三角形的筛选(culling),然后再将结果交给通常的渲染管道渲染。这一过程通常被称为dispatch draw。

dispatch draw的出现,是因为随着画面的提升,一帧画面要处理的模型面数越来越多。在DX9级别的固定管线GPU当中,三角形首先以顶点+索引流的方式进入VS阶段,完成一系列坐标变换之后,才会被后续的PA模块组装成为三角形送入后续的SC模块,也就是离散化模块进行离散。

这样的处理在近代的高品质游戏渲染当中,出现几个显著的问题。VS的过程是基于顶点的,而且近代的游戏会在顶点上附着很多属性:比如两套甚至两套以上的uv、顶点色、法线、tangent、高度图等等。这些属性值都需要在VS过程当中处理,并且结果需要存储。

因此VS通常在执行之前,GPU的调度模块需要为其分配片上缓存区域,进行结果的存储。这个分配过程本身开销较大,而且片上缓存十分金贵,一般只能存储十几个顶点这样,后续的SC则需要对附着在顶点上的所有属性完成插值,并且在后续的PS完成对这些值的参照之后,相应的缓存才能释放。

也就是说,VS计算本身是相当占用片上资源的。然而,DX9的传统管线缺乏在VS之前对顶点进行筛选的能力,所有的三角形,无论大小,都是一个三角形3个顶点,需要进行VS处理。很多微小三角形最终画面面积可能只有几个甚至不足一个像素,却要完整地经历3个顶点的VS处理。

也就是,如果从最终画面角度来看,各个顶点的VS计算对于最终画面的「面积贡献度」不是恒定的。这是由于VS基于顶点,而不是基于三角形的处理方式导致的。

所以,在出现CS之后(CS本身为什么会出现,又是另外一个故事了,篇幅原因这里不赘述,和GPGPU的出现有关),一些渲染方面的老手,开始着手用CS解决VS的这个问题,也就是在执行VS之前,先用CS按照三角形的概念对顶点和索引进行一遍过滤,调整顶点的「画面面积贡献度」之后,再交给VS处理。

这个成果在实际的商业游戏当中得到运用和检验(抱歉我忘记了是哪款游戏了)(评论区补充:源起15年大革命的cluster rendering和17年ea的visibility buffer,可以补充上~[爱] @SaeruHikari ),并且在siggraph上面进行了发表。很可能N社和Nanite的作者都是看到了这个发表,然后分别用自己的屁股进行了思考。

N社的屁股是硬件,所以自然而然反思了VS本身的实现,将其输入从顶点+index拓展到primitive+顶点+index,也就是在考虑顶点的同时考虑三角形本身,并且在这个基础上进一步拓展(泛化)成为输入数据格式可以任意自定义,在shader内通过一系列计算直接输出最终需要渲染的顶点和index流给SC,这就是mesh shader。

而Nanite的作者的屁股是UE4,这么一个跨平台引擎。所以自然以当时广泛的技术条件为基础,也就是CS为实现手段,考虑如何进一步提升效率的问题。其结果就是进一步统合dispatch draw的上下游。首先是对于上游,吸收Virtual Texture的经验,将模型以层级方式进行组织,以流送的方式加载并且用CS进行基于三角形的culling,选择出对于当前画面必须的顶点送入后续处理。并且在此基础上统合下游,将VS负责的部分,甚至是部分PA+SC的部分也直接合并到CS当中,减少GPU在不同stage之间切换的开销,将不同stage之间数据的传递转化为同一个stage内的寄存器数据传递,提升效率。

nanite的这种做法的好处是其泛用性,对硬件要求相对较低。但是自然也无法完全享受硬件升级带来的所有好处。可以预见在几个版本迭代之后,nanite应该会切换到改用mesh shader实现的方式。(或者至少,加入对mesh shader的支持)