动态局部曝光

哈Ha! 我向您介绍约翰·查普曼(John Chapman)的文章“动态局部暴露”的翻译。

图片

在本文中,我将介绍有关HDR渲染中动态局部曝光的一些想法。 巴特·沃伦斯基(Bart Wronsky)在该主题上已有出色的文章 ,强烈建议您立即阅读; 这些想法在很大程度上是基于他的文章。 最后,我还介绍了其他一些很棒的链接。

低/高动态范围


在过去的好时代(1990年代),游戏直接以显示的LDR (窄动态范围)格式(伽玛空间,8位)呈现。 它既简单又便宜,但另一方面却严重干扰了真实照片级照片的创建。

当前,尤其是随着PBR (基于物理的渲染)的出现,以更高的精度在线性空间中以巨大的动态范围渲染游戏。 随着这一运动,真正的问题就变成了写实主义:我们如何在LDR中显示HDR图像?

全球自动曝光


自动曝光控制的标准方法是测量场景的平均(或平均对数)亮度,可选地使用权重函数,该函数更喜欢接近图像中心的值。 通过使用并行缩减或通过在亮度缓冲区mipmap中反复下采样 ,可以非常有效地完成此操作。 后一种方法具有一些优点,我将在下一节中讨论。

随后,例如通过计算场景的最大允许亮度的倒数,将平均亮度转换为曝光值:

float Lavg = exp(textureLod(txLuminance, uv, 99.0).x); float ev100 = log2(Lavg * 100.0 / 12.5); ev100 -= uExposureCompensation; // optional manual bias float exposure = 1.0 / (1.2 * exp2(ev100)); 

从ISO标准获得,用于基于饱和度计算速度;有关完整说明,请参见

由于潜在的平均亮度在动态条件下不稳定,因此通常可以使用指数滞后函数(2)随时间进行平滑:

 Lavg = Lavg + (Lnew - Lavg) * (1.0 - exp(uDeltaTime * -uRate)); 

译者评论
仅在计算最后一个Mip级别(1x1)时,才需要在着色器下采样纹理亮度时应用此功能。 还将对此进行详细介绍,但是我认为这很容易忽略。

由于其全局性质,该方法会遭受严重阴影或图像高光区域的影响,这些区域与平均亮度存在偏差:

图片

尽管这与眼睛适应光线水平变化的能力相对应,但总体效果与我们在现实世界中的真实感受相差甚远。

本地AE


如果我们通过降采样生成中等亮度,则可以访问亮度缓冲区(4)的较低mip级别以获得局部平均亮度。

 float Lavg = exp(textureLod(txLuminance, uv, uLuminanceLod).x; 

请注意,为使此功能起作用,应仅在最后一步(记录1x1 mip电平时)应用磁滞,否则会出现失真。

从理论上讲,这是一个好主意:与相邻区域形成对比的同时,图像的每个区域都可以具有良好的曝光度。 但是,实际上,会得到令人恶心的结果:

图片

最不愉快的是在高对比度区域中发现的“晕圈”:

图片

但是,可以通过预过滤亮度缓冲区或简单地使用双三次采样来平滑它们:

图片

它看起来仍然令人作呕,但已经好了。

亮度不同的Mipmap进行采样会更改光晕的半径。 该参数可用于控制结果的整体“外观”,并且可最大程度地减少光晕效果,尽管由于对比度普遍降低(它成为边框滤镜)或曝光控制的局部性降低:

图片

但是,仅平滑重影是不够的。 结果一点都不自然。 看起来像极端的“ HDR照片”风格,与人所见不同。 但是,将全球和本地价值观融合在一起,我们可以兼得两全:

 float Llocal = exp(textureLod(txLuminance, uv, uLuminanceLod).x; float Lglobal = exp(textureLod(txLuminance, uv, 99.0).x; float L = mix(Lglobal, Llocal, uLocalExposureRatio); // .. use L to compute the final exposure scale as before 

图片

通过更改混合因子,可以调整局部曝光,从而最大程度地减少伪影并最大化感知到的真实感:

图片

自动混合比


在我们绝对控制摄像机位置,照明等情况下,手动调整混合比例是正常的。 但是,在许多情况下(例如,白天和晚上动态变化的户外游戏),这种控制水平根本是不可能的。 在这种情况下,最好自动生成混合因子

在下图中,我们有很宽的动态范围; 大多是中等偏低的亮度值,以及一些强度较高的区域(窗户上有天空):

图片

没有局部曝光,天空的色彩就会消失。 在这种情况下,我想要大的混合比例

图片

现在考虑下面的图像,该图像的动态范围较小,并且亮度值较高:

图片

在这种情况下,应用局部曝光会大大降低“明亮”区域的亮度:

图片

观测数据提示了一种将局部值与全局值混合的简单方法:如果场景的平均亮度和最大亮度之间的差异较大,则局部曝光的混合系数应更大。 可以在亮度计算过程中轻松完成场景最大亮度的生成,使用滞后使结果平滑,方法与平均值相同。 因此,我们可以如下扩展前一个代码片段:

 float Llocal = exp(textureLod(txLuminance, uv, uLuminanceLod).x; float Lglobal = exp(textureLod(txLuminance, uv, 99.0).x; // average in x float Lmax = exp(textureLod(txLuminance, uv, 99.0).y; // max in y float Lratio = min(saturate(abs(Lmax - Lglobal) / Lmax), uLocalExposureMax); float L = mix(Lglobal, Llocal, Lratio); // .. use L to compute the final exposure scale as before 

请注意, uLocalExposureMax出现在我们的输入中,用于控制局部暴露的绝对最大影响程度。 我得到了很好的结果uLocalExposureMax <0.3

最终代码
 float Llocal = exp(textureLod(txLuminance, uv, uLuminanceLod).x; float Lglobal = exp(textureLod(txLuminance, uv, 99.0).x; // average in x float Lmax = exp(textureLod(txLuminance, uv, 99.0).y; // max in y float Lratio = min(saturate(abs(Lmax - Lglobal) / Lmax), uLocalExposureMax); float L = mix(Lglobal, Llocal, Lratio); float ev100 = log2(L * 100.0 / 12.5); ev100 -= uExposureCompensation; // optional manual bias float exposure = 1.0 / (1.2 * exp2(ev100)); vec3 result = hdrColor * exposure; result += bloom; //etc outColor.rgb = result; 


结论


上面概述的方法对何时需要测量场景的亮度施加了一些限制。 通常,测量是在照明通过后立即进行的,以避免粒子效应, 光晕等的适应。 但是,当使用局部亮度时,重要的是要在亮度图中显示参与曝光的实际值。 这意味着需要在进行曝光之前立即进行亮度测量。 如果这是不可接受的,则解决方案是与平均值和最大值分开生成局部亮度。

尽管我认为将场景的局部和全局亮度一起使用是创建平衡自然外观图像的“正确”方法,但是结果的质量显然是主观的。 这种方法是否适合特定游戏完全取决于内容和所需的视觉样式。 我想听听这方面的其他想法。

参考文献


  1. 局部音调映射 (Bart Wronski)
  2. 实施基于物理的相机 (Panoric Hennessy)
  3. 将冻伤患者转移到PBR (SébastienLagarde等)
  4. 近距离观察音调映射 (Matt Pettineo)
  5. 线性的重要性 (Larry Gritz等)
  6. HDR / VDR彩色管线的先进技术和优化 (Timothy Lottes)

sIBL存档中获取的HDR图像。

Source: https://habr.com/ru/post/zh-CN440018/


All Articles