首发于 RGBA

[总结] 漫谈HDR和色彩管理(二)颜色空间

这一节我们主要看具体Color Space的相关定义和颜色空间变换相关的内容,包括:

  • 定义一个完整的颜色空间(Color Space),包括三原色、白点和传递函数
  • 常见的颜色空间标准及其定义

讲之前还是要吹一波动视的R&D工作,不仅仅是专业度和深度,PPT制作得也非常良心和通俗易懂,这次的 HDR in Call of Duty 同样水平极高,以下大量可视化内容来自这篇演讲,respect。再吹一波 CG Cinematography ,是一本CG行业的美术大佬最近刚刚完成的电子书,涵盖范围从色彩管理到打光参考,质量极佳,仍在更新,非常推荐,respect。

# 颜色空间(Color Space)

上一节最后我们学会如何看懂色度图,即CIE 1931 xy chromaticity diagram。这张色度图定义的CIE 1931 xy color space基本包含了我们能够感知到的最大色域,这也让它成为我们定义其他颜色空间所使用的标准空间。通常,我们会指定一个RGB颜色空间,它定义了当前工作空间(workspace)下我们所关心的所有颜色值域范围:

来源:https://www.colour-science.org/posts/the-importance-of-terminology-and-srgb-uncertainty/

如上所示,RGB颜色空间本质是一个三维空间(左图),但为了方便可视化,我们通常会在CIE 1931 xy色度图里来表示它的色度范围(右下图),上面的黑点表示右上图的所有像素在这个RGB颜色空间的位置。那么如何在数学上定义这样一个完整的RGB颜色空间呢?它需要包含以下三个组成部分:

  • 三原色(Primaries)
  • 白点(Whitepoint)
  • 传递函数(Transfer Functions)

## 三原色(Primaries)

为了定义一个RGB颜色空间的三原色,我们通常会在CIE 1931 xy色度图里中指定它的三原色的xy坐标,这些三原色定义了这个颜色空间的色域(gamut)。例如我们熟悉的sRGB颜色空间三原色的xy坐标如下:

来源:HDR in Call of Duty

这些三原色就是色度图中三角形的三个顶点坐标,它们分别表示了这个新的RGB颜色空间中Red (1, 0, 0)、Green (0, 1, 0)、Blue (0, 0, 1)在色度图中的位置:

The color primaries tells us what the primary colors Red (1,0,0), Green (0,1,0) and Blue(0,0,1) in our new color space map to in terms of real world chromaticities.
—— From HDR in Call of Duty

以下给出几种常见的颜色空间以及它们的三原色和白点(后面会详细讲白点)的位置(更多其他空间的三原色信息可以参见 wiki ):

色度图中的这些颜色空间三角形给了我们一个比较不同颜色空间范围的可视化方式,范围越大的颜色空间可以访问到更鲜亮的颜色,这在画面颜色体验上是非常不同的感受( 符华的MMD 可以下载HDR版本,在iPhone手机用nPlayer看HDR版本和在电脑上看普通版本是完全不一样的体验)。以下在色度图里比较各种颜色空间的色域范围:

来源:https://chrisbrejon.com/cg-cinematography/chapter-1-color-management/

## 白点(White Point)

我们之前说过,CIE 1931 xy色度图已经是经过降维后的二维空间了,这个空间只能表示色度信息而丢失了亮度信息,而我们实际需要使用的颜色空间仍然是三维空间。要想得到完整的颜色信息,我们需要定义一个 白点(White Point)

The whitepoint defines the white color for a given RGB color space. Any set of colors lying on the neutral axis passing through the whitepoint, no matter their luminance, will be neutral to that RGB colorspace.
—— From this article

例如,我们之前提到的sRGB颜色空间的白点D65的位置如下(xy坐标为(0.31271, 0.32902)):

来源:HDR in Call of Duty

白点的定义

简单来说,白点定义了这个RGB颜色空间中纯白色 (1, 1, 1)在色度图上的位置。下面的可视化视频表示了这个定义过程:

来源:HDR in Call of Duty https://www.zhihu.com/video/1235214514741600256

解释一下视频里的内容:利用三原色在色度图里的xy坐标可以它们在XYZ坐标空间中的向量方向,为了方便计算我们可以只考虑它们的单位向量方向记为(Rx, Ry, Rz)、(Gx, Gy, Gz)和(Bx, By, Bz)。通过缩放并叠加这三个单位向量方向我们可以得到这个颜色空间的任意一点的空间位置,即P = (Rx, Ry, Rz) * r + (Gx, Gy, Gz) * g + (Bx, By, Bz) * b,其中r、g、b分别表示三个向量的缩放大小。那么,我们可以找到一组(r, g, b)系数使得P = 白点位置。也就是说,通过这个白点我们可以定义三原色向量的 相对长度关系 (在之前的学习过程中,我总是搞不清楚色度图中的这个白点和RGB颜色空间里真正(1, 1, 1)纯白色的对应关系。用我自己的理解来说,色度图中的白点坐标是RGB三维颜色空间中纯白色(1, 1, 1)在二维xy空间中的投影,就如同三原色的xy坐标是RGB三维颜色空间中纯红色(1, 0, 0)、纯绿色(0, 1, 0)、纯蓝色(0, 0, 1)在二维xy空间中的投影一样,也就是说,色度图中的位置都是定义了真正RGB颜色空间各个点的投影位置)。那么有了相对长度关系,我们可以通过等比例缩放这组(r, g, b)系数来访问到原点到白点这条射线上的所有点的位置,其中有一个点是我们特别关心的——亮度值即纵坐标Y = 1的白点,这个特殊的三维空间白点位置定义了三原色的 绝对长度值 。通过这样的方式,最终我们可以在XYZ颜色空间里定义新的RGB颜色空间的三原色索引向量基Rxyz、Gxyz、Bxyz,其缩放范围均为0到1,并且当其三者的缩放值为(1, 1, 1)的时候我们可以到达亮度为1的白点Wxyz。这三个值域范围是0到1的向量基就定义了一个真正的三维RGB颜色空间范围。

还是有点绕,程序员信奉——Talk is cheap, show me the code! 下面我们以Rec. 709 / sRGB空间为例,推导下在XYZ颜色空间里它的三原色向量基的值。推导的伪代码参上:

// 我们需要求解新的RGB颜色空间(sRGB)的三原色在XYZ颜色空间的索引值:
Rxyz = (Rx, Ry, Rz)
Gxyz = (Gx, Gy, Gz)
Bxyz = (Bx, By, Bz)
// 通过色度图中的三原色和白点坐标,我们可知:
Rxyz = (0.64, 0.33, 0.03) * r
Gxyz = (0.30, 0.60, 0.10) * g
Bxyz = (0.15, 0.06, 0.79) * b
// 白点在XYZ颜色空间的索引值为
Wxyz = (Wx, 1, Wz)
     = (0.31271, 0.32902, 0.35827) * w
     = |Rx Gx Bx| |1|
       |Ry Gy By| |1|
       |Rz Gz Bz| |1|
// 那么有:
w = 3.03933
Wxyz = (0.95043, 1, 1.08890)
// 结合两者有:
0.64r + 0.30g + 0.15b = 0.95043
0.33r + 0.60g + 0.06b = 1
0.03r + 0.10g + 0.79b = 1.08890
// 求解可得:
r = 0.644463125
g = 1.191920333
b = 1.202916667
// 那么sRGB颜色空间的三原色在XYZ颜色空间的索引值为:
Rxyz = (0.4124564, 0.2126729, 0.0193339)
Gxyz = (0.3575761, 0.7151522, 0.1191920)
Bxyz = (0.1804375, 0.0721750, 0.9503041)

通过这样的方式,我们就可以靠色度图中的三原色和白点xy坐标还原sRGB颜色空间在XYZ空间中的三维体空间:

来源:HDR in Call of Duty

颜色空间变换

显而易见,有了上述Rxyz、Gxyz和Bxyz的坐标值,我们就可以在新的RGB颜色空间和XYZ颜色空间之间做颜色转换。以下为列矩阵形式的变换矩阵:

RGB_2_XYZ_Mat = |Rx Gx Bx|
                |Ry Gy By|
                |Rz Gz Bz|
XYZ_2_RGB_Mat = inverse(RGB_2_XYZ_Mat)

更妙的是,我们可以以XYZ颜色空间为中间者、在任意两个颜色空间之间做转换。通常我们会离线计算出这些颜色空间变换矩阵,把它们的结果值直接存起来供实时颜色空间变换使用。以UE4源码为例,我们可以在ACES.ush里找到大量这样的变换矩阵:

// REC 709 primaries
static const float3x3 XYZ_2_sRGB_MAT =
  3.2409699419, -1.5373831776, -0.4986107603,
 -0.9692436363,  1.8759675015, 0.0415550574,
  0.0556300797, -0.2039769589, 1.0569715142,
static const float3x3 sRGB_2_XYZ_MAT =
  0.4124564, 0.3575761, 0.1804375,
  0.2126729, 0.7151522, 0.0721750,
  0.0193339, 0.1191920, 0.9503041,