大家好,我是来自Epic Games中国的首席引擎开拓工程师王祢,紧张卖力引擎干系技能的开拓者支持事情,帮助海内的开拓者办理各种利用UE开拓项目时碰着的技能问题,同时也会参与部分引擎事情的开拓。
本日我紧张为大家先容UE5的新功能。当然,UE5有太多新功能了,我会挑大家最关心的Nanite和Lumen多讲一些。
在开拓UE5的时候,我们的目标是:提高各方面的渲染品质,让构建的数字天下更动态一些,提高全体虚拟天下构建和表现的上限;同时我们也希望提高开拓和迭代的效率,供应更多更丰富易用的工具,改进用户编辑和创造的体验,降落大家利用的门槛。
比较UE4,UE5做了大量改进,紧张是Nanite和Lumen等渲染技能,构建全体大天下的工具以及底层对渲染大量工具天生一些Proxy Mesh技能。
在协同事情方面,改进包括管理大量资产的性能、编辑器和用户体验、次世代的一些动画技能Chaos、网络同步的物理系统,以及一些全新模块、游戏框架、AI集群系统、进一步完善的Niagara系统以及各种音频模块,像Meta Sound之类的功能都有非常大的改进。
Nanite功能
首先是我们主打功能之一的Nanite,Nanite是全新的Mesh表示形式,是一种虚拟微表面几何体,解放了之前美术同学制作模型时对大量细节的限定,现在可以直策应用真正用于影视级别的资产,几百万乃至上亿面的模型直接可以导入到引擎中,非常顺畅的放很多的实例去高效的渲染。
例如来自照片建模或者Zbrush雕刻的高模,或者CAD的数据都可以直接放进来,我们有过测试可能几万乃至十几万的,这些实例每个都是百万面以上的都在view内能被看到的情形下,用Nanite的办法渲染依然能在2080s这样的GPU上跑到60fps,分辨率可能是1080P旁边。Nanite还在开拓中,还有很多功能支持并不完善,我们在后续会逐步改进。
Nanite支持的平台紧张是新一代的主机和PC,比较去年我们放出来的Lumen in the land of Nanite ,这项技能的品质和效率都有不少提升,包括磁盘的编解码效率和压缩、支持Lightmap烘焙光照、支持可破碎物体、对光芒追踪场景或者物理碰撞支持自动天生减面高质量的替代Proxy mesh。
其余通过这种办法,我们还可以用解析微分法决定像素偏差,使偏差肉眼不可见。末了,我们还高效支持了多光源投影,全体Nanite管线基于GPU driven的管线产生,紧张流程我会分以下几个部分来讲。
为了让大量工具在场景上高效剔除,我们须要把所有场景数据都送到GPU上。实在从4.22开始,引擎就逐步在不影响上层利用的情形下,在底层做出改进了,使渲染器成为retained mode,掩护了完全的GPU scene,Nanite在这个根本上做了大量新的事情。
Nanite中cluster的天生
接下来我们大略讲讲Nanite的事情机制。首先在模型导入时,我们会做一些预处理,比如按128面的cluster做切分处理。有了这些cluster往后,我们就可以在间隔拉远拉近时,做到对每个cluster group同时切换,让肉眼看不到切换lod导致的偏差没有crack,同时还能对这些不同层级、细节的cluster做streaming,这实在便是Nanite最关键的部分。
cluster的天生紧张分以下几步:首先,原始的mesh lod0数据进来后,我们会做一个graph partition。partition条件是比如说我希望共享的边界尽可能少,这样我在lock边界做减面处理时,减面的质量会更高一些。
第二是希望这些面积尽可能均匀、大小同等,这样在lod打算偏差处理投影到屏幕上时,都是对每个cluster或cluster group同等处理。我们会把个中一组cluster合并成一个cluster group,又一次按照“lock的边界尽可能少、面积尽可能均匀”的条件找出,一组组cluster天生group,对这个group内cluster的边解锁,即是把这组group算作一个大的cluster,然后对这组group做对半的减面。
减完面后,我们可以得到一个新的cluster偏差,我会对这个减面的group重新做cluster划分。这时,cluster的数量在同一个group里实在就已经减半,然后我司帐算每个新的cluster偏差。大家要把稳,这个过程是循环的,递归一贯到终极值 ,对每个instance、模型只天生一个cluster为止。这里有一个比较关键的点:我们在减面天生每个cluster时,会通过减面算法(QEM)得到这个cluster的偏差值并存下。
除此之外,我们还会存group的偏差值,这个值实在便是更风雅的那一级cluster group里cluster的最大偏差值,和我新一级里产生的每个cluster偏差值取maximum得到的值。这样我就能担保这个cluster每次合并的group,去减面到上一级的group里的cluster时的偏差值,永久是从不风雅到风雅逐步上升的状态。
也便是说,我从最根结点的cluster逐步到最细的cluster,里面的error一定是降序排序的。这一点很主要,由于它能担保后续选择culling和lod时,恰好是在一个cluster组成的DAG上。由于cluster会合并group,group天生打散往后不才一级里,又会有一个共享的cluster。
有了这个降序排列的偏差,我就能担保这个DAG上有一刀很干净的cut,使我的边界一定是跨lod的cluster group的边界。末了,我们对这个天生的各个lod层级的cluster分别天生bvh,再把所有lod的cluster的bvh的root,挂到总的bvh root上。
当然,这里还有很多额外处理,现在没有讲是考虑到做streaming时的一些分页处理。这个分页可能会对cluster group造成切割,以是cluster group,还有一些group partition的观点,我们这里不做细化。
其余,对付一些眇小物体离得很远往后的情形,我们减到末了一级cluster,实在它还是有128个面,那如果场景里非常小的东西位于很远的地方,这又是一个模块化的构成。我们又不能直接把它culling掉,这种情形下,我们会有其余一种Imposter atlas的办法,这里我也不展开讲了。
Nanite裁剪流程
末了裁减到它bvh的叶子节点,实在便是我们刚才说的cluster group,然后再对个中的cluster做裁减。裁减完之后,我们就会有一个分外的光栅化过程,然后我们就能得到新的Depth Buffer,重新构建HZB,再对这个新的HZB做一遍裁减。
前面那次HZB的可见性,我们用了上一帧可见的instance来做,做完之后形成新的HZB,我们再把上一帧不可见的,在这一帧内所有剩下的再做一遍,就能守旧地担保没有什么问题。
重新经由光栅化后,天生到新的visibility buffer,再从visibility buffer经由material pass,终极合入Gbuffer。详细做culling时会有一些问题,比如刚才cluster天生时我们说到过,天生cluster group的bvh构造,我们在CPU上不会知道它有多少层。
也便是说,如果我要去做的话,CPU要发足够多的dispatch,这时比如小一点的物件,它空的dispatch就会很多,这种情形下GPU的利用率也会很低。
以是我们选择了一种叫persistent culling的方法,利用一个persistent thread去做culling,也便是只做一次dispatch,开足够多的线程,用一个大略的多生产者、多消费者的任务行列步队来喂满这些线程。
这些线程从行列步队里实行时,每个node会在做封层级别剔除的同时产生新的node,也便是bvh node,Push back回新的。在可见的children的列表里,我们一贯处理这个列表,直到任务为空。
这里的处理分为几种类型:首先在一开始的node里,只有我们开始构建的bvh的节点,直到我一贯做剔除,剔除到叶子节点往后,里面是个cluster group,再进入下一级,便是这个group里面所有的cluster culling。末了cluster并行独立地判断,自己是否被culling 掉,这里实在和刚刚lod选择的条件是千篇一律的。
还记得我刚才说的error的单调性吧?由于这里的cluster中,所有lod都是稠浊在一起的,以是我们每个cluster在并行处理时,我不知道父级关系是什么样的,但我在每个cluster上存了自己的偏差,和我全体group在父一级上的最大偏差,以是这时我就知道,如果我自己的偏差足够小,但是我Parent的偏差不足小,我就不应该被culling掉。
同理,跟我共处一个cluster group的这些节点,如果它在我上一级lod里,也便是比较粗的那一级里,那它的error一定不足大,以是上面那一级lod所在的全体group都会被抛弃掉,而选中下一个。
但是下一个里面,实在还是可能会有一些偏差太大的——它的偏差如果足够大,就意味着它在再下一级更风雅的地方,肯定属于其余一个cluster group。以是它又不才一级的cluster group里又有一个边界,和它下一级的cluster group边界接起来会没有接缝,全体cluster的选择便是这样并行做的。
同时,对应自己cluster group的parent,刚刚我们说了,肯定会被剔除掉。这样就能担保我们能分cluster group为边界,去对接不同lod层级的cluster,并使经由culling存活下来的cluster来到分外的光栅化阶段。
Nanite中的光栅化
由于当前图形硬件假设了pixel shading rate,肯定是高于triangle的,以是普通硬件光栅化处理器在处理非常的眇小表面时,光栅化效率会很差,完全并行也只能一个时钟周期处理4个triangle,由于2x2像素的会有很多quad overdraw,以是我们选择利用自己用compute shader实现的软件光栅化,输出的结果便是Visibility Buffer。
我这里列出的构造统共是64位的,以是我须要atomic64的支持,利用interlocked maximum的实现来做仿照深度排序。以是我最高的30位存了depth、instanceID、triangleID。由于每个cluster128个面,以是triangleID只要7位,我们现在实在全体opaque的Nanite pass,一个draw就能画完天生到visibility Buffer,后续的材质pass会根据数量,为每种材质分配一个draw,输出到Gbuffer,然后像素大小的三角面就会经由我们的软件光栅化。
我们以cluster为单位来打算,比如我当前这个cluster覆盖屏幕多大范围,来估算我接下来这个cluster里是要做软件光栅化还是硬件光栅化。我们也利用了一些比如浮点数当定点数的技巧,加速全体扫描线光栅化的效率。
比如我在subpixel sample的时候是256,我就知道是由于边长是16。亚像素的改动担保了8位小数的精度,这时我们分界利用软光栅的边界,刚好是16边长的三角面片的时候,可以担保整数部分须要4位的精度,在后续打算中最大偏差,比如乘法缩放导致小数是8位、整数是4位,便是4.8。
乘法往后精度缩放到8.16,依然在浮点精度范围内,实际的深度测试是通过Visibility buffer高位的30位的深度,利用一些原子化的指令,比如InterlockedMax实现了光栅化。大家感兴趣可以去看看Rasterizer.ush里面有Write Pixel去做了,实在我们为了并行地实行软件光栅化和硬件光栅化,终极硬件光栅化也依然是用这个Write Pixel去写的。
Nanite中的材质处理
有了Visibility buffer后,我们实际的材质pass会为每种材质绘制一个draw call,这里我们在每个cluster用了32位的材质信息去储存,有两种编码办法共享这32位,每个三角面都有自己对应的材质索引,支持最多每个工具有64种材质,以是须要6位去编码。
普通的编码办法一共有两种,一种是fast path直接编码,这时只要每个cluster用的材质不超过三种就可以,比如每一种64个材质,我须要用6位来表示索引是第几位,用掉3X6=18位还剩下14位,刚好每7位分别存第一,和第二种材质索引的三角面片数的范围,由于7位可以存cluster 128个面, 这是最大范围了。
前几个面索引用第一种,剩下的范围用第二种,再多出来的便是第三种。当一个cluster超过3种材质时,我们会用一种间接的slow path,高7位本来存第一种材质,三角面片的范围的那7位,我们现在padding 0 剩余个中19位存到一个全局的,材质范围表的Buffer Index,还有6位存Buffer Length,Slow path会间接访问全局的GPU上的材质范围表,每个三角面在表里面顺着entry找自己在哪一组范围内。
这个构造里存有两个8位三角面index开始和结束,6位(64种)材质index,实在这种办法也很快。大家想一下,实在我们大部分材质、模型,就算用满64个材质,我切成小小的cluster往后,128个面里你切了好多section,超过三种材质的可能性实在很低。
这里可以看到不同的绘制工具,它在Material Index表里面实在顺序是不一样的,我们须要重新统一映射材质ID,也能帮助合并同样材质的shading打算开销。
在处理Nanite的mesh pass时,我们会对每一种material ID做一个screen quad的绘制,这个绘制只写一个“材质深度”,我们用24位存“材质深度”可表示几百万种材质,肯定是够了。每一种材质有一个材质深度平面,我们利用屏幕空间的小Tile做instanced draw,用深度材质的深度平面做depth equal的剔除,来对每种材质实际输出的Gbuffer做无效像素的剔除。
那为什么要切tile做instanced draw呢?由于就算用硬件做Early Z,做了rejection,也还是会耗一些韶光的。以是如果在vs阶段,某个tile里根本没有的材质的话,就能进一步减少开销,详细可以看ExportGbuffer.usf里的FullScreenVS这里的处理。
Nanite中的串流
处理完渲染部分,我们来看看串流。由于韶光关系,我这里可能要轻微简化一下:由于资源很大,我们希望占用内存是比较固定的,有点类似VT这种观点。但是geometry比拟virtual texture有分外的challenge。
还记得之前lod选择的时候我们说过,终极结果刚好是让DAG上有一个干净的Cut,以是如果数据还没进来,这个cut就不对了,我们也不能在cluster culling时加入已有数据信息的判断,只能在runtime去patching这个实际的数据指针。
以是我们保留了所有用来culling的层级信息,让每个instance加载的时候都在GPU里面,只streaming实际用到的geometry的细节数据。这样做有很多好处——在新的工具被看到的一瞬间,我们最低一级的root那一级的cluster还是有的,我们就不用一级一级要求。
并且我有全体cluster表,以是我可以在一帧中就准确知道,我feedback时实际要用到的那些cluster实际层级的数据。全体层级信息本身是比较小的,在内存里的占用,相对来说不那么可不雅观。
回顾之前culling的过程可以知道,我们在streaming粒度最小的时候, 也是在cluster group层级的,以是我们的streaming会按照我刚刚说的cluster group来切配置。由于有些切割的边界最好是在cluster group的中间,以是我们会有一些partial group的观点,在末了让GPU发出要求。
在哪个cluster group里,我就发这个group所在的那个page。如果我是partial的切到几个page,我就会同时发这几个page的要求。加载完之后,我会重新在GPU上patch,我刚刚全体culling的算法,条件如果变成了是叶子节点,我刚刚说的偏差知足条件里还有一个并行条件——是不是叶子节点。
除了真的lod0的cluster是叶子节点,还有便是我现在没有添补patch完、没有加载进来的时候,内存里最高、最风雅的那一级是什么?也是叶子节点,总体观点便是这样的。
Nanite中的压缩
实际上,我们在硬盘里利用了通用的压缩,由于大部分的主机硬件都有LZ77这类通用的压缩格式,这种压缩一样平常都是基于重复字串的index+length编码,把长字符串和利用率高的字符串利用Huffman编码办法。
按频度来做优化的,我们实在可以重新调度。比如在我们切成cluster往后,每个cluster的index buffer是高度相似的,我们的vertex 在cluster的局部位移又很小,以是我们可以做大量的position量化,用normal八面体编码把vertex的所有index排到一起,来帮助重复字符串的编码和压缩。
实在我们每个三角形就用一个bit,表示我这个index是不是不连续下去要重新开始算,并且其余一个bit表示重新开始算的朝向的是减还是加,这样顶点数据跨culster的去重,做过这样的操作后,我们磁盘上的压缩率是非常非常高的。当然,我们还在探索进一步压缩的可能性。
Nanite的未来与其他
由于韶光关系, 借助Nanite其他的一些feature,尤其是Virtual Shadow Map,我们可以高效地通过Nanite去做多个view的渲染,并且带投shadow的光源——每个都有16k的shadowmap,自动选择每个texel投到屏幕一个pixel的精度,该当在哪个miplevel里面,并且只渲染屏幕可见像素到shadowmap,效率非常高,详细细节这里就不详细讲了。
接下来我们看看Nanite未来有什么样的操持:只管我们目前只支持了比如纯opaque的刚体几何类型,对付眇小物体,末了我们还是会用Imposter的办法来画,但是在超过90%的情形下,场景中实在都是全静态工具。
以是目前的Nanite,实在已经能处理繁芜场景的渲染,在大部分情形下都能起到非常大的浸染。至于那些不支持的情形,我们依然会走传统管线,然后整合起来。当然,这远没有达到我们的目标,我们希望往后能支持险些所有类型的几何体,让场景里不再有观点,不再须要去区分哪些工具是启用了Nanite的,包括植被、动画、地形、opaque、mask和半透。
伴随Nanite的研究,我们也希望达成一些新技能,比如核外光芒追踪,便是做到让实际ray tracing的数据,真的是Nanite已经加载进来的细节层级的数据。当然,离屏的数据可能还是proxy mesh。
其余,由于我们现在已经不支持曲面细分了,以是也希望在Nanite的根本上做微多边形的曲面细分。
Lumen
UE5的另一大功能Lumen,是全新的全动态GI和反射系统,支持在大型高细节场景中无限次反弹的漫反射GI,以及间接的高光反射,跨度可以从几公里到几厘米,一些CVar的设置乃至可以到5厘米的精度。
美术和设计师们可以用Lumen创建更加动态的场景。譬如做实时昼夜变革、开关手电筒,乃至是场景变换。比如炸开天花板后,光从洞里射进来,全体光芒和场景变革都能实时反馈。以是Lumen改进了烘焙光照带来的大量迭代韶光丢失,也不须要再处理lightmap的uv,让品质和项目迭代效率都有了很大提升。
为了跨不同尺度供应高质量GI,Lumen在不同平台上也适用不同的技能组合。但是目前Lumen还有很多功能不敷正在改进。我们先来大略理解下Lumen的大框架:为了支持高效追踪,我们除了支持RTX硬件的ray tracing,其他情形下我们也用Lumen在GPU上掩护了完全的简化场景构造,我们称之为Lumen scene。
个中部分数据是离线通过mesh烘焙天生一些赞助的信息,包括mesh SDF和mesh card,这里的card只标记这个mesh经由grid切分之后,从哪些位置去拍它的一些朝向,和Bounding Box的一些标记。
利用刚刚这些赞助信息,和Nanite的多view高效光栅化天生Gbuffer,以及后续须要用到的其他数据,运行时会通过两个层面更新LumenScene:一层是CPU上掌握新的Instance进来,或者一些合并的streaming的打算;另一层是更新的GPU数据,以及更新LumenScene注入,直接和间接Diffuse光照到光照缓存里面。
我们会基于当前屏幕空间放一些Radiance Probe,利用比较分外的手段去做主要度采样。通过高效的Trace probe得到Probe里面的光照信息,对Probe的光照信息进行编码,天生Irradiance Cache 做spatial filter。
当然,接着还会有一些fallback到global天下空间,末了再Final Gather回来,和全屏幕的bentnormal合成天生,终极全屏幕的间接光照,再在上面做一些temporal滤波。这便是我们Diffuse全体全屏的光照,末了再跟Direct光照合起来,就得到了终极的渲染结果。
Lumen中的Tracing
Lumen的整体框架是软件追踪,靠Mesh SDF来做快速的Ray Tracing。在硬件许可时,我们会用RTX,这个本日不展开讲。Lumen的追踪是个Hybrid的方案,包括优先利用HZB做屏幕空间的Trace,如果失落败的话,我们在近间隔用一个全屏做Mesh SDF的Trace,这里由于Mesh SDF的instance做遍历效率实在还比较低。
由于用bvh在GPU上访问时,树形构造的缓存同等性很不好,以是我们只在很近间隔1.8米内做第一层级的加速构造,这时我们利用一个大略的Froxel去做grid划分,快速求交所有instance的Bounding Sphere和对应cell相交结果,并存在对应cell的列表里,这是全屏做一次的。
接下来在tracing时,我每次只须要访问当前tracing点,比如marching往后所在的位置,所在的cell就能很快算出来,然后直接查询里面的instance列表,将第二层加速构造实际的,以及查出来列表里instance的SDF,都做一遍marching,取一个minimum值。
对付稍远一点的,我们会对场景做一个合并天生Global的SDF,它是个clipmap。但由于提高精度往后,数据存储等各方面每翻一倍精度会有8倍增加,我们会有一些稀疏的表达,我之后会大略讲一下。
在都没有trace到的情形下,我们会循环Global SDF的clipmap,对每一级clipmap做loop,直到Global SDF。比如二百多米全都没有trace到,那便是miss。当然,我们在之前的Demo里也用了RSM做末了的fallback,现在这个版本我们还没有放进去。
在SDF天生时,tracing我们都会做一些守旧的处理,担保不会有薄墙被穿透。SDF实在是个volumetric,按voxel间隔来采样的天生过程,如果我的面很薄,在你的voxel精度以内,实在我们会有一些守旧处理。
Lumen与场景构造
随之而来的问题是,我们trace到了某个表面之后,SDF里面没有办法拿到我们实际须要的数据,只能帮助快速找到交点位置,这个时候我们能拿到什么?近场MeshSDF时MeshId是我知道的,由于遍历列表的时候存了;其余我还知道SDF,以是可以靠SDF的gradient算出对应的normal,但是我有ID、normal和位置,要若何得到我要的Radiance呢?包括Gbuffer的一些数据,这时我们是没有三角面片数据来插值打算的,没有各种材质的属性,以是我们须要一种高效的参数化方法。
我们利用了一种平铺的CubeMapTree构造:首先在Mesh导入时我们会预先处理,刚刚提到天生一组Card的描述,在runtime的时候,我们对放在舆图里的每个实例,会根据mesh的Card信息实际利用Nanite高效光栅化,天生对应的Gbuffer。
Atlas在一张大的Atlas里面,实在是几张里面存了MRT,存了三张——包括albedo,opacity,normal,depth这样的信息。存的这个Atlas我们叫做Surface Cache,实在便是大家终极看到的LumenScene。当然,LumenScene还会经由SDF tracing,然后做tri-planar reprojection,这实在便是我们 tracing的结果。
我们tracing时tracing到哪个位置,就会找到它对应三个方向的Lumen card,把光栅化完的那些信息tri-planar reproject出来,得到的便是这个点要的信息。包括Gbuffer、Radiance信息。
Radiance信息从哪里来呢?是在天生这个card时,还会做直接的光照注入,然后天生它Irradiance的Atlas,并且这个Atlas中会根据掩护的budget更新对应的Card,从texel出发,利用GlobalSDF去trace上一帧的lighting状态,也便是上一帧LumenScene的信息。
以是我们用屏幕空间Probe去trace时,trace到的那个Irradiance cache里的东西,便是多次反弹的结果。这个Atlas里card存的cache,实在都是2的整数次幂,为了方便我们做mip。由于我们有些阶段要用prefilter的mip,利用conetracing快速地做prefiltering结果的tracing。对付更远的Ray,我们其实在trace的时候,就已经借助的GlobalSDF,超过1.8米时,这个时候我们也没有对应的MeshID了。
以是类似地,在对应天生GlobalSDF的clipmap时,我们也会用Surface Cache天生一个voxel Lighting Cache,也便是LumenScene更低精度的voxel的表达。这个voxel Scene便是来自Cube Map Tree预处理后,radiance合并天生出来的。
这时我们每一帧都会重新天生voxel Lighting Cache,全体Lumen的构造是持续存在GPU上的,在CPU上掩护对它的增减。我们哪些东西重新Streaming进来了,视角调度往后哪些card变得可见,为了掌握开销,我会每帧固定更新一定数量的card,并且根据对应的Lighting类型,对这个Surface cache做一些裁减。对付那些tracing时不在屏幕中的shadow遮挡,我们都是靠Global SDF Trace来做的。
Final Gather
有了Tracing的手段,又从中得到了想要的数据的信息后,我们就要办理终极的GI问题了。传统模式中,比如Cards里存的是Surface Cache,已经有了多次反弹的照度信息,这里我们已经把追踪到的表面缓存不一致的求解打算分离到Card Capture和Card光照打算部分,就只须要在屏幕空间直接来Trace Ray,Trace这些Surface Cache里的Irradiance就可以了。
传统做RTX GI时,每每只能支撑1-2spp在Gbuffer发出BentNormal半球空间均匀分布的光芒,如果靠SpatialTemporay,方差勾引的这种滤波,在光芒相对充足的情形下效果会非常好,但是当光芒很不充足,譬如只有一束光从门缝或小窗口照进来时,离远一点的地方你Trace出来的Ray能采样到,实际有光源的地方概率太低,导致在滤波前的画面信息实在太少,终极滤波完的品质也是非常差、不能接管的。
我们的方法,是利用远低于Gbuffer分辨率的Screen Space的Probe,约每16个像素,根据实际像素插值失落败的情形下,我们在格子里面还会进一步细化放置,放到一个Atlas里,我的每个Probe实在有8×8个Atlas,小的一个八面体投影的便是半球,自己World Space normal的半球,均匀分布我的立体角朝向的那个Tracing的方向,每一帧我还会对这个采样点做一些jitter,之后再去插值。
我们也会在像素平面,将末了全屏每个像素按照BRDF主要度采样,找周围Screen的Probe做跟我方向同等的weight调度,再去做插值,然后在打算probe的时候,我们利用半球投到八面体的办法,存了8×8的像素全都Atlas到一起,在细化时一贯往下放。
以是最坏的情形,是比如每个像素都是一个前景,下一个像素便是一个后景——这实在不太可能,只是极度情形。这种情形我就变成要细化到每个像素,又变成逐像素去做这个tracing的Probe Cache。为了避免这种情形,我们实在是粗暴地限定了全体Atlas的大小,也便是最细化的东西,我填不下就不要了。
这样的好处是,我按照1/16的精度去做的Screen Probe,实在是1/256的精度,纵然8×8我处理的像素数还是以前的1/4或者1/8,在做Spatial Filter末了每个像素插值时,我只要做Screen Probe3×3的filter,实在就相称于以前48x48的filter大小,而且效率很高。并且在求解间接的环境光蒙特卡洛积分时,可以靠上一帧这些ScreenProbe里reproject回来的Incoming Radiance的值,作为lighting的importance sampling的勾引。
同样,BRDF也可以这样做。譬如BRDF值小于0的部分,无论入射光如何都不会贡献出射,随便这个方向上lighting在上一帧的incoming radiance。在这个点上有多少,这个朝向有光过来,我贡献也是0——我不须要它,以是我终极就把这两个东西乘到一起,作为我新的这一帧probe的importance sampling的方向。
末了,我就会根据这个方向去tracing,之后radiance会存到跟它对应起来其余一张8×8的图里,Atlas到一起。对付小而亮的部分离的表面越远,每帧又有jitter又有方向,勾引方向不一样。有时没追踪到,它的噪点就会比较多,并且trace长度越长光芒的同等性也不好,以是相反离得远的光源,相对贡献得光照变革频率也比较低。由于我离的很远往后局部光有一些位移,对我这里的影响是很小的。
以是我们可以用一个天下空间的probe来处理,由于这个时候可以做大量的cache,这里我的天下空间也是一个clipmap,它也是稀疏存储的。由于只有我Screen Space的Probe Tracing访问不到的东西,我才会去支配更多的World Space的Probe去做更新处理,这里就不展开讲了。
终极,我们须要在全分辨率的情形下做积分,这时有一个办法,便是根据全分辨率像素得到BRDF采样,方法便是我刚才说的,从Screen Probe里面找。比如8×8像素周围的都去找跟它方向同等的weight去插值,但这样噪点还是很多,以是我们实在是从它的mip里面去预处理,从filter过的结果里去找。
这样还会有一个问题:我自己朝向的平面,比如8×8像素周围的都去找跟它方向同等的weight去插值,以是终极我们把八面体的radiance转成了三阶球谐,这样全分辨率的时候能非常高效的利用球谐系数做漫反射积分,这样的结果质量和效率都很好。
末了的末了我们又做了一次,我对每个像素都做完之后,再做一次temporal的滤波,但是会根据像素追踪到的位置的速率和深度来决定我这个像素的变革,是不是快速移动物体区域投影过来的,来决定我这个temporal filter的强度。
我temporal filter越弱,实在就相称于前面我去采样的时候积分起来的时候,我采样周围3×3 Spatial Filter效果就越强。整体上Lumen的框架便是这样,我略过了大量细节和一些分外处理的部分。譬如半透明物体的GI没有讲到,Spectular我也没有分外讲,但是像spectular在粗糙度0.3到1的情形下,和这里importance sampling的diffuse实在是同等的。
Lumen的未来
在未来,我们也希望能做进一步改进,比如镜面反射,Glossy反射我们已经能很好处理,但是镜面反射在不用硬件追踪的情形下,现在Lumen效果还是不足的,包括SkeletalMesh的场景表达办法、破碎物体的场景表达办法,以及更好处理非模块化的全体物体。由于现在模块化整体captured card或者SDF的各种精度处理,可能还不足完善。
我们希望提升植被品质,以及更快速地支持光照变革,由于我们有很多hard limiter的更新,比如card数量之类的,会导致你过快更新时跟不上。末了,我们还希望能支持更大的天下,譬如可以串流SDF数据,以及做GPU driven的Surface Cache。关于Lumen我们本日就先讲到这里。
其他功能与Q&A
讲完两大招牌功能,我们快速过一下别的功能:比如最常被大家提到的大天下支持。从UE5开始我们有了更好的工具,比如World Partition就升级成了全新的数据组织办法,合营一套streaming系统,我们不须要手动处理runtime的streaming,引擎会帮你自动切分出不同的Partition,自动处理加载策略。
而且在这个根本上,我们又有Data Layer对付不同逻辑的处理,有World Partition Stream Policy根据layer对不同的Policy的定制,有Level Instance——可以把Level算作Actor、嵌套组成模板、模块化搭建舆图,并且在Level Instance层级上设置Hlod的参数。
为了协同事情,我们还引入了One File Per Actor,大家每次在舆图上编辑或新增时,实在只改到了一个独立的actor所对应的文件,文件锁的粒度比较细,就不会去动全体舆图文件,这样引擎也会自动帮你管理这些散文件的changelist天生。
末了,我们还做了大天下的精度支持,把全体Transform的各种打算都改到了双精度浮点支持。其余,我们在Mobile上也做了更多支持,比如Turnkey全新的打包事情流程,移动端延迟渲染也进入了beta阶段。
除此之外,iOS我们也做了很多改进,在正式版本我们新增了opengles延迟渲染管线的支持,比如mali上的pixel local storage。同时我们也加入了DFShadow支持,以及一些新的shading model:例如和pc统一利用Burley SSS参数驱动的移动版本的preintegrated皮肤。
同时我们终于对DXC下的半精度做了支持,而且把所有的Metal Vulkan openGLES都用DXC做了转换。同时我们还加入了point light shadow、CSM cache和带宽优化过的565的RVT,做了全新的 gpu instance culling和更高效的auto-instancing等功能。
对付其他的各种平台,我们Unreal Turnkey事情流是跨所有平台开拓打包发布的流程。目标是一键就把项目发布到任意支持的平台的全新设备上,自动完成所有工具链和SDK的安装支配,从4.25后面版本开始我们已经支持下一代主机了,伴随接下来《堡垒之夜》在这些平台上的优化和迁移,UE5上我们对这些支持会更完善一些。
在这些主机平台上我们对体积云、物理场等,包括加载效率 TSR等都做了大量的优化。也有大量内存优化(尤其DX12,buddy allocator改pooling)。rdg insights供应了对RHI层的每帧中间渲染资源的复用的这种profiling编辑器。
编辑器我们改进了用户体验,用了比较深色的主题的统一配色,大家可以自己去灵巧配置。扩大了视窗的利用率,集成Quixel Bridge能方便的浏览bridge库中的资产并直接拖到场景中。
其余我们也加入了Game Feature plugin,是一个全新的数据驱动的框架,类似之前的游戏框架,现在可以干净的分离到gamefeature插件里方便的激活和卸载,做Game Features和模块化的Gameplay,你可以把各种类、数据、内容以及对应的debug/cheat干系的内容封装到不同的插件模块里面,可以针对性的通过component来在特定的Actor上启用,这样 gameplay模块的扩展性、重用性、稳定性都能有很好的改进。
MetaSounds也是在EA版本就发布的, 是让音频系统有跟渲染管线类似的可编程性的一套工具。类似audio用的shader,以是可以更灵巧的利用和操纵原始的音频数据。MetaSounds用来更换以前比较简陋的soundcue,可以更高效、更灵巧、更可扩展,Quartz 是暴露给bp系统的一套用来精确采样音频数据和事宜的系统,可以让你完善地完成类似吉他英雄这种的须要非常准确的帧韶光的采样音效、同步掌握的这样一套系统。
其余我们还有别的一套系统(Audio Synesthesia),可以帮助你离线也可以实时对音频做剖析,然后把音频做可视化。比如利用niagara或者材质做可视化。
chaos的物理系统,在UE5开始我们全体物理系统都会切换到chaos,它的一大目标是能有更多的gameplay的交互接口,跟gameplay系统结合更紧密,并且支持网络同步。Early Access版本中我们Rigid body仿照已经修了很多bug,减少了碰撞对的天生、预先做了更多剔除、修复了一些查询巨大三角面时的缺点结果、改进了超高速移动物体或很低帧数下默认启用CCD时不正常的效果、也改了ragdoll仿照稳定性的一些bug。
在正式版本中,对付PBD的性能和稳定性做了更多优化,做了很好的载具的支持,并且支持网络同步的物理仿照的框架。然后还有各种分外的solver,比如布料和流体的仿照也得到了增强和改进。
关于整套Unreal Insights工具,我们改进了现有的Timing, Loading, Network和Animation 的功能,同时新版本里面还有Memory Insights。通过Memory Insights,我们能按照LLM的办法按类型来看内存是怎么分配的,然后我们如果加Memory Malloc这样一个参数的话,Memory Insights还可以帮助大家看到所有的调用、堆栈、分类 乃至查Memory Leak。有很多这种好用的工具,刚刚也说到RDG Insights,来看全体渲染的管线资源分配、复用、生命周期各种各样比较好用的功能。
末了是Meta Human Creator,高质量的数字人创建工具,可以帮助小团队在几分钟或者几小时内就创作出3A品质的、带完全rigging,可以用来做动画的数字角色。伴随UE5的发布,我们MHC的创建角色在引擎里面会有更多更好的表现。这样品质的数字角色本来只在3A作品里能看到,现在每个人都能制作,并且通过bridge直接在content browser拓出来,在引擎里面利用。
Q & A
UE5.0正式版会在什么时候发布?
王祢:目前估量是明年上半年,可能在4月份旁边发布。
UE5.0之后还会支持曲面细分吗?
王祢:由于不少硬件平台曲面细分效率的问题,我们打算彻底去掉。未来我们会考试测验用Nanite去做,但是目前还没有做到。以是现在的workaround如果不做变形,那就只能靠Nanitemesh或者靠Virtual Heightfield Mesh来处理。