我在搜索一个略有不同但相关的问题时遇到了这个问题,立即引起我注意的是顶部的渲染。它与我几个小时前制作的渲染相同,并且试图弄清楚为什么它没有意义,部分是导致我来到这里的原因。
对于读者:渲染是当您从{x ∈ [0, 1], y ∈ [0, 1], Y = 1} 转换为XYZ,将该颜色转换为sRGB,然后将各个组件固定为[0, 1] 时的结果。
乍一看,它看起来不错。乍一看,它看起来不太好……它似乎没有预期的那么饱和,并且在奇怪的角度有可见的过渡线。经过仔细检查,很明显,初选并没有顺利过渡到彼此。例如,红色和蓝色之间的大部分范围只是洋红色——R 和B 几乎在它们之间的整个距离上都是 100%。然后,当您添加一个检查以跳过绘制任何具有超出范围分量的颜色时,而不是钳制,一切都会消失。这一切都超出了范围。那到底是怎么回事?
我认为我已经了解了至少 80% 的比色法的一小部分,所以我将其设置出来,大大简化,以启发其他任何可能觉得它有趣或有用的人。我也尝试回答这个问题。
(⚠️ 在开始之前,重要的一点:xyY 空间中的有效 RGB 显示颜色可以在 CIE 1931 2° 标准观察者的边界之外。情况并非如此对于 sRGB,但 对于 Display P3、Rec. 2020、CIE RGB 和其他宽色域来说是这样。这是因为三个原色需要自己加起来才能达到白点,并且因此,即使是单色原色,与同等照明下的相同波长相比,也必须具有令人难以置信的、不自然的亮度。)
为色度图着色
xy 色度图不仅仅是xyY 空间的切片。它本质上是二维的。 xy 平面中的一个点代表 色度,除了 亮度,因此在一定程度上,它是尽可能最好地代表的颜色 色度,不是任何特定的颜色。通常,颜色似乎是该色度的最亮、最饱和的颜色,或者是显示器色彩空间中最接近的颜色,但这是一个任意的设计决定。
也就是说:如果绘制了说明性颜色,它们必然是虚构的,就像为选举地图着色纯粹是数据可视化的问题一样:方便以帮助理解。只是,在这种情况下,我们使用颜色来可视化比色法的一个方面,因此很容易将两者混为一谈。
(Image credit: Michael Horvath)
当我们考虑xyY 空间中可见光谱的完整 3D 形状时,颜色的虚假性和必要性变得显而易见。经典的光谱轨迹(“马蹄铁”)很容易被视为准直布罗陀体积的底部,在光谱轨迹处最宽,在@987654347 处变窄至顶点(白点) @。如果将其视为自上而下的投影,则位于光谱轨迹上和附近的颜色将非常暗(尽管仍然是该色度可能最亮的颜色),并且朝向中心变得越来越亮。如果将其视为xyY 体积的一部分,通过Y 的特定值,颜色将同样明亮,但整体上会变得更亮,并且边界的形状会随着Y 的增加而再次不均匀地缩小,直到完全消失。据我所知,这些可能性都没有多少实际用途(如果有的话),尽管它们可能很有趣。
相反,图表是由内而外着色:绘制的色域以最大强度着色(每个原色在其最亮处,然后是内部的线性混合),并且从内部色域三角形投射出色域外的颜色到光谱轨迹。这很烦人,因为您不能简单地使用矩阵变换将xy 平面上的点转换为可感知的颜色,但就实际传达有用且有些准确的信息而言,不幸的是,这似乎是不可避免的。
(澄清一下:实际上可以将单个色度点移动到 sRGB 空间中,并使用可能的最明亮饱和的 sRGB 颜色逐个像素地为色度图着色——这只是比简单的矩阵变换更复杂。为此,首先将三坐标 xyz 色度移动到 sRGB。然后将任何负值钳位到 0。最后,统一缩放分量,使得最大组件值为1。请注意,这可能比绘制白点和原色然后在它们之间进行插值要慢得多,具体取决于您的渲染方法和数据表示及其操作的效率。)
绘制光谱轨迹
获得特征马蹄形最直接的方法就是使用经验数据表。
(http://cvrl.ioo.ucl.ac.uk/index.htm,向下滚动以查找与其他面向非专业人士的来源最接近的“历史”数据集。他们选择数据的过于聪明的图标方案是虚线图标用于在 5nm 采样的数据,实线图标表示在 1nm 采样的数据。)
以点为顶点构造一条路径(您可能想从顶部修剪一些,我将其切回 700nm,即 CIERGB 红色原色),并将生成的形状用作蒙版。对于 1nm 样本,折线应该足够平滑以接近任何分辨率:不需要拟合贝塞尔曲线或诸如此类的东西。
(注意:仅为说明目的,仅显示每 5 个点。)
如果我们只想绘制由三角形{x = 0, y = 0}、{0, 1} 和{1, 0} 界定的标准马蹄铁,那么这就足够了。请注意,我们可以通过跳过x + y >= 1 所在的任何坐标来节省渲染时间。如果我们想做更复杂的事情,比如为不同的Y 值绘制变化的边界,那么我们正在讨论定义XYZ 空间的颜色匹配函数。
配色功能
(图片来源:User:Acdx - Own work, CC BY-SA 4.0)
XYZ 空间的基本事实是三个函数的形式,将 光谱功率分布 映射到 {X, Y, Z} 三刺激值。大量数据和计算用于构建XYZ 空间,但所有这些都融入了这三个函数,它们唯一地确定了给定光谱的{X, Y, Z} 值。实际上,函数所做的是定义 3 种虚构的原色,它们不能用任何实际光谱创建,但 可以 混合在一起以创建可感知的颜色。因为它们可以混合,所以XYZ 空间中的每个非负点在数学上都是有意义的,但并不是每个点都对应一种真实的颜色。
函数本身实际上被定义为查找表,而不是可以精确计算的方程。 Munsell 颜色科学实验室 (https://www.rit.edu/science/munsell-color-lab) 提供 1nm 分辨率的样品:向下滚动到“教育资源”下的“有用的颜色数据”。不幸的是,它是 Excel 格式的。其他来源可能会提供 5nm 数据,任何比 1nm 更精确的数据都可能是现代重建,可能无法与 1931 年的空间相通。
(出于兴趣:本文—http://jcgt.org/published/0002/02/01/—提供了在原始人类受试者数据的可变性范围内存在误差的分析近似值,但它们主要用于特定的用例。就我们的目的而言,最好也更简单地坚持经验采样数据。)
这些函数称为x̅、y̅ 和z̅(或x bar、y bar 和z bar。)统称为CIE 1931 2 度标准观察者。有一个单独的 1964 年标准观察者,由更宽的 10 度视场构成,差异很小,可以用来代替 1931 年的标准观察者,但可以说创建了不同的色彩空间。 (不应将 1964 标准观察者与单独的 CIE 1964 色彩空间混淆。)
要计算三色值,您需要 (1) 颜色光谱和 (2) 颜色匹配函数的内积。这只是意味着光谱中的每个点(或样本)都与颜色匹配函数中的对应点(或样本)相乘,用于重新加权数据。然后,您在整个可见光范围([360nm,830nm])上进行积分(或更准确地说是求和,因为我们正在处理离散样本)。函数被归一化,以便它们在曲线下具有相等的面积,因此相等的能谱(每个波长的采样值相同)将具有{X = Y = Z}。 (FWIW,Munsell Color Lab 数据已正确归一化,但它们总和为 106 并且由于某种原因发生了变化。)
再看一下xyY 空间的 3D 图,我们再次注意到熟悉的光谱轨迹形状似乎是 {Y = 0} 的体积形状,即这些颜色实际上是黑色的。现在这有点道理,因为它们是单色的,它们的光谱应该由一个点组成,因此当你对一个点进行积分时,你总是会得到 0。然而,这就提出了一个问题:既然其他两个函数也应该为0,那么它们的色度如何?
最简单的解释是形状底部的Y 实际上比零略大。采样的使用意味着单色光源的光谱不被视为瞬时值。相反,它们是接近其波长的窄带光谱。您可以任意接近瞬时值,并且仍然期望在精度范围内有意义的色度,因此采样带宽变为 0 时的限制是理想的光谱轨迹,即使它恰好在 0 处消失。但是,光谱轨迹实际上派生值仅根据 x̅、y̅ 和 z̅ 颜色匹配函数的单样本值计算得出。
这意味着您实际上只需要一组数据——x̅、y̅ 和 z̅ 的查找表。只需将x̅(wl) 和y̅(wl) 除以x̅(wl) + y̅(wl) + z̅(wl),即可根据每个波长计算光谱轨迹。
(图片来源:Apple,ColorSync Utility 截图)
有时您会看到这样的情节,一条引人注目的弧形彩虹色线在情节上方和周围俯冲,然后在光谱的最红色端回落至 0。这只是沿光谱轨迹绘制的y̅ 函数,按比例缩放为y̅ = Y。请注意,这不是可见色域的 3D 形状的轮廓。当以二维方式绘制时,这样的轮廓将在光谱轨迹内通过蓝绿色范围。
在 XYZ 空间中描绘可见光谱
最后的问题变成了:给定这三个颜色匹配函数,我们如何使用它们来确定给定的{X, Y, Z} 是否在人类颜色感知的范围内?
有用的事实:你不能自己拥有光度。对于其他功能中的一个或两个,任何真实颜色也将具有非零值。我们也知道Y 的定义范围是[0, 1],所以我们实际上只是在讨论确定{X, Z} 是否对给定的Y 有效。
现在问题变成了:当由y̅ 加权时,哪些光谱(为我们的目的而简化:波长 [360nm、830nm]、带宽 1nm 的 471 个值(0 或 1)的数组)将求和到Y?
XYZ 空间是加法的,就像 RGB 一样,因此任何非单色光都等效于各种强度的单色光的线性组合。换句话说,光谱轨迹内的任何点都可以通过恰好位于边界上的点的某种组合来创建。如果您采用单色 CIE RGB 原色并将它们的三色值相加,您会得到白色,而该白色的光谱将只是叠加的三个原色的光谱,每个原色的波长上都有一个细带。
因此,单色的每一种可能组合都在人类视觉的范围内。然而,有大量的重叠:不同的光谱可以产生相同的感知颜色。这称为同色异谱。因此,虽然列举每个可能单独可以产生它们的可感知颜色或光谱可能是不切实际的,但实际上计算空间的整体形状相对容易可枚举的频谱集。
我们所做的是逐个波长地遍历色域,并且对于给定的波长,我们从该点开始迭代地对越来越大的光谱切片求和,直到我们达到Y 目标或运行超出范围。你可以把它想象成绕着一个圆转,从一个起点开始画出逐渐变大的弧线,然后画出最终形状的中心——当你到达一个刚好是整个圆的弧线时,中心重合,你会得到白色,但是在那之前,您绘制的点将从边缘向内螺旋。从圆周上的每个点重复此操作,您将沿着每条可能的路径盘旋,覆盖整个色域。实际上,您有时可以在 3D 色彩空间图中看到这种螺旋效应。
在实践中,这采用两个循环的形式,外循环从 360 到 830,内循环从 1 到 470。在我的实现中,我为内循环所做的是保存当前和最后求和值,一旦总和超过目标,我就使用差值来计算带的小数,并将外循环的计数器和插值宽度推到数组上,然后跳出内循环。对波段进行插值可以极大地平滑曲线,尤其是在船头。
一旦我们有了一组正确亮度的光谱,我们就可以计算它们的X 和Z 值。为此,我有一个更高阶的summation 函数,该函数将函数传递给求和和间隔。从那里,Y 的色度图上的色域形状只是由派生的{x, y} 坐标形成的路径,因为此方法仅枚举色域的表面,没有内部点。
实际上,这是已接受答案中提到的库之类的库的更简单版本:它们通过耗尽连续光谱空间来创建 3D 网格,然后在点之间进行插值以确定确切的颜色是否在内部或色域之外。是的,这是一种蛮力的方法,但它简单、快速且有效,足以用于演示和可视化目的。在浏览器中渲染色度空间整体形状的 20 步等高线图实际上是即时的,例如,几乎完美的曲线。
有几个地方无法完全消除精度不足的问题:尤其是靠近橙色的两个角被剪掉了。这是由于该区域中部分和线的形状是(1)几乎完全水平和(2)在拐角处具有硬尖的组合。由于恰好位于尖端的点不是很好的偶数值Y,因此轮廓的平坦度更成问题,因为它们垂直于尖端的大部分垂直线,因此插值点以适合任何给定的Y 将是该地区最悲观的。另一个问题是这些点不是均匀分布的,它们集中在非常靠近尖端的地方:角落的剪裁对应于插值外围点的情况。所有这些问题都可以在此图中清楚地看到(为了清晰起见,使用 20nm 箱进行渲染,但同样,更高的精度并不能消除问题):
结论
当然,这是一种技术含量高且容易陷入陷阱的问题 (PPP),通常最好将其外包给高质量的 3rd 方库。然而,了解其背后的基本技术和科学,揭开整个过程的神秘面纱,并帮助我们有效地使用这些库,并根据需求变化调整我们的解决方案。