Unity可编程渲染管线系列(十二)图像质量(MSAA和HDR)

蹲街弑〆低调 提交于 2020-08-14 20:09:58

目录

1 渲染比例

1.1 向下缩放

1.2 渲染到缩放后的纹理

1.3 向上缩放

2 MSAA

2.1 配置

2.2 多采样渲染纹理

2.3 解析纹理贴图

2.4 无深度解析

2.5 Depth-Only 通道

3 HDR

3.1 配置

3.2 纹理格式

3.3 色调映射

3.4 莱因哈德(Reinhard)

3.5 修改Reinhard

本文重点:

1、调整渲染比例

2、支持MSAA

3、启用HDR,带有可选的色调映射

这是涵盖Unity的可脚本化渲染管道的教程系列的第12部分。它涉及通过调整渲染比例,应用MSAA以及与色调映射结合使用HDR缓冲区进行渲染来提高图像质量。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。“原创”标识意为原创翻译而非原创教程。

本教程使用Unity 2018.4.6f1制作。

(HDR,MSAA和渲染比例一起使用)


1 渲染比例


要渲染的图像的宽度和高度由相机确定,这不受管道的控制。但是我们可以在渲染到摄像机目标之前做任何我们想做的事情。如果渲染为中间的纹理,可以提供我们想要的任何大小。例如,我们可以将所有内容渲染为较小的纹理,然后对相机目标进行最后的blit处理以将其缩放到所需大小。这会降低图像质量,但由于要处理的片段较少,因此加快了渲染速度。Lightweight/Universal管道具有“Render Scale”选项来支持此功能,因此我们也将其添加到我们自己的管道中。


1.1 向下缩放


将渲染比例的滑块添加到MyPipelineAsset,初始范围为?~1。将分辨率降低到四分之一会使质量下降很多(像素数除以16),并且除非原始分辨率很高,否则很可能无法接受。

(Render scale 滑块设置为最小值)

将渲染比例传递给管道实例。

并让MyPipeline跟踪它。

在“渲染”(Render)中渲染摄影机时,请确定在创建渲染纹理之前是否使用缩放渲染,以防出现活动堆栈。缩小渲染比例后,我们将使用缩放渲染,但是仅对游戏窗口摄影机使用,因此场景,预览和其他摄影机不会受到影响。使用布尔变量跟踪此决策,让我们可以参照。

还要跟踪变量中的渲染宽度和高度。它们默认情况下由相机确定,但是在使用缩放渲染时必须进行调整。


1.2 渲染到缩放后的纹理


现在,当使用缩放渲染或后处理时,我们必须渲染为中间纹理。还要使用布尔值对此进行跟踪,并在获取纹理时使用调整后的宽度和高度。

从现在开始,当调用RenderAfterOpaque时,必须将调整后的宽度和高度传递到活动堆栈。

RenderAfterTransparent也是如此。现在,我们必须始终在渲染纹理时释放纹理,而仅在使用堆栈时才调用RenderAfterTransparent。如果不是,我们可以使用常规blit将缩放后的纹理复制到相机的目标。

(渲染比例1、0.75、0.5和0.25;放大且无后处理)


调整渲染比例会影响管道渲染的所有内容,但阴影除外,因为阴影具有自己的大小。有时候偶然地,稍微减小渲染比例似乎会带来一些抗锯齿效果。但是进一步减少可以清楚地看出,这只是细节损失导致的,当blit到最终渲染目标时,会由于双线性插值而模糊不清。


渲染比例如何与双线性插值交互的?

0.5的渲染比例是最简单的:最终,每块2×2目标像素只有一个像素。每个最终像素使用相同的四个权重进行插值,但是有四个可能的方向。


(Render scale 0.5 filtering)


其他渲染比例尺会生成具有不同权重配置的像素,因为从源像素到目标像素的距离会根据比例尺以规则模式变化。

(Render scale 0.75 filtering)


1.3 向上缩放


我们可以按比例缩小图像质量以提高性能。也可以做相反的事情:以性能为代价扩大规模以提高图像质量。为此,在MyPipelineAsset中将最大渲染比例增加到2。

并且还可以在MyPipeline.Render中激活缩放渲染。

(渲染比例1.25、1.5、1.75和2)


(Render scale 1.5 和2 filtering)


进一步增加渲染比例不会改善图像质量。在3的时候,我们得到的结果与渲染比例1相同,而在4的时候,我们以每个像素2×2块的块返回,但距离更近。那是因为单个双线性blit只能平均四个像素。利用更高的比例将需要一个通道,每个片段执行一次以上的纹理采样。尽管可行,但这是不切实际的,因为所需的工作比例与渲染比例成平方关系。SSAA 4x将需要使用渲染16倍的像素。


2 MSAA


SSAA的替代方法是MSAA:多样本抗锯齿。想法是相同的,但是执行方式不同。MSAA跟踪每个像素的多个样本,而不必将其放置在常规网格中。最大的区别是片段程序每个片段的每个图元仅被调用一次,在原始分辨率下调用。然后将结果复制到栅格化三角形覆盖的所有子样本中。这显着减少了必须完成的工作量,但这意味着MSAA仅影响三角形的边缘,而没有其他影响。高频表面(High-frequency surface)模式和alpha裁剪的边缘仍然有锯齿。


alpha-to-coverage是什么?

这是在某种程度上平滑alpha裁剪边缘的技巧,在某些情况下可以产生不错的效果。本教程将不涉及它。


2.1 配置


为MyPipelineAsset添加一个选项以选择MSAA模式。默认情况下,MSAA是关闭的,其他选项是2×,4×和8×,可以用枚举表示。枚举值表示每个像素的样本数量,因此默认值为1。

(MSAA模式)


为什么不支持MSAA 16×?

可以支持,但是与8倍相比,它几乎没有额外的质量增益,因为非常昂贵,并且没有广泛的支持。


将每个像素的样本量传递到管道实例。

并在MyPipeline中对其进行跟踪。

并非所有平台都支持MSAA,最大样本数也有所不同。超过最大值可能会导致崩溃,因此我们必须确保保持在限制之内。我们可以通过将样本计数分配给QualitySettings.antiAliasing来实现。我们的管道不使用此质量设置,但在分配给它时会注意执行限制。在分配给它之后,我们将其复制回我们自己的样本计数。我们唯一需要知道的是,当不支持MSAA时,它会产生零,我们必须将其转换为样本计数1。


2.2 多采样渲染纹理


每个相机都设置了MSAA支持,因此请在“Render”中跟踪用于渲染的样本,如果相机未启用MSAA,则将其强制为1。然后,如果最终每个像素有多个样本,则必须渲染为中间的多采样纹理,简称MS纹理。

为了正确配置渲染纹理,我们必须在GetTemporaryRT中再添加两个参数。首先是读写模式,这是颜色缓冲区的默认模式,而对于深度缓冲区则是线性的。下一个参数是样本计数。

在禁用所有后期处理的情况下尝试此操作。

(MSAA 2×,4×,8×,和没有MSAA比较 render scale为 2)


MSAA是否可以与定向阴影一起使用?

对于我们的渲染管道,它工作正常。Unity的管道有麻烦,因为它们使用屏幕空间通道来级联定向阴影。在本教程中,我们会再遇到类似的问题。


与将渲染比例增加一倍相比,MSAA 4X的最终效果要好于渲染比例2,但需要注意的是,渲染比例不仅影响几何边缘,还影响所有事物。你也可以将两种方法结合起来。例如,渲染比例为2的MSAA 4x大致可与渲染比例为1的MSAA 8x大致相比,尽管它使用16个样本而不是每个最终像素使用8个样本。

(MSAA 4× and 8×,render scale 2)


2.3 解析纹理贴图


虽然我们可以直接渲染为MS纹理,但是不能以常规方式直接从它们读取图像。如果要对像素进行采样,则必须首先对其进行解析,这意味着将所有采样取平均值以得出最终值。整个纹理在特殊的“Resolve Color”通道中立即进行解析,该通道在采样之前自动插入。

(在最终blit之前解决颜色)


解析MS纹理会创建一个临时的常规纹理,该纹理在所有新纹理渲染到MS纹理之前一直有效。因此,如果我们多次采样然后渲染到MS纹理,则最终将获得相同纹理的额外解析通道。激活启用了模糊的后效果堆栈时,你会看到此信息。在强度为5的时候,我们获得了3个解析通道。

(解析三遍,模糊强度5。)


附加的解析通道没有用,因为我们的全屏效果无法从MSAA中受益。为了避免不必要地渲染到MS纹理,我们可以一次渲染到中间纹理,然后使用它代替相机目标。为此,可以在MyPostProcessingStack中的RenderAfterOpaque和RenderAfterTransparent方法中添加示例参数。如果启用了模糊并且使用了MSAA,则将其复制到已解析的纹理并将其传递给Blur。

将渲染样本添加为MyPipeline.Render中的参数。

现在将三个解析通道减少为一个,再加上一个简单的blit。

(只用了一次 模糊强度为5)


2.4 无深度解析


颜色样本通过对它们进行平均来解决,但这不适用于深度缓冲区。平均相邻深度值没有任何意义,也没有可以使用的通用方法,因此多采样深度根本无法解析。所以,启用MSAA时,深度条纹效果不起作用。


使效果再次起作用的幼稚方法是在启用深度条纹时不将MSAA应用于深度纹理。首先,向MyPostProcessingStack添加一个getter属性,该属性指示是否需要从深度纹理读取。仅在使用深度条纹效果时才需要。

现在,我们可以跟踪MyPipeline.Render中是否需要可访问的深度纹理。仅当需要深度时,才需要获得单独的深度纹理,否则我们可以通过设置颜色纹理的深度位来解决。而且,如果确实需要深度纹理,那么让我们明确地始终将其样本设置为1,以禁用它的MSAA。

这也会影响绘制不透明效果后设置渲染目标。

最后需要释放哪些纹理。

(MSAA 8×的深度条纹)


现在,启用MSAA时会显示深度条纹,但是抗锯齿功能似乎已损坏。发生这种情况是因为深度信息不再受MSAA的影响。我们需要另寻解决方案。


2.5 Depth-Only 通道


我们需要一个MS深度纹理来进行常规渲染,并需要一个非MS深度纹理来进行深度条纹效果。可以通过为深度纹理创建自定义解析过程来解决此问题,但不幸的是,对此的支持非常有限。另一种方法是通过添加Depth-Only的通道将深度渲染两次,以将其渲染到常规深度纹理。这是昂贵的但可行。当深度缓冲区与MSAA结合使用时,例如当级联方向阴影需要屏幕空间阴影通道时,这就是Unity的实现方式。


这是否意味着Unity的Depth-Only 通道不影响常规渲染?

确实。它不用于填充深度缓冲区,我们也不会使用它。


首先要区分是可以直接使用深度纹理还是因为MSAA处于活动状态而需要一个Depth-Only通道。我们现在拥有的获取纹理和设置渲染目标的逻辑适用于不使用MSAA的情况。

如果需要,在调用RenderAfterOpaque之前添加Depth-Only通道。就像不透明的通道一样,除了我们使用DepthOnly作为通道名称,不需要渲染器配置,并且必须设置和清除深度纹理作为渲染目标。

将所需的过程添加到Lit着色器。它是默认通道的拷贝,其中除去了实例化,剪切和LOD淡入淡出的所有功能。它不会写入颜色信息,因此请将其颜色掩码设置为零。它始终写入深度,并依赖于单独的DepthOnly HLSL文件中的专用顶点和片段函数。

DepthOnly.hlsl是Lit.hlsl的副本,其中删除了所有不影响深度的数据。

我们只关心位置和UV坐标。片段功能仅适用LOD并执行裁剪。它的最终结果就是零。

现在,在深度条纹和MSAA再次起作用之前,我们获得了Depth-Only通道。不幸的是,深度条纹效果本身无法从MSAA中受益,因此仍然会在严重影响图像的地方引入锯齿。Unity的级联方向阴影以相同的方式干扰MSAA。

(深度条纹具有功能性MSAA 8倍)


但是后来获得渲染的透明几何体仍然可以从MSAA中受益。

(透明,带有MSAA和深度条纹)


3 HDR


我们将讨论的最后一个主题是高动态范围渲染。到目前为止,纹理的每个颜色通道的范围为0-1,因此它可以表示强度为1的光照级别。但是入射光的强度没有固有的上限。太阳是非常明亮的光源的一个例子,这就是为什么你不应该直接看它的原因。它的强度远大于我们在眼睛受损之前所能感知的强度。但是许多常规光源也会产生强度超过观察者极限的光,尤其是近距离观察时。例如,我将场景中的点光源的强度增加到100,并将白色球体的发射强度提高了一步。

(高强度光和自发光)


结果是过亮的像素被吹散为均匀的白色,并且在边缘附近有一些颜色偏移。HDR渲染的目的是防止图像的非常亮的部分退化为均匀的白色。这将需要存储明亮的数据并将其转换为可见的颜色。


3.1 配置


通过向MyPipelineAsset添加“Allow HDR”切换,将其传递给管道实例,使我们的管道是否支持高动态范围渲染成为可选。

MyPipeline只需要跟踪它。

(HDR 开启)


3.2 纹理格式


要存储超过1的颜色值,我们需要更改用于渲染纹理的纹理格式。如果我们的管道和摄像机都启用了HDR,则我们需要默认的HDR格式,否则我们可以使用常规默认值。区别在于HDR纹理的颜色通道包含浮点值而不是8位值。因此它们需要更多的内存,这意味着你仅应在需要时使用HDR。


我们不必根据是否启用HDR来决定renderToTexture,因为在不使用后期处理的情况下也没有理由使用HDR,因为最终的摄影机目标是每通道LDR 8位。

HDR显示器呢?

Unity当前不支持HDR显示器,因此我们也不能支持。除此之外,HDR显示的问题是使用颜色分级的游戏必须在色调映射之后(或同时)进行此操作,从而生成常规的LDR图像,然后在发送之前将其转换回HDR 到显示器上,然后它会做自己的事情。


不使用渲染纹理时,请不要使用HDR。因此,让我们也用MSAA,并不进行其他后处理。最初的结果看起来没有什么不同,只是抗锯齿质量变差了。之所以发生这种情况,是因为只有在所有值均为LDR时,平均颜色才能很好地起作用,否则非常明亮的样本将主导结果。因此,当涉及HDR颜色时,MSAA会降低。

(MSAA×4均可打开和关闭HDR)


在MyPostProcessingStack中添加所需的参数。我们只需要将其用于DepthStripes中的临时纹理,因为无论如何必须在LDR中执行模糊处理才能获得最佳效果。


3.3 色调映射


从HDR到LDR的转换称为色调映射,它来自摄影和胶片开发。传统的照片和胶片范围也很有限,因此已经开发了许多技术来执行转换。没有唯一、绝对正确的方法来处理此问题。可以使用不同的方法来设置最终结果的气氛,例如经典的电影外观。但是,调整颜色属于色调分级之后的颜色等级。我们只关心降低图像的亮度,以使其最终落在LDR范围内。


色调映射是一种后期处理效果,可以是可选的,因此请将其切换添加到MyPostProcessingStack中。

(开启色调映射)


它是通过自己的过程完成的,因此为其添加一个枚举值和方法,最初只是使用其自己的探查器样本进行涂抹。设置源ID和目标ID参数RenderTargetIdentifier,以便我们可以灵活地传递给它。

如果禁用了模糊,则在RenderAfterTransparent中,将色调映射到摄像机目标而不是常规blit。否则,当使用色调映射或MSAA时,通过适当的遍历创建解析的纹理。因此,在这种情况下解决可能意味着MSAA解决,色调映射解决或两者兼而有之。

将新的通道添加到PostEffectStack着色器。

并将所需的功能添加到HLSL文件。最初返回的颜色为负1,饱和度。这可以指示我们哪些像素包含过亮的颜色。

(只有过亮的颜色)


3.4 莱因哈德(Reinhard)


色调映射的目的是降低图像的亮度,以使均匀的白色区域显示各种颜色,从而揭示丢失的细节。就像你的眼睛不适应突然明亮的环境,直到再次看的清楚。但是我们不想均匀地缩小整个图像,因为那样会使深色变得难以区分,将高亮度换成曝光不足。因此,我们需要一个非线性转换,该转换不会减少很多暗值,但会减少很多高值。在极端情况下,零保持为零,并且接近无穷大的值减小为1。完成的函数为 c/(1 + c),其中c是颜色通道。该功能被称为Reinhard色调映射操作,最初由Mark Reinhard提出,只是他将其应用于亮度,而我们将其应用于各个颜色通道。使我们的色调映射通道使用它。

(没有VS有 Reinhard RGB 色调映射)


结果是保证没有任何过亮像素的图像,但是整个图像已经去饱和并有些暗了。完全处于满强度状态的像素减半,你可以在禁用HDR的情况下应用色调映射来清楚地看到。

(色调映射在LDR图像上的应用)


我们不能在解决MS纹理之前执行色调映射吗?

可以,通过自定义解析通道,在对每个样本进行平均之前对每个样本执行色调映射,这比每个像素执行一次代价更高。除此之外,像bloom这样的后处理效果还需要HDR数据。为了使这些工作有效,解析通道必须在平均后转换回HDR,以近似原始强度。

3.5 修改Reinhard


色调映射有多种方法-可以使用颜色分级对其进行进一步调整-但每通道RGB Reinhard最简单,因此我们先将其保留下来。但是我们可以进行的简单调整是通过调整压缩为LDR的值范围来限制效果的强度。超出此范围的任何东西都保持明亮。这样一来,我们就可以减少对亮度较弱的场景的调整,或者接受一些亮度过高的效果,以保持较暗的颜色不变。

Reinhard也描述了此调整,并将函数转换为,其中w 是白点,或本例中的最大色调映射范围。如果 w是无限的,那么我们将再次拥有原始函数,当w 为1时,色调映射将不执行任何操作。

w从1到5)

为该范围添加一个配置选项,最小值为1,因为较低的值会过度曝光整个图像。最大值可以为100,足以近似原始功能。然后计算,并将其作为Reinhard函数的修改器发送到GPU。

然后,着色器只需计算

(色调映射范围减少到2)


现在我们结束了原始的SRP教程系列,该系列从尚处于实验阶段开始。Unity 2019发生了很多变化。为此,有一个新的自定义SRP系列,它以更现代的方式涵盖了新旧主题。


欢迎扫描二维码,查看更多精彩内容。点击 阅读原文 可以跳转原教程。










本文翻译自 Jasper Flick的系列教程

原文地址:

https://catlikecoding.com/unity/tutorials








本文分享自微信公众号 - 壹种念头(OneDay1Idea)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!