17

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

 3 years ago
source link: https://zhuanlan.zhihu.com/p/118971518
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

DeNA中国 CTO

Charlie同学翻译了两篇以后,就没再进一步翻译,我来把第三篇补全吧,也算是狗尾续貂!


原文链接:

https://interplayoflight.wordpress.com/2017/10/25/how-unreal-renders-a-frame-part-3/​interplayoflight.wordpress.com

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

这是“UE如何渲染一帧”系列的第3部分,您也可以访问第1部分和第2部分。

在这篇博客文章中,我们将通过图像空间照明,透明度渲染和后期处理来结束对虚幻渲染器的探索。

图像空间照明(Image Space Lighting-IBL)

接下来,屏幕空间的反射会在全屏进行计算。(RBGA16_FLOAT rendertarget format).

v2-a49c2abde40a0c872d632bf45f628ff6_720w.jpg

着色器还使用在帧开始时计算出的Hi-Z缓冲区,通过根据表面粗糙度在raymarching过程中选择Hi-Z mip来加快相交计算速度(比如,对于没有反射细节的较粗糙表面,使光线跟踪更粗糙)。 最终,每一帧的渲染过程中,光线开始位置的都会发生抖动,这与时间抗锯齿功能(Temporal antialiasing)相结合,可以提高反射图像的质量。

v2-6fbe1837d4a68879bf0433fcf85b01d9_720w.jpg

在raymarching过程中,如果命中被确认的话,着色器会使用前一帧的渲染目标来采样颜色,您可以从反射中的体积雾以及反射的透明道具(雕像)中看到这一点。 您还可以在右侧的椅子下看到粒子效果的提示。 由于我们缺乏透明表面的适当深度(以计算正确的命中率),因此反射通常会被拉伸,但在很多情况下效果是令人满意的。

使用计算着色器(ReflectionEnvironment pass)将屏幕空间反射应用于主渲染目标。 该着色器还应用场景中两个反射探针捕获的环境反射,反射信息存储在每个探针的mipmapped立方体贴图中:

环境反射探针是在游戏启动过程中生成的,而且,它们只针对”静态“几何体来进行环境反射信息的计算。(在上图中,大家可以注意到具有动画效果的石头道具是不存在的。)

在我们的场景中,就具备了SSR和环境反射效果了。现在看起来像这个样子:

雾与大气效果

接下来,我们看一下雾和大气效果。同样的,如果你在你的场景中开启了这两项支持的话。

首先,创建四分之一分辨率LightShaft遮挡遮罩,该遮罩指定将接收LightShaft的像素(仅适用于此场景中的定向光)。

(译者注:Light Shafts can be generated by directional lights to simulate the real world effect of crepuscular rays, or atmospheric shadowing of atmospheric in-scattering. These rays add depth and realism to any scene. Light Shafts可以通过方向光来生成,以模拟现实世界中的裂缝射线或大气散射的大气阴影。 这些光线为任何场景增加了深度和真实感。这个单词不知道咋翻译。)

然后,renderer继续使用时间抗锯齿(Temporal Antialiasing)来改善遮罩的质量,并对其应用3次blur pass,以生成此遮罩(我必须着重支出,它主要是白色的):

从这个GPU捕获中,既然最终结果的分辨率非常低,我不太清楚为什么在模糊蒙版之前将时间抗锯齿(Temporal Antialiasing)应用于遮罩。 在不同环境下可能需要更多用例来阐明这一点。

在将雾气和光轴应用于场景之前,渲染器需要花费一些时间才能将大气应用于主要渲染目标(完整分辨率)。

这看起来像是使用预先计算的透射率,辐照度和散射度来进行一次全面的散射计算,类似于Bruneton的方案

不幸的是,这是一个室内场景,模拟效果并不十分明显。

最后,渲染器(renderer)应用指数雾和LightShafts到场景中。效果如下:

着色器使用在几各pass生成的“体积雾”(Volumetric Fog)体积纹理,并使用实体几何位置进行采样。 它还应用上面计算的LightShafts遮罩。

透明物体的渲染

在将雾效应用到不透明道具之后,渲染器(renderer)来处理半透明的几何体和效果;

在这个场景中,我添加了2个玻璃雕塑,这两个雕塑会首先被渲染出来。渲染的方式就是普通的alpha混合渲染模式。

这两个透明道具在场景中的位置非常好,受到局部点光源和方向光,环境反射,雾等的影响。默认情况下,渲染器使用高质量的着色器渲染透明道具,并采样了预先模拟的大气模拟纹理,烘焙的光照贴图数据,包含来自方向光和局部光照的半透明光照量以及反射探针立方体贴图,并使用它们来计算光照。 虽然我没有看到着色器读取体积雾体积纹理,但似乎只计算基于高度/距离的雾,也许我错过了某个地方的设置。 像大气散射一样,距离雾在顶点着色器中进行了计算。

针对粒子效果,渲染器会将其渲染到另外一个单独的渲染目标上(全屏分辨率)。

和透明道具类似,大气散射和雾效是在顶点着色器中计算的。另外,基于粒子系统本身的设置,渲染器是可以使用透明光照体来照亮这些粒子的(在像素着色器中,我注意到了这一点。)。

在结束透明物体渲染之前,渲染器执行了另外一个pass来计算折射。

再次渲染透明的道具和粒子(设置需要考虑折射的部分),以构建出具有失真矢量(Distorion Vector)的全分辨率缓冲区,该失真矢量随后将用于计算折射(我对图像进行了增强以使矢量更加可见)。 模板缓冲区在此过程中也处于活动状态,以标记需要折射的像素。

在折射计算的pass(DistortionApply)中,渲染器会读取主渲染目标到现在为止的像素内容以及失真向量的数据,并生成一个奇怪的折射贴图。

因为模板缓冲区已经被激活来制定哪些像素会接收折射信息,渲染器不需要主动地清理这张贴图。

最后的折射pass只是使用上面已经被写入信息的模板缓冲区的状态下,简单地将折射贴图混合在主渲染目标上。

你可能注意到右边的椅子上折射效果,这主要是因为我们还没应用上去的例子效果造成的。对于透明道具来说,折射效果的绘制就在透明物体绘制后。

下一个Pass(BokehDOFRecombine),最终将粒子效果应用到场景中。这就是一个简单的着色器效果。相对于这么复杂的pass名称来说,这个着色器并没有特别复杂。(也许是有一些特别的渲染设置)

后处理(Post Processing)

最后一部分包含了一些后处理的pass,我们来简要地介绍一下。

使用当前场景配置,渲染器将时间抗锯齿(Temporal Antialising),运动模糊(Motion Blur),自动曝光(Auto Exposure)计算,光晕(Bloom)和色调映射(ToneMapping)应用于主要渲染目标。

Unreal的时间抗锯齿功能使用历史记录缓冲区随着时间的推移累积采样,并在两个pass中进行渲染。第一个pass使用主渲染目标,历史缓冲区和速度缓冲区对未投影的像素(在这种情况下为某些粒子)实现时间抗锯齿,以进行重新投影:

然后,就是一个类似的时间抗锯齿pass来针对这些被模板缓冲标记的像素来生成最终的抗锯齿图像。

这两个时间抗锯齿pass的区别是,在第一个pass使用一个混合参数(feedback)来混合历史缓冲区和当前的主渲染目标,这个参数是可变的,而且,可能是依赖于像素的明度信息,距离信息,renderer提供的信息等等。第二个pass使用一个固定的混合参数(0.25),这意味着,最终的抗锯齿后的像素信息主要是包含当前的采样器信息。我认为这样做是为了减少没有速度信息的快速移动粒子的重影。

接下来是运动模糊效果,通过速度平滑和扩张的pass。

在这种情况下,运动模糊的效果不是很明显,因为在当前场景中,相机是静态的,而我们具有速度的唯一运动的物体是这个岩石(由于运动和时间抗锯齿,它已经略微模糊了)。

为了实现自动曝光(眼睛适应),渲染器使用计算着色器创建当前场景亮度的直方图。 直方图按照像素强度来划分强度区间,并计算每个强度区间内有多少像素。

这种方法的优点是,我们可以轻松跳过图像中具有非常暗或非常亮的值的区域,并产生更合理的平均场景亮度近似值。 然后,使用该平均亮度,渲染器可以通过相应地调整曝光来计算眼睛适应性(明亮的图像将导致较低的曝光,较暗的图像将导致较高的曝光)。

使用高斯滤波进行了一系列的缩小处理,然后进行了一系列的放大和合并以实现布隆效应(图像经过调整以使其更可见,而没有曝光控制)。

PostProcessCombineLUTs Pass使用几何着色器和一个好长好长的像素着色器来生成一个颜色映射的查找表(一个32x32x32 RGB10A2的体积纹理)来用于色调映射。

在这一帧中的最后一个pass,Tonemapper,结合上面针对主渲染目标计算的Bloom效果,使用前面计算的眼睛适应信息,以及创建的颜色查找表,一起来调整整个图像的曝光度,从而生成最终的像素颜色。

结束语

我必须强调,这只是通过渲染器的一条路径,许多参数和设置可能会影响它,而我们实际上只是在进行一次非常表面的尝试。

总体而言,这是一个有趣的练习,尽管在一帧中完成了大量的工作,结果却比我想要的更多的是渲染器“做什么”而不是“如何”,并且我留下了很多未探索的东西喜欢重访。

虚幻引擎的渲染器源代码没有得到广泛的文档记录,但是它很干净而且易于理解,并且通过跟踪调用列表,可以轻松找到与其对应的代码。但是,只是通过研究源代码,很难跟踪着色器在许多情况下的工作,因为它广泛使用了条件编译。如果存在一些经过处理的“可编译”着色器专门化(名称已注入到drawcall列表中)的中间缓存以进行检查和性能分析,那将是很好的。

默认情况下,虚幻引擎的渲染器似乎着重于生成高质量图像。它尽可能地依赖数据(环境,光线,体积等)的烘焙,并使用时间抗锯齿功能来极大地改善图像质量。

如果您在场景中有很多道具并且没有很多遮挡机会(例如,许多大型遮挡物),则值得留意遮挡pass的成本。同样,透明道具和粒子上的折射也会迫使它们渲染两次。最后,许多固定或可移动的局部光在照明pass中可能会受到影响,因为它们是单独渲染的(并增加了透明度和体积的光注入pass成本)。

结束语,对Baldurk优秀的RenderDoc以及对Epic的虚幻源代码供所有人使用,学习和学习表示敬意。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK