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