macOS 上 Metal 中的 sRGB colorPixelFormat 给出了奇怪的结果

Geo*_*rge 5 macos colors metal

背景:我试图在 iTerm2 中解决这个问题:https://gitlab.com/gnachman/iterm2/issues/6703#note_71933498

MTLPixelFormatBGRA8UnormiTerm2对所有纹理使用默认的像素格式,包括 MTKView 的可绘制对象。在我的机器上,如果我的纹理函数返回颜色C,则“以 sRGB 显示”中的数字色度计应用程序将显示屏幕颜色为C。我不知道为什么会这样!它应该将原始颜色值从我的纹理函数转换为 sRGB,从而导致它们发生变化,对吧?例如,当我的片段函数返回(0, 0.16, 0.20, 1)窗口的背景颜色时,我会看到以下内容:

证明非 sRGB 像素格式的纹理从片段函数获取 sRGB 值

这很奇怪,但我可以忍受它,直到有人抱怨它在他们的机器上不起作用(请参阅上面链接的问题)。对于他们来说,我的片段函数返回的值经过 Generic -> sRGb 转换,看起来是错误的。这是一个 hackintosh,所以它可能是一个驱动程序错误或其他什么,但它促使我更多地研究这个问题。

如果我在各处更改像素格式,那么MTLPixelFormatBGRA8Unorm_sRGB我会得到我无法解释的颜色:

在此输入图像描述

片段函数保持不变,返回(0, 0.16, 0.2, 1)。它正在渲染到我使用像素格式创建的纹理MTLPixelFormatBGRA8Unorm_sRGB(不是可绘制的,如果重要的话,尽管可绘制具有相同的像素格式),并且我可以在 GPU 调试器中看到该纹理被分配了您在上面看到的太亮的值。在此屏幕截图中,数字色度计显示“中间纹理”缩略图的颜色:

在此输入图像描述

这是片段函数(出于调试目的进行了修改):

fragment float4 iTermBackgroundColorFragmentShader(iTermBackgroundColorVertexFunctionOutput in [[stage_in]]) { return float4(0, 0.16, 0.20, 1); }

我还尝试破解 Apple 的示例应用程序( MetalTexturedMesh) 以使用 sRGB 像素格式并从其片段着色器返回相同的颜色,我得到了相同的结果。这是我对其所做的更改。我只能得出结论,我不明白 Metal 如何定义像素格式,并且我找不到任何合理的解释来给出我所看到的行为。

Ken*_*ses 4

原始字节位于7C 6E 00 FFBGRA 中。这当然不是数字色度计报告的结果,但也与我的预期相差甚远。

就是色度计的报告。该字节序列对应于 (0, 0.43, 0.49)。

我早就预料到了33 29 00 ff。由于纹理函数被硬编码为return float4(0, 0.16, 0.20, 1);并且像素格式为 sRGB,因此我期望0.2在蓝色通道中生成 0.2*255=51(十进制)= 0x33,而不是 0x7c。

你的期望是不正确的。在着色器中,颜色始终是线性的,而不是 sRGB。如果纹理是 sRGB,则读取该纹理时会从 sRGB 转换为线性,写入时会从线性转换为 sRGB。

给定着色器中的线性 (0, 0.16, 0.2),金属着色语言规范(PDF)第 7.7.7 节中描述的 sRGB 转换将产生 (0, 0.44, 0.48) 浮点和 (0, 111, 124) 又名 (0, 0x6f, 0x7c) RGBA8Unorm。这与您得到的非常接近。因此,对于 sRGB,结果似乎是正确的。

规范中给出的从线性到 sRGB 的转换算法是:

if (isnan(c)) c = 0.0;
if (c > 1.0)
    c = 1.0;
else if (c < 0.0)
    c = 0.0;
else if (c < 0.0031308)
    c = 12.92 * c;
else
    c = 1.055 * powr(c, 1.0/2.4) - 0.055;
Run Code Online (Sandbox Code Playgroud)

sRGB到线性的转换算法为:

if (c <= 0.04045)
    result = c / 12.92;
else
    result = powr((c + 0.055) / 1.055, 2.4);
Run Code Online (Sandbox Code Playgroud)

我还可以看到,它显然比没有金属绘制的相同颜色明亮得多(例如,在 -drawRect: 中)。

您是如何构建为这种情况绘制的颜色的?请注意,通用(又称校准)RGB 颜色空间不是线性的。它的伽马值约为 1.8。核心显卡具有kCGColorSpaceGenericRGBLinear线性。

神秘的是为什么当您使用非 sRGB 纹理时您会看到更深的颜色。着色器中的硬编码颜色应始终代表相同的颜色。纹理的像素格式不应该影响它最终的显示方式(在舍入误差范围内)。也就是说,它从线性转换为纹理的像素格式,并在显示时从纹理的像素格式转换为显示颜色配置文件。这是传递性的,因此结果应该与直接从线性到显示颜色配置文件相同。

您确定没有从该纹理中提取字节然后将它们解释为 sRGB 吗?或者也许使用-newTextureViewWithPixelFormat:...