译:UE4是如何渲染一帧的(2)

转载于:https://zhuanlan.zhihu.com/p/33868831

原文链接:How Unreal Renders a Frame part 2

作者:Kostas Anagnostou, Lead Graphics Programmer at Radiant Worlds

 

我们继续探索UE4是如何渲染一帧的,重点研究光源网格(light grid)的生成,G-prepass以及光照部分。

光源分配(Light assignment)

接下来,渲染器会使用一个compute shader来将光源分配给3D网格(ComputeLightGrid pass),这一步和clustered shading类似。这个光源网格可以根据物体的位置快速获取影响物体表面的光源。

如这个pass的名字所示,这个相机空间(view space)的light grid大小为29x15x32。对应到屏幕空间上每个块的大小为64x64像素,在z轴上划分为32段。这意味着在X-Y维上的光源网格数量取决于屏幕分辨率。另外从名字上来看我们放了9个光源以及2个环境反射探针(reflection probe)。一个环境反射探针是一个“实体”,它保存了探针的位置和半径来捕捉四周环境,用以计算物体表面的反射效果。

根据compute shader的源码(LightGridInjection.usf),Z-轴上的网格划分是呈指数分布的,意味着在相机空间中每一个光源网格块在Z-轴上的长度会随着与相机距离的增大而变大。不过每个光源网格通过一个轴对齐的网格来进行光源包围盒的求交测试。UE4使用一个链表来保存光源索引,这个链表随后会在Compact pass中被转化为一个连续的数组。

光源网格随后会被用于体积雾的渲染、环境反射的渲染以及透明物体的渲染流程中。

我注意到的另一个有意思的地方是,CullLights pass在开始时会清空保存光源数据的Unordered Access Views,但对于所有3个UAV只有其中的两个会使用ClearUnorderedAccessViewUint函数来清理。对于另一个UAV,UE4使用一个compute shader来手动清除数据(上面的draw call列表中的第一个Dispatch操作)。根据源码,对于超过1024bytes的缓冲,UE4倾向于在compute shader中手动清理而不是使用API调用。

 

体积雾(Volumetric Fog)

接下来是体积雾的相关计算,同样也是用compute shader。

这个render pass的作用是计算透明度(transmittance)以及光照散射,并将结果保存在一个体纹理中,使得我们可以只通过物体表面位置直接计算出光照散射结果。和之前的光源分配这一步一样,这个体纹理的形状和视锥体是对应的,每一个tile大小为8x8个像素,由128个depth slice组成。depth slice的位置以指数形式分布。近裁面会被向前移动一点位置,保证在相机前不会过于密集地分布大量小的网格(和Avalanche工作室的clustered shading系统类似)。

 

对于体积雾的渲染,UE4采样的方法类似刺客信条4寒霜引擎所采用的方法,体积雾通过3步计算:

  • 第一步(InitializeVolumeAttributes)计算并将体积雾的参数(散射和吸收系数)存储进体纹理,此外自发光材质的全局自放光参数会被保存进另一个体纹理中。
  • 第二步(LightScattering),对于每一个网格,计算每一个可投影的平行光、天光和本地光源的光照散射和吸收,将结果在之前提到的ComputeLightGrid这个render pass赋给光源体纹理。这一步还会对于compute shader的输出结果(光源散射,吸收)执行Temporal AA操作,使用一个历史缓冲(history buffer)完成,这个历史缓冲也是一个体纹理。这一步能改善每一个网格的光照质量。
  • 第三步(FinalIntegration),这一步简单地在3D纹理中沿着Z轴做raymarch,累计散射的光线和透明度,并将结果保存于网格中。

最终的体纹理缓冲中的光照结果如下图所示。我们可以在这里看到由于平行光和本地光源在雾气中散射而产生的光束效果。

 

G-Prepass

接下来是UE4版本的G-Prepass,一般用于延迟渲染架构中。这一步的作用是在一系列渲染目标中缓存材质属性,以降低昂贵的光照与着色计算所带来的开销。

在这一步中,所有物体(静态,动态的等等)以传统方式渲染。在UE4中,首先在这一步渲染的是天空球!大部分情况下这是个比较糟糕的做法,因为天空球在之后会被其他更接近摄像机的物体覆盖,浪费掉很多被绘制的像素。在UE4中这个做法不会有太大的影响,因为在渲染器在先前执行的Z-prepass一步会避免大部分对于天空盒部分的重绘(overdraw)操作(以及所有的其他重绘操作)。

接下来是在这一步中保存输出结果的渲染目标列表。

深度缓冲只被用来进行深度测试,由于深度缓冲在z-prepass中已经被填充完毕了,所以在G-prepass中不会对深度缓冲做任何修改。但渲染器会写入模板缓冲(stencil buffer),用来标记被绘制的像素属于哪个物体。

G-buffer的内容会随着渲染器的设置而改变。举个例子,如果渲染器要将物体的速度值写入g-buffer,这些数据会占据GBufferD,原来保存的数据会被移到其他地方。对于我们目前的测试场景以及渲染设置,g-buffer以如下形式分布:

SceneColorDeferred:包含间接照明的数据

GBufferA:世界坐标下的法向量,以RGB10A2_UNORM的格式存储。似乎没有使用任何形式的编码(译者注:也就是直接保存了法向量的三个坐标,很多游戏/引擎会使用一些编码方法来改善法线的存储精度、所占空间等,例如Crytek的best fit normal)

Distortion:一系列材质属性(金属度,粗糙度,高光强度以及材质所使用的光照模型索引)

GBufferC:RGB通道中保存了反照率(Albedo),alpha通道中保存了环境光遮蔽(AO)系数

GBufferE:由光照模型所决定的自定义数据(例如表面下散射的颜色,切空间向量等等)

GBufferD:预烘焙的阴影值

Stencil:标记像素属于哪个物体

值得注意的是场景中所有的静态物体(solid props,不含可移动的石头以及天空球)从三个缓存辐射照度(irrandiance),阴影和表面法向量的带mipmap的纹理集(atlas)中采样光照信息。

 

再看粒子模拟

粒子模拟是在每一帧中最先执行的计算操作,这一步会输出每个粒子的世界坐标与速度。由于这一步在每帧中执行的时间太早,渲染器在那时没有办法访问深度与法向量缓冲,因而没有办法在GPU上进行碰撞检测,现在我们可以对那些需要碰撞检测的粒子效果再跑一遍粒子模拟。

 

速度值渲染(Velocity rendering)

在默认情况下渲染器会将运行物体的速度输出到一个R16G16的缓冲上。速度信息会被用在动态模糊的计算以及一切需要重投影操作(reprojection)的特效上(例如Temporal AA)。在目前的场景中只有那块石头是被标记为可移动的,所以只有它被渲染到了速度缓冲上。

 

环境光遮蔽

现在我们已经拿到了所有的材质信息,渲染器将准备执行光照阶段。在这之前,渲染器需要首先计算屏幕空间环境光遮蔽(SSAO)。

我们的场景中没有延迟贴花(deferred decal),我猜测假设测试场景中存在贴花的话,目前是空着的DeferredDecal pass会修改在G-buffer中的材质属性。SSAO通过两步计算完成,一步在1/4分辨率下完成,一步在全分辨率下完成。AmbientOcclusionPS 908×488这个render pass使用1/4分辨率的法向量缓冲(由AmbientOcclusionSetup pass输出),先前得到的Hi-Z缓冲,以及一个用于采样深度/法向量的随机向量纹理来计算AO。此外,shader每次采样随机纹理时会抖动(jitter)采样点,以模拟超级采样(supersampling)的效果,随着渲染的进行改善AO质量。

随后,AmbientOcclusionPS 1815×976这个渲染pass会以更高的分辨率计算全分辨率的AO效果,并将之与之前的1/4分辨率AO结合。UE4的SSAO得到的结果相当好,这个方法免去了使用额外的pass来模糊降噪(blur)。

最终,一个全分辨率的AO缓冲中保存的结果会被加到SceneColourDeferred缓冲(G-buffer阶段得到),这个缓冲目前保存间接(环境)照明结果。

光照

在我们讨论光照之前,有必要偏个题简单讲讲UE4是怎么计算透明物体光照的,因为我们在之后会反复碰到这套系统。UE4对于透明物体光照计算的方法是将光照注入到两个64x64x64,格式为RGBA16_FLOAT的体纹理中。这两个材质保存两类信息,一是体纹理中每个像素代表的网格所获得的光照信息(带阴影+衰减)的球谐函数表示(TranslucentVolumeX纹理),另一个是每个光源近似的照明方向(TranslucentVolumeDirX纹理)。渲染器维护两组这样的纹理(译者注:也就是两个cascade,共4个纹理),一个用于靠近摄像机的透明物体,这部分需要更高精度的光照,另一个用于远处的透明物体,这部分物体对光照精度要求不高。这个做法和级联阴影(cascaded shadowmap)的思想很像。

下面是用于靠近相机部分的两个用于透明物体渲染的体纹理,光照只含一个会投影的平行光。

这个透明物体光照体纹理(译者注:translucency lighting volume,翻成中文比较拗口…)不会用于不透明的物体,只会在之后被用来计算透明物体的光照以及特效(例如粒子)。这些纹理将在光照这一步被填充。

回到对于不透明物体的直接光照,现在渲染器将计算光照结果并将其赋给场景。draw call列表会非常长,具体长度取决于光源数量。我只把最相关的部分展开来。

所有光源被划分成两组处理,不投影的光源(NonShadowedLights)和投影光源(ShadowedLights)。不投影光源包含简单光源(simple lights),如那些用于粒子特效的,以及不投影的普通场景光源(normal scene light)。这两者间的区别之一是普通场景光源在渲染时会用光源的包围盒和深度信息做求交测试,以防止对光源包围盒之外的物体进行光照计算。这一步是通过驱动对应扩展激活的。光照结果会被累积到之前提到的SceneColourDeferred这个缓冲上。另一个区别是简单光源似乎完全不会将其光照信息写入用于透明物体光照的体纹理中(不过渲染器中似乎有允许这么做的地方,可能在某个地方有对应的选项开启这个功能)。

有趣的是,只有当场景中不投影(以及非static类型)的可见光源数量超过80的时候,渲染器才会切换到tiled deferred lighting方法,低于这个值的时候使用传统延迟渲染方法。(译者注:所谓传统延迟渲染就是指每个光源一个pass,80这个值可以通过控制台参数设置)

在使用tile deferred shading方法时,UE4使用一个computer shader来计算光照,光照信息通过一个constant buffer传入shader。此外,似乎只有直接光照计算部分会转换到tiled deferred lighting,使用compute shader在一个pass中计算光照。InjectNonShadowedTranscluscentLighting这一步仍然以每个光源一个draw call的方式将光照注入到透明物体光照体纹理中。

ShadowedLights这个render pass处理所有投影光源、静态光源(stationary)和移动光源的光照。默认情况下,UE4对于每一个投影光源分3步处理:

首先,计算屏幕空间阴影(ShadowProjectionOnOpaque),随后将光照贡献注入到透明物体光照体纹理中(InjectTranslucentVolume)并在最后计算场景照明(StandardDeferredLighting)。

对于平行光,由于我们测试场景的缘故只有Split0含有阴影信息,这一点之前讨论过。阴影计算的结果会被写入一个格式为RGBA8、大小与屏幕分辨率一致的缓冲中。

下一步(InjectTranslucentVolume)将平行光的光照贡献写入全部两组透明物体光照体纹理中(每个pass一个draw call)。最终,StandardDeferredLighting这一步根据屏幕空间阴影缓冲的遮罩值,计算并将光照结果写入SceneColorDeferred缓冲中。

本地光源似乎使用差不多的方法,将阴影投影到屏幕空间缓冲上,将光照注入透明物体光照体纹理并将光照结果写入SceneColorDeferred缓冲中。

在这一步两种类型的本地光源都用差不多的方法处理,有一个区别是移动光源会将带投影信息的光照结果注入透明物体光照体纹理中,另外移动光源使用立方体阴影而不是逐物体的阴影贴图集。

所有光源使用同一个屏幕空间阴影渲染目标,每当需要光照阴影信息时会清理缓冲中的对应部分,我猜测这样做的原因是为了节约一些内存。

在lighting pass结束时,SceneColorDeferred会保存有所有光源的直接光照累积结果。

(译者注:由于光照发生在线性空间,目前还没有做任何tonemap/color grading操作,所以上图得到的光照结果看上去会比较怪)

值得注意的是尽管渲染器会在之前输出一个lighting binning/clustering的数据结构(Light Assignment pass),这个数据结构在不透明物体的光照计算中完全没有用到,UE4倾向于使用更加传统的延迟渲染方法,也就是对每个光源用一个独立的pass计算。(译者注:由于作者使用的测试场景中放置的光源数量太少,所以UE4没有切换到tiled deferred shading方法,修正后这段没有删去)

最终两个透明物体光照体纹理在被用于计算透明物体/特效光照时会被过滤一遍,以减轻走样(aliasing)。

下一篇将是该系列的最后一篇文章,我们将研究UE4渲染器的图像空间光照(image space lighting),透明物体渲染以及后处理流程,以此结束我们对UE4渲染部分的探索。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值