使用 AMD FreeSync™ Premium Pro HDR 代码示例
AMD FreeSync™ Premium Pro 技术系列文章的第 4 部分。在此,我们探讨如何为所有下一代图形 API 启用 FreeSync Premium Pro。
这是关于 AMD FreeSync™ Premium Pro 技术(以下简称 FreeSync HDR!)系列教程的第三部分。
在本教程中,我们将介绍什么是色域映射,我们如何实现色域映射器来展示 FreeSync HDR 的工作原理,以及不同色域映射算法的一些潜在陷阱。我们还将解释 FreeSync HDR 如何使我们能够实现一个色域映射算法,该算法为每个显示器提供快速准确的输出。请注意,教程的其余部分将把色域体积简单地称为色域,因为这是大多数文献中常用的术语。
色域映射是一种技术,我们尝试将颜色从源色域(例如 Rec2020)转换为目标色域(例如 Rec709),同时仍然努力保留场景的整体外观。通常,源色域大于目标色域,但也可以进行翻译或旋转,使得两个色域重叠但不相同。在色调映射中,我们将场景的亮度缩放到显示器可以显示的亮度级别,同时仍然保留图像属性,如对比度。
通过色域映射,我们做了一些类似的事情,但不是仅仅修改场景亮度,而是修改场景的颜色,使其能够被我们的显示器准确显示。借助 FreeSync HDR,我们可以获得显示器的原生显示色域,从而能够将帧的颜色映射到显示器可以显示的颜色。
色域映射算法应用于帧缓冲区的每个像素,类似于色调映射,在显示帧到显示器之前(它应该是最后一个操作)。实现色域映射器有很多方法,但通常色域映射器分为两类:剪裁和感知。
剪裁色域映射器 是一种不改变目标色域内的颜色的映射器,而目标色域外的颜色则被映射。感知色域映射器 是一种将所有颜色映射到新颜色的映射器。其思想是保留颜色之间的相对距离,以便图像的局部/全局属性保持不变(例如,对比度)。您可以在 此链接 中找到色域映射的视觉介绍。
两种方法都不是完美的,您选择哪种方法很大程度上取决于您的源内容。如果映射的内容中色域外像素很少,剪裁映射器通常效果更好;如果色域外像素很多,感知映射器通常效果更好。您可以在下面的示例中看到由剪裁映射器和感知映射器映射的图像。


在上图中,左侧您可以看到剪裁伪影,花朵的很大一部分看起来像是涂抹了亮红色的污迹。右侧您可以看到感知映射的图像,它消除了这些伪影,但您会注意到图像的饱和度也较低。这是因为在感知映射过程中保留了对比度,结果是图像略微变暗。上面的图像夸大了每种映射器可能出现的伪影,但它显示了在选择色域映射器时可能需要权衡的内容。
作为 FreeSync HDR 工作的一部分,我们实现了一个剪裁色域映射器,因为大多数游戏对于任何给定的帧来说色域外像素都很少,它有助于理解如何最大程度地减小色域外颜色与其映射的色域内颜色之间的感知差异。
为了做到这一点,我们需要在一个颜色距离与其感知相似度相关的颜色空间中进行工作。我们选择在 CIELAB 中工作,因为它是一个感知均匀的颜色空间(请参阅本系列 第一篇文章 了解更多!)。我们下面可以看到 Toshiba 显示器在 CIELAB 中的色域体积的可视化,它与 sRGB 中的体积相似。

我们将显示 2D 色域可视化,其中包含一个亮度轴和一个色度轴,如下图所示。

在 CIELAB 中,a, b 平面上的角度代表色相,而a, b 平面上到原点的距离代表色度(查看 CIELAB 色彩空间)。由于我们的色域映射器保持色相恒定,要可视化某个特定色相的色域,我们可以将 3D 色域与一个在亮度轴上无限延伸,并在a, b 轴上与我们的色相结合的角度相切的平面相交。在上图的右上角,我们可以看到 3D 色域在a, b 平面上的投影,以及一条穿过色域橙色部分的线。该线代表我们与 3D 色域相交以生成图像左下角三角形(该色相值的色域形状)的平面。对于每个色相值,我们只显示具有正色度的值,如上所示。
在色域映射问题域中,我们有一个源色域,代表输入图像中的颜色,还有一个目标色域,代表显示器可以显示的颜色。在整篇博文中,我将展示如下图所示的源色域和目标色域的可视化,并显示亮度和色度轴。

在上图的可视化中,L 轴上的最高点代表我们的显示器可以显示的最高亮度颜色(可以使用 FreeSync HDR 查询其亮度)。在上图中,我们还可以看到目标色域之外但源色域之内的颜色区域。这些颜色需要映射到目标色域,同时尽可能与色域外颜色在感知上相似。由于 CIELAB 是感知均匀的,我们将欧氏距离用作感知均匀性的度量。这并不是一个完美的感知距离度量,因为随着颜色之间距离的增加,欧氏距离的准确性会降低,但我们发现,通过改变颜色的移动方向,我们可以获得不错的结果。
在构建色域映射器时,通常会将某个颜色属性设置为源颜色和映射颜色之间不变的属性,这样我们就可以限制移动到找到映射颜色的方向数量。对于剪裁色域映射器,我们发现大多数研究都集中在保持色相恒定,因为色相的变化比亮度和色度的变化更明显。色度被认为是变化最大的颜色属性,因为我们注意到色度的差异不如亮度的差异明显。知道了这一点,我们首先尝试保持色相和亮度恒定,只改变色度值直到颜色在色域内。但这导致许多像素完全饱和,我们可以在下面的可视化中确切地看到原因。

在上图中,黄色线代表如果我们保持亮度和色相恒定,颜色将沿哪个方向移动。可以看到,由于色域形状不规则,如果我们保持色相和亮度恒定,可能会移动很长一段距离才能找到一个在色域内的颜色。我们还可以看到,如果我们能够修改亮度并沿蓝色线方向移动,那么我们可以在L 轴上稍微向下移动,找到一个比原始颜色更接近的颜色。注意到这一点后,我们允许颜色修改其亮度值,并让它们向L 轴的中心移动,如下所示。

这使得从 Rec2020 到我们显示器的色域的颜色保留效果更好,但仍存在一些问题。对于色域底部的暗颜色,我们可以看到它们映射到目标色域中更亮的颜色,即使存在更接近的颜色可以保持相似的亮度水平。对于色域的较高部分也是如此,它们向下映射太多,降低了亮度,即使存在具有相似亮度水平的更接近的颜色。
最后,我们可以看到在目标色域的尖端附近,很少有颜色映射到那里,而目标色域的较低部分有更多的线条汇聚到那里。这意味着我们将源色域中的颜色非均匀地映射到目标色域,导致更多的颜色冲突,从而产生剪裁伪影。
为了解决这个问题,我们实现了 这篇论文 中的算法(Masaoka-san 等人,2016 年),该算法考虑了色域的三角形形状来计算颜色应移动到源色域的方向。这导致颜色映射所需的距离更短,同时也将颜色映射得更均匀。本节其余部分将逐步介绍如何实现色域映射论文,以及每个步骤如何帮助改进算法。
为了开始实现这篇论文,我们需要定义几个点。我们将色域尖端 定义为在某个指定色相上具有最高色度值的色域上的点(源/目标色域的色域尖端称为源/目标尖端)。在上图的可视化中,源/目标尖端是色域内最右边的点。
由于色域的形状是三角形的,我们希望尖端以上的所有颜色向下映射到色域,而尖端以下的颜色则向上映射到色域,因为这些是欧氏距离最短的路径。为了实现这种行为,我们可以创建一个通过源色域和目标色域的尖端连接的尖端线,并让它与L 轴相交。这将给我们一个称为*LCusp* 的点,如下图所示。

如果我们看上图,我们可以看到亮颜色被更好地映射,没有如此剧烈的亮度变化,但暗颜色仍然显著变亮。为了解决这个问题,我们想改变暗颜色的映射方向,使其更水平,因为我们的眼睛对暗颜色亮度的变化更敏感。为此,我们检查尖端线是向下倾斜还是向上倾斜。如果它向下倾斜,我们就将其延长直到它与C 轴相交。我们称这个交点为*CFocal* ,如下图所示。

如果它向上倾斜,那么我们在*LCusp* 处反射尖端线。无论反射后的尖端线在哪里与C 轴相交,我们都称该点为*CFocal* ,如下图所示。

如果我们查看连接*CFocal* 和*LCusp* 的三角形,我们会看到我们两个色域之间的所有暗颜色都位于此三角形内,因此当我们映射这些颜色时,随着它们变暗,我们希望它们移动得更水平。这可以通过将它们从*CFocal* 移开而不是朝*LFocal* 移动来实现,从而得到以下颜色移动方向:


通过这样做,我们的映射过程现在能更好地将暗颜色映射到其他暗颜色,而且由于我们避免了来自上方和下方颜色映射到的表面点,因此它将颜色更均匀地映射到目标色域表面。
我们还需要对这个映射过程做一个最后的修改。在下图的示例中,我们提供了一个颜色被显著去饱和的例子。

很难看清,但红线代表生成*LCusp* 的线,而黄线是从源颜色到*LCusp* 的线。我们可以看到源颜色移动到目标颜色,即使存在一个更接近的期望颜色,我们希望它映射到。当源颜色和 LCusp 之间的线几乎平行于色域表面时,就会发生这种情况,导致源颜色移动很远才与目标色域相交。当*LCusp* 接近最大亮度时(如上图所示),就会出现这种情况。
为了防止这种情况,我们将*LCusp* 限制在一个特定范围内,并将新点称为*LFocal* 。论文通过找到提供每个色相角度最小感知差异的值来选择范围。如果我们对上图应用此修复,我们将得到以下结果。

通过限制*LFocal* 在L 轴上的高度,我们确保亮颜色仍然向下映射且永远不会向上映射,从而防止严重的颜色去饱和。现在我们有了一个衍生的色域映射算法,作为 论文 (Masaoka-san 等人,2016 年) 的一个实现。在实际映射颜色时,给定一个色域外颜色,我们生成该颜色色相的色域,然后运行上述色域相交算法。下面我们提供一个最终的可视化,它将我们迄今为止描述的所有概念结合在一起,并展示了我们如何实际映射颜色。

现在我们知道了色域映射器本身是如何工作的,我们需要知道如何生成 3D 色域体积以便编写上述算法。为此,我们参考了 这篇论文 (Sun, Liu, Li 和 Zhou, 2014),该论文在 RGB 中生成了一系列恒定亮度的色域边界,为我们提供了色域的近似值。论文指出,在 RGB 立方体的表面上,如果有一个亮度为*A* 的点*X* 和一个亮度为*B* 的点*Y* ,那么在*X* 和*Y* 之间的直线上存在具有*A* 和*B* 之间所有亮度值的点。
然后,论文推导了一个插值公式,该公式找到*X* 和*Y* 之间的具有所需亮度值(介于 A 和 B 之间)的点。论文的公式似乎找到了具有不同亮度值的点,我们无法使其正常工作,因此我们推导了自己的插值公式。利用 RGB 立方体表面代表 RGB 中色域边界的事实,论文推导了一个公式,用于在 RGB 立方体表面上生成固定亮度的色域边界。算法如下:
给定一个亮度值,我们检查 RGB 立方体的每个边,看我们的亮度值是否落在该边端点的范围内。
如果是,我们使用我们的插值公式在该边上找到色域边界点。
完成每个边的操作后,我们得到一组点,然后将它们连接起来生成一个形状。该形状将是一个三角形或一个矩形,如下所示。


当前生成的形状具有连接整个立方体面线的直线,但实际的色域边界在 RGB 中可能是弯曲的,而不是直线。为了解决这个问题,我们需要在立方体面上更好地采样色域边界,我们通过在立方体面上生成线条来实现,如下图像所示。

上图中的红色点是我们之前在立方体边上找到的点,而黄色点是我们通过沿立方体面进行更多采样找到的新点。我们首先从角落生成这些线,并且我们知道这些线必须与色域边界曲线相交,因为曲线在两个红色点之间连续连接。然后,我们在每条生成的线上使用插值公式来找到具有我们亮度值的点,并像上面一样将它们连接起来。我们对我们的色域边界曲线触及的每个面都这样做,从而得到更精炼的色域边界。然后,我们为我们想要生成的每个平面重复此过程,得到一组 RGB 中的闭合曲线,这些曲线代表不同亮度的色域边界。下面我们展示了 RGB 立方体上色域边界的可视化。

在 CIELAB 空间中绘制上述曲线,得到以下结果。

当我们将颜色与我们的色域相交时,我们只需要对与色相平面相交的色域部分进行相交测试。上图中的方块代表色域边界平面与具有正色度的色相平面之间的交点。如果我们以色度为水平轴,亮度为垂直轴来绘制这些点,我们得到以下结果。

在上图的图像中,我们将相交点连接起来以生成色域形状。上图中的硬边仅为方便本文档显示;我们的色域映射器生成更多的色域边界曲线以更好地采样色域形状。通过与上图中的每条线段相交,我们可以轻松找到我们的颜色与目标色域的交点。
FreeSync HDR 通过禁用显示器的色域映射器来减少显示延迟,从而减少显示器侧的处理时间。在 FreeSync HDR 之前,游戏会进行色域映射然后将其发送到显示器,显示器也会对图像进行色域映射,导致应用了两次色域映射。借助 FreeSync HDR,只有游戏对帧进行色域映射,从而消除了由显示器的色域映射器引入的延迟。一个潜在的优化是,大多数剪裁色域映射器可以被减少到一个查找表(LUT),并且游戏目前将其他颜色处理技术(如色彩分级、色调映射等)减少到 LUT 中,并将它们合并为一个最终的 LUT。通过合并颜色处理 LUT 和色域映射 LUT,我们引入了非常小的性能成本,同时获得了 FreeSync HDR 带来的延迟降低的好处。
FreeSync HDR 的另一个好处是它为开发者提供了显示器原生色域的信息,使开发者能够充分利用显示器的颜色能力。没有 FreeSync HDR,游戏只能将它们的源色域映射到玩家显示器支持的任何标准颜色格式。例如,游戏可以在 Rec2020 中渲染世界,并尝试将其映射到一个输出 Rec709 的 SDR 显示器。
问题在于,大多数显示器都具有非标准的原生色域,可能比 Rec709 更大,但比 Rec2020 更小。因此,如果我们直接从 Rec2020 映射到 Rec709,我们可能会映射到我们的显示器无法显示的颜色,从而降低图像的潜在质量。通过使用 FreeSync HDR,开发者可以了解显示器的原生色域,并确保映射算法仅映射超出该色域的颜色,从而使算法显示更高质量的图像。
最后,FreeSync HDR 允许开发者选择最适合他们想要显示的内容的色域映射算法。例如,如果对于任何给定的帧,只有少数颜色超出色域,开发者会希望使用裁剪算法,因为它以低处理成本保留了游戏的观感。如果游戏有很多超出色域的颜色,开发者可以选择实现一个感知色域映射器,以更好地保留图像的观感。
在 FreeSync HDR 之前,开发者会应用他们的色域映射器,然后显示器会应用自己的色域映射器,这可能会以意想不到的方式裁剪或转换图像的某些部分。有了 FreeSync HDR,开发者可以完全控制色域映射过程,并致力于实现最适合他们试图显示的内容的算法。
FreeSync HDR 通过只应用一个色域映射器来处理游戏的帧,从而减少了冗余工作,使色域映射过程更有效率;它为开发者提供了显示器原生色域的完整信息,使开发者能够充分利用显示器的功能;并且它消除了显示器中的可变行为,为色域映射过程提供了更高的确定性。在下一篇文章中,我们将使用不同的渲染 API 实践 FreeSync HDR,并提供一个参考 FreeSync HDR 示例作为最终结果。