前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity通用渲染管线(URP)系列(十二)—— HDR(Scattering and Tone Mapping)

Unity通用渲染管线(URP)系列(十二)—— HDR(Scattering and Tone Mapping)

作者头像
放牛的星星
发布2021-01-11 14:29:23
3.5K0
发布2021-01-11 14:29:23
举报
文章被收录于专栏:壹种念头壹种念头

目录

· 1 高动态范围

· 1.1 HDR反射探针

· 1.2 HDR相机

· 1.3 HDR渲染纹理

· 1.4 HDR后处理

· 1.5 解决萤火虫

· 2 Bloom散射

· 2.1 Bloom 模式

· 2.2 散射局限

· 2.3 阈值

· 3 色调映射

· 3.1 额外的 Post FX步骤

· 3.2 色调映射模式

· 3.3 Reinhard

· 3.4 Neutral

· 3.5 ACES

本文重点内容: 1、渲染到HDR纹理 2、减少Bloom萤火虫 3、增加Bloom散射 4、支持多种色调映射模式

这是有关创建自定义脚本渲染管道的系列教程的第12部分。它增加了对高动态范围渲染,基于散射的光晕和色调映射的支持。

本教程是CatLikeCoding系列的一部分,原文地址见文章底部。

本教程使用Unity 2019.4.8f1制作。

(一个黑暗、明亮和超亮结合的区域)

1 高动态范围

到目前为止,渲染摄像机时,我们已经在低动态色彩范围(简称LDR)中进行了设置,这是默认设置。这意味着每个颜色通道均使用固定为0–1的值表示。在此模式下,(0,0,0)代表黑色,(1,1,1)代表白色。尽管我们的着色器可能会产生超出此范围的结果,但GPU会在存储它们时限制颜色,就好像我们在每个片段函数的末尾使用了饱和一样。

(1,1,1)真的是白色吗? 它是理论上的白点,但其实际观察到的颜色取决于显示器及其配置。调整显示器的亮度会改变其白点。此外,你的眼睛会根据所看物体的整体亮度进行调整,从而移动自己的相对白点。例如,即使你观察到的强度发生了变化,如果你降低房间的照明水平,你仍将以相同的方式解释颜色。你还可以补偿照明的色相偏移。当照明突然改变时,这是很明显的,因为调整是逐渐的。

可以使用帧调试器检查每个DrawCall的渲染目标的类型。普通相机的目标描述为B8G8R8A8_SRGB。这意味着它是一个RGBA缓冲区,每个通道有8位,因此每个像素32位。同样,RGB通道存储在sRGB色彩空间中。当我们在线性色彩空间中工作时,GPU在读取和写入缓冲区时会自动在两个空间之间进行转换。渲染完成后,缓冲区将发送到显示器,后者将其解释为sRGB颜色数据。

那么HDR显示呢? Unity当前不支持HDR显示。假定所有显示均为LDR sRGB。 只要光强度不超过每个颜色通道的1,就可以正常工作。但是入射光的强度没有固有的上限。太阳就是非常明亮的光源的一个例子,这也是为什么你不应该直接看它的原因。它的强度远大于我们在眼睛受损之前所能感知的强度。但是许多常规光源也会产生强度超过观察者极限的光,尤其是近距离观察时。为了正确地使用这种强度,我们需要渲染高动态范围的HDR缓冲区,该缓冲区支持大于1的值。

1.1 HDR反射探针

HDR渲染需要HDR render targets。这不仅适用于普通相机,对于反射探针也是如此。可以通过其HDR切换选项(默认启用)控制反射探针是否包含HDR或LDR数据。

(HDR反射探针开启)

当反射探针使用HDR时,它可以包含高强度颜色,这些颜色大多数是它捕获的镜面反射。你可以通过它们在场景中引起的反射间接观察它们。不完美的反射会削弱探针的颜色,从而使HDR值突出。

(反射,有和没有HDR)

1.2 HDR相机

摄像机还具有HDR配置选项,但它本身无法执行任何操作。可以将其设置为Off或Use Graphics Settings。

(相机的HDR依赖于图形设置)

Use Graphics Settings模式仅表示相机允许HDR渲染。是否发生这种情况取决于RP。我们将通过向CustomRenderPipelineAsset添加一个切换开关来允许HDR,并将其传递给管道构造函数来进行控制。

让CustomRenderPipeline追踪它,并将其与其他选项一起传递给相机渲染器。

然后,CameraRenderer追踪是否应使用HDR,这是在摄像机和RP都允许的情况下。

(允许HDR)

1.3 HDR渲染纹理

HDR渲染仅与后处理结合使用才有意义,因为我们无法更改最终的帧缓冲区格式。因此,当我们在CameraRenderer.Setup中创建自己的中间帧缓冲区时,我们将在适当的时候使用默认的HDR格式,而不是LDR的常规默认格式。

帧调试器将显示默认的HDR格式为R16G16B16A16_SFloat,这意味着它是RGBA缓冲区,每通道16位,因此每像素64位,是LDR缓冲区大小的两倍。在这种情况下,每个值都是线性空间中的有符号的float,而不是固定为0~1。

我们可以使用不同的渲染纹理格式吗? 是的,但是你需要确保目标平台支持它。在本教程中,我们使用默认的HDR格式,该格式将始终有效。

‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍逐步执行DrawCall时,你会注意到场景看起来比最终结果要暗。发生这种情况是因为这些步骤存储在HDR纹理中。由于线性颜色数据按原样显示,因此看起来很暗,它错误地解释为sRGB。

(HDR和LDR 在后处理结果之前,通过帧调试器查看)

为什么亮度会变化? sRGB格式使用非线性传递函数。显示器会为此调整,执行所谓的伽马校正。伽玛调节函数通常用c的2.2次方和c原色近似,但实际传递函数略有不同。

(不正确的线性数据调整)

1.4 HDR后处理

现在,结果看起来与以前没有什么不同,因为我们对扩展范围不做任何事情,并且一旦渲染到LDR目标,它就会被钳位。Bloom可能显得更亮一些,但也不是很多,因为在预过滤通过之后颜色会被钳位。为了充分利用它,我们还需要在HDR中执行后处理。因此,让我们通过在调用CameraRenderer.Render中的PostFXStack.Setup时设置是否使用HDR。

现在,PostFXStack也可以跟踪是否应使用HDR。

而且我们可以在DoBloom中使用适当的纹理格式。

根据场景的明亮程度,HDR和LDR绽放之间的差异可能很大,也可能不明显。通常,光晕阈值设置为1,因此只有HDR颜色起作用。这样,发光指示的颜色对于显示器来说太亮了。

(HDR bloom 阈值为1 knee为0)

因为bloom对颜色进行平均,即使是单个非常明亮的像素也会在视觉上影响一个非常大的区域。你可以通过比较预过滤步骤和最终结果来看到这一点。即使是单个像素也能产生巨大的圆形辉光。

(HDR Bloom 在 pre-filtering 步骤)

例如,当由于下采样而将值0、0、0和1的2×2块平均时,结果将为0.25。但是,如果HDR版本的平均值为0、0、0和10,则结果为2.5。与LDR相比,似乎0.25被结果提高到了1。

1.5 解决萤火虫

HDR的一个缺点是,它可以产生比周围环境更亮的小图像区域。当这些区域大约是一个像素大小或更小的时候,它们可以剧烈地改变相对大小,并在移动期间突然出现或消失,这就会导致闪烁。这些区域被称为萤火虫。当bloom被应用到它们身上时,其效果会变成频闪效应。

(HDR Bloom萤火虫)

完全消除此问题将需要无限的解决方案,而这是不可能的。我们可以做的第二件事是在预过滤过程中更加主动地模糊图像,以淡出萤火虫。为此,我们向PostFXSettings.BloomSettings添加一个切换选项。

(淡出萤火虫开关)

为此添加一个新的pre-filter萤火虫Pass。这一次,我同样不会显示将Pass添加到PostFxStack着色器和PostFXStack.Pass枚举代码。选择适当的通道以在DoBloom中进行预过滤。

使萤火虫淡化最直接的方法是将预过滤通道的2×2降采样过滤器增长为大型6×6盒式过滤器。我们可以用9个样本做到这一点,然后在平均之前分别将绽放阈值应用于每个样本。为此,将所需的BloomPrefilterFirefliesPassFragment函数添加到PostFXStackPasses。

(6X6的盒过滤)

但这还不足以解决问题,因为非常明亮的像素只会散布在更大的区域上。为了使萤火虫淡化,我们将根据颜色的亮度使用加权平均值。颜色的亮度是其感知的亮度。我们将为此使用Luminance功能,该功能在核心库的Color HLSL文件中定义。

(涉及公式的看原文,数学公式实在难弄)

最后,我们将样本总和除以这些权重的总和。这有效地将萤火虫的亮度分散到所有其他样本中。如果其他样品很暗,萤火虫就会淡化。例如,0、0、0和10的加权平均值为

(基于亮度的权重平均)

由于我们在初始预滤波步骤之后执行了高斯模糊处理,因此可以跳过直接靠近中心的四个样本,从而将样本数量从九个减少到五个。

(6X6交叉过滤)

这将使单个像素萤火虫变成×形图案,并在预过滤步骤中将单个像素水平或垂直线分成两个单独的线,但是在第一个模糊步骤之后,这些图案消失了。

(Pre-filtering 步骤 5和9次采样,一般的分辨率)

这并不能完全消除萤火虫,但是会降低萤火虫的强度,以至于它们不再明显地可见,除非将Bloom强度设置为远高于1。

(淡化萤火虫)

2 Bloom散射

现在我们有了HDR bloom,让我们考虑一个更现实的应用程序。这个想法是相机并不是完美的。他们的镜头不能正确地聚焦所有的光线。一部分光线散射到更大的区域,有点像我们现在的bloom效果。相机越好,散射越少。与我们添加的bloom效果最大的区别是散射并没有添加光,它只是散射光。散射可以在视觉上从一个轻微的辉光变化到一个轻的薄雾,让整个图像朦胧。

眼睛也不是完美的,光线在眼睛内部以一种复杂的方式散射。它发生在所有入射光的情况下,但只有当它很亮的时候才会真正被注意到。例如,在黑暗的背景下看一个明亮的小光源是很明显的,就像在晚上看灯笼,或者在明亮的白天看太阳的反射。

它不是均匀的圆形模糊发光,我们将看到多点不对称的类似原点的图案,这些图案也具有色相偏移,这是我们自己的眼睛所特有的。但是我们的光晕效果将代表具有均匀散射的无特征相机。

(相机里 Bloom 引起的 散射)

2.1 Bloom 模式

我们将支持经典的additive和energy-conserving 的Bloom。在PostFXSettings.BloomSettings中为这些模式添加枚举选项。另外添加一个0 ~ 1滑块来控制光线散射的程度。

(Scattering模式选中并设置为0.5)

将现有的BloomCombine Pass重命名为BloomAdd,并引入一个新的BloomScatter Pass。确保枚举和传递顺序保持字母顺序。然后在合并阶段在DoBloom中使用适当的通道。在散射的情况下,我们将散射量用于强度而不是1。我们仍将配置的强度用于最终绘制。

BloomScatter pass的函数和BloomAdd的函数是一样的,只是它根据强度在高分辨率和低分辨率的光源之间进行插值,而不是添加它们。因此,散点的值为零意味着只使用最低的bloom金字塔级别,而散点1意味着只使用最高的bloom金字塔级别。在0.5时,连续级别的贡献在4个水平的情况下为0.5、0.25、0.125、0.125。

(可变的散射,强度为20,最大迭代次数为16,光源在结构内)

散射并不会使图像变亮。上面的例子可能看起来变暗了,但那是因为它只显示了原始的裁剪部分。然而,能量守恒并不是完美的,因为高斯滤波器被钳位在图像的边缘,这意味着边缘像素的贡献被放大。我们可以弥补这一点,但不用,因为它通常不会特别明显。

2.2 散射局限

因为散射值为0和1消除了除一个金字塔等级以外的所有等级,所以使用这些值没有意义。因此,让我们将分散滑块的范围减小到0.05–0.95。这将使默认值零无效,因此请使用值显式初始化BloomSettings。我们使用0.07,这与URP和HDRP使用的默认散射值相同。

而且,大于1的强度不适用于散射光晕,因为那样会增加光。因此,我们将其限制在DoBloom中,将最大值限制为0.95,这样原始图像将始终对结果有所帮助。

(强度为0.5 散射值为0.7)

2.3 阈值

散射Bloom效果远比叠加性的Bloom效果要好。它通常也用于低强度。这意味着,就像真实的相机一样,只有在非常明亮的光线下,即所有的光线都被散射,bloom效果才会非常明显。

尽管不真实,但仍然可以应用阈值来消除较暗像素的散射。使用更强的光晕效果时,可以使图像清晰。但是,这同样会消除光线,从而使图像变暗。

(阈值为1 knee为0 强度为1)

我们需要补偿丢失的散射光。为此,我们创建了一个额外的BloomScatterFinal Pass,将其用于散射Bloom的最终绘制。

此Pass的功能是其他散射Pass功能的拷贝,只是有1点不同。通过添加高分辨率光,然后再次将其减去,但使用了光晕阈值,它将丢失的光添加到低分辨率通道。这不是一个完美的重建方案,它不是加权平均值,可以忽略由于萤火虫淡化而造成的光线损失,但是距离足够近,并且不会为原始图像增加光。

(最终的Pass,带阈值的散射)

3 色调映射

尽管我们可以使用HDR进行渲染,但对于常规摄像机而言,最终的帧缓冲区始终为LDR。因此,色彩通道在1处被切断。最终图像的白点实际上还是在1处。极亮的颜色最终看起来与完全饱和的颜色没有什么不同。例如,我制作了一个具有多个光照级别的场景,并且发出了各种发光量远大于1的物体。最强的发光强度是8,最亮的发光强度是200。

(没有后处理,只有实时光)

如果不应用任何后置FX,则很难甚至无法分辨哪些物体和灯光是非常明亮的物体。我们可以使用Bloom使其变得明显。例如,我使用阈值1,Knee0.5,强度0.2和散射0.7进行最大迭代。

(Bloom 叠加和散射表现)

发光的物体显然应该是明亮的,但我们仍然无法感觉到它们相对于场景其余部分的亮度。为此,我们需要调整图像的亮度(增加其白点),以使最亮的颜色不再超过1。我们可以通过均匀地使整个图像变暗来做到这一点,但这会使大多数图像变暗 我们将无法清楚地看到它。理想情况下,我们会调整很多非常明亮的颜色,而只调整一点深色。因此,我们需要进行非均匀的颜色调整。这种颜色调整并不代表灯光本身的物理变化,而是代表如何观察它。例如,我们的眼睛对较暗的色调比较浅的色调更敏感。

从HDR到LDR的转换称为色调映射,它来自摄影和胶片开发。传统的照片和胶片也具有有限的范围和不均匀的感光度,因此已经开发了许多技术来执行转换。没有所谓执行色调映射的正确方法。可以使用不同的方法来设置最终结果的气氛,例如经典的电影外观。

3.1 额外的 Post FX步骤

在bloom之后,我们在一个新的post FX步骤中执行色调映射。为此,向PostFXStack添加一个DoToneMapping方法,它最初只是将一个源复制到摄像机目标。

我们需要调整Bloom的结果,因此获得新的全分辨率临时渲染纹理并将其用作DoBloom中的最终目标。还使它返回是否绘制任何内容,而不是在跳过效果时直接绘制到摄影机目标。

调整Render,以便在启用Bloom效果时对它执行色调映射,然后释放Bloom效果纹理。否则,将其色调映射直接应用于原始源,完全跳过Bloom。

我们可以将色调映射与最终的Bloom Pass相结合吗? 是的,URP和HDRP通过Uber Pass可以做到这一点,甚至更多。但是,将FX完全分开是很清楚的,并且可以更轻松地更改它们,因此这是我们在本教程中所做的。

3.2 色调映射模式

色调映射有多种方法,我们将支持其中的几种,因此向PostFXSettings添加一个ToneMappingSettings配置结构,并带有一个最初只包含None的Mode枚举选项。

(色调映射模式设置为None)

3.3 Reinhard

色调映射的目的是降低图像的亮度,以使均匀的白色区域显示多种颜色,从而揭示丢失的细节。就像当你的眼睛适应突然明亮的环境,直到你再次看到清晰。但是我们不想均匀地缩小整个图像,因为那样会使深色变得难以区分,将高亮度换成曝光不足。因此,我们需要一个非线性转换,该转换不会减少很多暗值,但会减少很多高值。在极端情况下,零保持为零,而接近无穷大的值减小为1。

一个简单的函数可以实现这一点,即c/(1+ c)其中c是一个颜色通道。这个函数被称为Reinhard tone mapping操作,其最简单的形式,最初是由Mark Reinhard提出的,但他将其应用于亮度,而我们将其应用于每个单独的颜色通道。

(Reinhard 色调映射)

在None之后,将Reinhard的选项添加到ToneMappingSettings.Mode。然后使枚举从-1开始,因此Reinhard值为零。

接下来,添加一个ToneMappingReinhard的Pass,并在适当的时候使PostFXStack.DoTonemapping使用它。具体来说就是,如果模式为负,则执行简单复制,否则应用Reinhard色调映射。

ToneMappingReinhardPassFragment着色器函数仅应用该函数。

(没有色调映射,Additive Bloom)

(没有色调映射,散射Bloom)

(Reinhard色调映射,Additive Bloom)

(Reinhard色调映射,散射Bloom)

这种方法可以工作,但是由于精度的限制,对于非常大的值可能会出错。出于同样的原因,非常大的值在1结束时比无穷早得多。因此,在执行色调映射之前,让我们钳位颜色。60的限制避免了我们将支持的其他模式出现其他的潜在问题。

精度何时会成为问题? 使用half时,对于某些功能可能会成为问题。由于着色器编译器中的错误,即使在显式使用float的情况下,Metal API也会在某些情况下发生这种情况。这不仅会影响移动设备,还会影响某些MacBook。

3.4 Neutral

Reinhard色调映射的白点在理论上是无限的,但可以对其进行调整,以便尽早达到最大值,从而削弱了调整效果。该替代功能是

,W就是白点。

(Reinhard 的白点在无限和4的时候)

我们可以为此添加一个配置选项,但是Reinhard并不是我们可以使用的唯一功能。一个越来越有趣的有趣的应用是

输入颜色通道,其他值是配置曲线的常数。

最终的颜色是

C是颜色通道,e是曝光偏置w是白点。它会产生s型曲线,底部区域从黑色向上弯曲到中间的线形部分,结束于肩区域,当它接近白色时变平。

以上功能由John Hable设计。它首先在《神秘海域2》中使用(请参阅幻灯片142和143)。https://www.slideshare.net/ozlael/hable-john-uncharted2-hdr-lighting

(Reinhard和神秘海域2的色调映射)

URP和HDRP使用此功能的变体,具有自己的配置值和5.3的白点,但它们也将白色标度用于曝光偏差,所以最后的曲线就是

这导致大约4.035的有效白点。它用于中性色调映射选项,可通过Color Core Library HLSL文件中的NeutralTonemap函数使用。

(Reinhard 色调映射 白点无限和4,以及neutral色调映射)

让我们为此色调映射模式添加一个选项。将其放在Mode枚举中的None之后和Reinhard之前。

然后为其创建另一个通道。现在,通过将模式添加到neutral选项(如果不是None),PostFXStack.DoToneMapping可以找到正确的Pass。

然后,ToneMappingNeutralPassFragment函数只需调用NeutralTonemap。

(1,2是Reinhard,3,4是neutral)

你也可以添加配置选项来调整自己的曲线,但是我们将继续使用最终的色调映射模式。

3.5 ACES

在本教程中,我们将支持的最后一种模式是ACES色调映射,URP和HDRP也会使用它。ACES是Academy Color Encoding System(学院色彩编码系统 https://acescentral.com/)的简写,Academy Color Encoding System是用于交换数字图像文件,管理色彩工作流程以及创建用于交付和存档的母版的全球标准。我们将仅使用Unity实施的色调映射方法。

首先,将其添加到Mode枚举,紧接着在None后面,以使其余字母保持字母顺序。

添加pass并调整PostFXStack。DoToneMapping从ace开始。

新的ToneMappingACESPassFragment函数可以简单地使用核心库中的AcesTonemap函数。它通过Color包括在内,但是有一个单独的ACES HLSL文件可供你研究。函数的输入颜色必须在ACES颜色空间中,我们可以使用unity_to_ACES函数。

(1,2 为neutral,3,4为ACES,5,6为没有色调映射)

ACES与其他模式之间最明显的区别是,它为非常明亮的颜色增加了色相转换,将它们推向白色。当照相机或眼睛被太多的光线淹没时,也会发生这种情况。结合Bloom,现在可以清楚地看到哪些表面最亮。而且,ACES色调映射会稍微减少较暗的颜色,从而增强对比度。结果是电影般的外观。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 壹种念头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档