HDR 颜色-space 转换导致负 RGB 值(Yxy 到 XYZ 到 sRGB)
HDR color-space transformations result in negative RGB values (Yxy to XYZ to sRGB)
我目前正在将 HDR 添加到旧引擎中,无意中发现了颜色-space 转换问题。
- 我用 Yxy 颜色定义灯光 space
- 然后我将 Yxy 转换为 XYZ
- XYZ 到 sRGB 转换。
- 渲染时使用 RGB 值 > 1.0,最后使用色调映射对结果进行归一化。
我正在处理相当大的数字,因为主要光源是具有高达 150k Lux 照度的太阳。
YxyToXYZ 函数
osg::Vec3 YxyToXYZ( const osg::Vec3& Yxy )
{
if ( Yxy[2] > 0.0f )
{
return osg::Vec3( Yxy[0] * Yxy[1] / Yxy[2] , Yxy[0] , Yxy[0] * ( 1.0f - Yxy[1] - Yxy[2]) / Yxy[2] );
}
else
{
return osg::Vec3( 0.0f , 0.0f , 0.0f );
}
}
XYZtosRGB
osg::Vec3 XYZToSpectralRGB( const osg::Vec3& XYZ )
{
// Wikipedia matrix
osg::Vec3 rgb;
rgb[0] = 3.240479 * XYZ[0] - 1.537150 * XYZ[1] - 0.498535 * XYZ[2];
rgb[1] = -0.969256 * XYZ[0] + 1.875992 * XYZ[1] + 0.041556 * XYZ[2];
rgb[2] = 0.055648 * XYZ[0] - 0.204043 * XYZ[1] + 1.057311 * XYZ[2];
std::cout << "newrgb rgb r:" << rgb[0] << " g:" << rgb[1] << " b:" << rgb[2] << std::endl;
// The matrix in pbrt book p. 235 gives unexpected results. We expect that if we have
// x = y = 0.33333 we get a white pixel but that matrix gives red. Hence we use a different
// matrix that is often used by 3D people
rgb[0] = 2.5651 * XYZ[0] -1.1665 * XYZ[1] -0.3986 * XYZ[2];
rgb[1] = -1.0217 * XYZ[0] + 1.9777 * XYZ[1] + 0.0439 * XYZ[2];
rgb[2] = 0.0753 * XYZ[0] -0.2543 * XYZ[1] + 1.1892 * XYZ[2];
std::cout << "oldrgb rgb r:" << rgb[0] << " g:" << rgb[1] << " b:" << rgb[2] << std::endl;
return rgb;
}
测试样本:
Yxy Y:1 x:1 y:1
XYZ X:1 Y:1 Z:-1
newrgb rgb r:2.20186 g:0.86518 b:-1.20571
oldrgb rgb r:1.7972 g:0.9121 b:-1.3682
Yxy Y:25 x:0.26 y:0.28
XYZ X:23.2143 Y:25 Z:41.0714
newrgb rgb r:16.3211 g:26.106 b:39.616
oldrgb rgb r:14.0134 g:27.5275 b:44.2327
Yxy Y:3100 x:0.27 y:0.29
XYZ X:2886.21 Y:3100 Z:4703.45
newrgb rgb r:2242.7 g:3213.56 b:4501.09
oldrgb rgb r:1912.47 g:3388.51 b:5022.34
Yxy Y:6e+06 x:0.33 y:0.33
XYZ X:6e+06 Y:6e+06 Z:6.18182e+06
newrgb rgb r:7.13812e+06 g:5.69731e+06 b:5.64573e+06
oldrgb rgb r:5.92753e+06 g:6.00738e+06 b:6.27742e+06
问题:
- 我想负值应该被限制掉?还是我的计算有误?
- 这 2 个矩阵产生相似但不同的值(非常接近 sRGB 的原色)。我想用 Wiki 替换旧矩阵(我不知道它来自哪里)。有谁知道旧矩阵的来源以及哪个是正确的?
- 我在@Yxy to RGB conversion 找到了部分答案,这听起来像是来自一位前同事:)...但没有解决我的问题。
非常感谢
你的 newrgb rgb
计算是正确的,我使用 Colour:
得到相同的输出
import colour
xyY = (1.0, 1.0, 1.0)
XYZ = colour.xyY_to_XYZ(xyY)
print(colour.XYZ_to_sRGB(XYZ, apply_encoding_cctf=False))
xyY = (0.26, 0.28, 25.0)
XYZ = colour.xyY_to_XYZ(xyY)
print(colour.XYZ_to_sRGB(XYZ, apply_encoding_cctf=False))
xyY = (0.27, 0.29, 3100.0)
XYZ = colour.xyY_to_XYZ(xyY)
print(colour.XYZ_to_sRGB(XYZ, apply_encoding_cctf=False))
xyY = (0.33, 0.33, 6e+06)
XYZ = colour.xyY_to_XYZ(xyY)
print(colour.XYZ_to_sRGB(XYZ, apply_encoding_cctf=False))
# [ 2.2020461 0.86530782 -1.20530687]
# [ 16.31921754 26.10605109 39.60507773]
# [ 2242.49706509 3213.58480355 4499.85141917]
# [ 7138073.69325106 5697605.86069197 5644291.15301836]
你在第一次转换中得到负值,因为你的 xy
色度坐标在光谱轨迹之外,因此它们代表虚构的颜色。
维基百科矩阵是正确的,是 IEC 61966-2-1:1999
中的矩阵,它是 sRGB
色彩空间的官方标准。
我目前正在将 HDR 添加到旧引擎中,无意中发现了颜色-space 转换问题。
- 我用 Yxy 颜色定义灯光 space
- 然后我将 Yxy 转换为 XYZ
- XYZ 到 sRGB 转换。
- 渲染时使用 RGB 值 > 1.0,最后使用色调映射对结果进行归一化。
我正在处理相当大的数字,因为主要光源是具有高达 150k Lux 照度的太阳。
YxyToXYZ 函数
osg::Vec3 YxyToXYZ( const osg::Vec3& Yxy )
{
if ( Yxy[2] > 0.0f )
{
return osg::Vec3( Yxy[0] * Yxy[1] / Yxy[2] , Yxy[0] , Yxy[0] * ( 1.0f - Yxy[1] - Yxy[2]) / Yxy[2] );
}
else
{
return osg::Vec3( 0.0f , 0.0f , 0.0f );
}
}
XYZtosRGB
osg::Vec3 XYZToSpectralRGB( const osg::Vec3& XYZ )
{
// Wikipedia matrix
osg::Vec3 rgb;
rgb[0] = 3.240479 * XYZ[0] - 1.537150 * XYZ[1] - 0.498535 * XYZ[2];
rgb[1] = -0.969256 * XYZ[0] + 1.875992 * XYZ[1] + 0.041556 * XYZ[2];
rgb[2] = 0.055648 * XYZ[0] - 0.204043 * XYZ[1] + 1.057311 * XYZ[2];
std::cout << "newrgb rgb r:" << rgb[0] << " g:" << rgb[1] << " b:" << rgb[2] << std::endl;
// The matrix in pbrt book p. 235 gives unexpected results. We expect that if we have
// x = y = 0.33333 we get a white pixel but that matrix gives red. Hence we use a different
// matrix that is often used by 3D people
rgb[0] = 2.5651 * XYZ[0] -1.1665 * XYZ[1] -0.3986 * XYZ[2];
rgb[1] = -1.0217 * XYZ[0] + 1.9777 * XYZ[1] + 0.0439 * XYZ[2];
rgb[2] = 0.0753 * XYZ[0] -0.2543 * XYZ[1] + 1.1892 * XYZ[2];
std::cout << "oldrgb rgb r:" << rgb[0] << " g:" << rgb[1] << " b:" << rgb[2] << std::endl;
return rgb;
}
测试样本:
Yxy Y:1 x:1 y:1
XYZ X:1 Y:1 Z:-1
newrgb rgb r:2.20186 g:0.86518 b:-1.20571
oldrgb rgb r:1.7972 g:0.9121 b:-1.3682
Yxy Y:25 x:0.26 y:0.28
XYZ X:23.2143 Y:25 Z:41.0714
newrgb rgb r:16.3211 g:26.106 b:39.616
oldrgb rgb r:14.0134 g:27.5275 b:44.2327
Yxy Y:3100 x:0.27 y:0.29
XYZ X:2886.21 Y:3100 Z:4703.45
newrgb rgb r:2242.7 g:3213.56 b:4501.09
oldrgb rgb r:1912.47 g:3388.51 b:5022.34
Yxy Y:6e+06 x:0.33 y:0.33
XYZ X:6e+06 Y:6e+06 Z:6.18182e+06
newrgb rgb r:7.13812e+06 g:5.69731e+06 b:5.64573e+06
oldrgb rgb r:5.92753e+06 g:6.00738e+06 b:6.27742e+06
问题:
- 我想负值应该被限制掉?还是我的计算有误?
- 这 2 个矩阵产生相似但不同的值(非常接近 sRGB 的原色)。我想用 Wiki 替换旧矩阵(我不知道它来自哪里)。有谁知道旧矩阵的来源以及哪个是正确的?
- 我在@Yxy to RGB conversion 找到了部分答案,这听起来像是来自一位前同事:)...但没有解决我的问题。
非常感谢
你的 newrgb rgb
计算是正确的,我使用 Colour:
import colour
xyY = (1.0, 1.0, 1.0)
XYZ = colour.xyY_to_XYZ(xyY)
print(colour.XYZ_to_sRGB(XYZ, apply_encoding_cctf=False))
xyY = (0.26, 0.28, 25.0)
XYZ = colour.xyY_to_XYZ(xyY)
print(colour.XYZ_to_sRGB(XYZ, apply_encoding_cctf=False))
xyY = (0.27, 0.29, 3100.0)
XYZ = colour.xyY_to_XYZ(xyY)
print(colour.XYZ_to_sRGB(XYZ, apply_encoding_cctf=False))
xyY = (0.33, 0.33, 6e+06)
XYZ = colour.xyY_to_XYZ(xyY)
print(colour.XYZ_to_sRGB(XYZ, apply_encoding_cctf=False))
# [ 2.2020461 0.86530782 -1.20530687]
# [ 16.31921754 26.10605109 39.60507773]
# [ 2242.49706509 3213.58480355 4499.85141917]
# [ 7138073.69325106 5697605.86069197 5644291.15301836]
你在第一次转换中得到负值,因为你的 xy
色度坐标在光谱轨迹之外,因此它们代表虚构的颜色。
维基百科矩阵是正确的,是 IEC 61966-2-1:1999
中的矩阵,它是 sRGB
色彩空间的官方标准。