我是否需要在现代计算机/显示器上对最终颜色输出进行伽玛校正

Pet*_*ark 21 opengl graphics rendering gamma

我一直假设我的伽马校正管道应该如下:

  • 对于(GL_SRGB8_ALPHA8)中加载的所有纹理使用sRGB格式,因为所有艺术程序都会对其文件进行预伽玛校正.从GL_SRGB8_ALPHA8着色器中的纹理采样时,OpenGL将自动转换为线性空间.
  • 在线性空间中进行所有照明计算,后期处理等.
  • 写入将在屏幕上显示的最终颜色时转换回sRGB空间.

请注意,在我的情况下,最终的颜色写入涉及我从FBO(线性RGB纹理)写入后缓冲区.

我的假设受到挑战,好像我在最后阶段进行了伽马校正,我的颜色比它们应该更亮.我设立了由我的价值灯光绘制纯色{255,106,0},但是当我使我得到的{255,171,0}(由打印筛选和颜色采摘确定).而不是橙色我变黄了.如果我没有在最后一步Gamma校正我得到完全相同的{255,权值106,0}.

根据一些资源,现代液晶屏幕模仿CRT伽玛.他们总是吗?如果没有,我怎么知道我是否应该伽马正确?我在其他地方出错了吗?


编辑1

我现在已经注意到,即使我用光写的颜色是正确的,我使用纹理颜色的地方也不正确(但是如果没有伽马校正,我的预期会更暗).我不知道这种差异来自何处.


编辑2

在尝试GL_RGBA8我的纹理而不是之后GL_SRGB8_ALPHA8,即使在光照计算中使用纹理值(如果我将光的强度减半,输出颜色值减半),一切看起来都很完美.

我的代码不再考虑任何地方的伽玛校正,我的输出看起来正确.

这让我更加困惑,不再需要/使用伽玛校正?


编辑3 - 响应datenwolf的回答

经过一些实验后,我对这里的几点感到困惑.

1 - 大多数图像格式都是非线性存储的(在sRGB空间中)

我加载了一些图像(在我的例子中是.png和.bmp图像)并检查了原始二进制数据.在我看来,好像图像实际上是在RGB颜色空间中,就好像我将像素值与图像编辑程序与我在程序中得到的字节数组进行比较,它们完美匹配.由于我的图像编辑器给了我RGB值,这表示存储在RGB中的图像.

我正在使用stb_image.h/.c来加载我的图像并一直加载.png并且没有看到加载时伽玛校正图像的任何地方.我还在十六进制编辑器中检查了.bmps,并且磁盘上的值与它们匹配.

如果这些图像实际存储在线性RGB空间的磁盘上,我应该如何(以编程方式)知道何时指定图像是在sRGB空间中?是否有某种方法可以查询更具特色的图像加载器可能提供的内容?或者由图像创建者来保存他们的图像作为伽马校正(或不校正) - 意味着建立一个约定并遵循它给定一个项目.我问了几位艺术家,他们都不知道伽马校正是什么.

如果我指定我的图像是sRGB,它们太暗了,除非我最后进行伽马校正(如果监视器使用sRGB输出,这是可以理解的,但请参见第2点).

2 - "在大多数计算机上,有效扫描输出LUT是线性的!这意味着什么?"

我不确定我能在你的回复中找到这个想法的结果.

据我所知,经过实验,我测试了所有显示器的输出线性值.如果我绘制全屏四边形并在着色器中使用硬编码值对其进行着色而不进行伽马校正,则显示器会显示我指定的正确值.

我从你的回答和我的结果中引用的句子会让我相信现代监视器输出线性值(即不模仿CRT伽玛).

我们的应用程序的目标平台是PC.对于这个平台(不包括带有CRT或真正旧显示器的人),对#1做出任何响应都是合理的,然后对#2进行伽马校正(即不执行最终的RGB-> sRGB转换 - 手动或使用GL_FRAMEBUFFER_SRGB)?

如果是这样的话,那GL_FRAMEBUFFER_SRGB的平台是什么(或者今天有效使用它的平台),或者是使用线性RGB的监视器真的那么新(假设GL_FRAMEBUFFER_SRGB是2008年推出的那样)?

-

我和我学校的其他一些图形开发人员谈过,并且从它的声音来看,他们都没有考虑伽马校正,他们没有注意到任何不正确的事情(有些人甚至都没有意识到).一个开发人员特别说他在考虑伽玛时得到了不正确的结果,因此他决定不担心伽玛.鉴于我在线/看到我的项目时出现的信息存在冲突,我不确定在我的目标平台项目中该怎么做.


编辑4 - 响应datenwolf的更新答案

确实是的.如果在信号链中的某处应用了非线性变换,但是所有像素值都未从图像中修改为显示,则该非线性已经预先应用于图像的像素值.这意味着,图像已经处于非线性色彩空间中.

如果我正在检查显示器上的图像,您的回答对我来说是有意义的.可以肯定我是清楚的,当我说我正在研究该图像的字节数组,我的意思是我正在研究在内存中数值的纹理,而不是屏幕上的图像输出(我没有为点#做2) .对我来说,唯一可以看到你说的是真实的方式就是图像编辑器给我的sRGB空间值.

另请注意,我确实尝试检查显示器上的输出,以及修改纹理颜色(例如,除以一半或加倍),输出显示正确(使用我在下面描述的方法测量).

你是如何测量信号响应的?

不幸的是,我的测量方法比你的方法要粗糙得多.当我说我在我的显示器上进行实验时,我的意思是输出纯色全屏四边形,其颜色在着色器中硬编码为简单的OpenGL帧缓冲区(写入时不进行任何色彩空间转换).当我输出白色,75%灰色,50%灰色,25%灰色和黑色时,显示正确的颜色.现在,我对正确颜色的解释肯定是错误的.我拍了一个截图然后使用图像编辑程序来查看像素的值是什么(以及视觉评估以确保值有意义).如果我理解正确,如果我的显示器是非线性的,我需要在将它们呈现给显示设备之前执行RGB-> sRGB转换,以使它们正确.

我不会撒谎,我觉得我在这里有点偏离我的深度.我正在考虑我可能会因为我的第二个混淆点(最终的RGB-> sRGB转换)而设置的解决方案将是一个可调整的亮度设置,并默认它在我的设备上看起来正确(没有伽马校正).

dat*_*olf 27

首先,您必须了解应用于颜色通道的非线性映射通常不仅仅是简单的幂函数.sRGB非线性可以近似约x ^ 2.4,但这并不是真正的交易.无论如何,您的主要假设或多或少是正确的.

如果纹理以更常见的图像文件格式存储,它们将包含显示在图形扫描输出中的值.现在有两种常见的硬件方案:

  • 扫描输出接口输出线性信号,然后显示设备将在内部应用非线性映射.由于它们的物理原因,旧的CRT显示器是非线性的:放大器只能在电子束中放入如此多的电流,磷光体饱和等等 - 这就是为什么首先引入整个伽玛物质来模拟CRT显示器的非线性.

  • 现代LCD和OLED显示器在其驱动放大器中使用电阻梯,或者在其图像处理器中具有伽马斜坡查找表.

  • 然而,一些设备是线性的,并要求图像产生设备在扫描输出上为所需的输出颜色配置文件提供适当的匹配LUT.

在大多数计算机上,有效扫描输出LUT是线性的!这意味着什么?稍微绕道而行:


为了说明,我很快将我的笔记本电脑的模拟显示输出(VGA连接器)连接到我的模拟示波器:蓝色通道到示波器通道1,绿色通道到示波器通道2,外部触发在线同步信号(HSync).一个快速而肮脏的OpenGL程序,故意用立即模式编写,用于生成线性颜色渐变:

#include <GL/glut.h>

void display()
{
    GLuint win_width = glutGet(GLUT_WINDOW_WIDTH);
    GLuint win_height = glutGet(GLUT_WINDOW_HEIGHT);

    glViewport(0,0, win_width, win_height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, 1, 0, 1, -1, 1);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glBegin(GL_QUAD_STRIP);
        glColor3f(0., 0., 0.);
        glVertex2f(0., 0.);
        glVertex2f(0., 1.);
        glColor3f(1., 1., 1.);
        glVertex2f(1., 0.);
        glVertex2f(1., 1.);
    glEnd();

    glutSwapBuffers();
}

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);

    glutCreateWindow("linear");
    glutFullScreen();
    glutDisplayFunc(display);

    glutMainLoop();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

图形输出使用Modeline配置

"1440x900_60.00"  106.50  1440 1528 1672 1904  900 903 909 934 -HSync +VSync
Run Code Online (Sandbox Code Playgroud)

(因为这是平板运行的相同模式,我使用的是克隆模式)

  • 绿色通道上的gamma = 2 LUT.
  • 蓝色通道上的线性(gamma = 1)LUT

这是单个扫描输出线的信号的样子(上面的曲线:Ch2 =绿色,下面的曲线:Ch1 =蓝色):

模拟视频信号Gamma = 1,Gamma = 2

您可以清楚地看到x⟼x²和x⟼x映射(抛物线和曲线的线性形状).


现在经过这个小小的绕道后,我们知道,到达主帧缓冲区的像素值会按原样运行:OpenGL线性斜坡不再进行任何更改,只有当应用了非线性扫描输出LUT时,它才会改变发送到显示器的信号.

无论哪种方式,您提供给扫描输出的值(这意味着屏幕上的帧缓冲区)将在信号链中的某个点进行非线性映射.并且对于所有标准消费者设备,该映射将根据sRGB标准,因为它是最小的公共因子(即,在大多数输出​​设备上可以再现sRGB颜色空间中表示的图像).

由于大多数程序(如web浏览器)假设输出经过sRGB显示颜色空间映射,因此它们只是将标准图像文件格式的像素值原样复制到屏幕上的帧,而不执行颜色空间转换,从而暗示这些图像中的颜色值是在sRGB颜色空间中(或者如果图像颜色配置文件不是sRGB,它们通常只会转换为sRGB); 正确的事情(如果,并且只有当写入帧缓冲区的颜色值被扫描到显示器不变时;假设扫描输出LUT是显示器的一部分),将转换为显示器所需的指定颜色配置文件.

但是这意味着,屏幕上的帧缓冲区本身处于sRGB色彩空间中(我不想分开关于如何愚蠢的问题,让我们接受这个事实).

如何将它与OpenGL结合在一起?首先,OpenGL线性地完成所有颜色操作.然而,由于扫描输出预计会出现在某些非线性色彩空间中,这意味着OpenGL渲染操作的最终结果必然会出现在屏幕上的帧缓冲区颜色空间中.

这是ARB_framebuffer_sRGB扩展(以OpenGL-3为核心)进入图片的地方,它引入了用于配置窗口像素格式的新标志:

New Tokens

    Accepted by the <attribList> parameter of glXChooseVisual, and by
    the <attrib> parameter of glXGetConfig:

        GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB             0x20B2

    Accepted by the <piAttributes> parameter of
    wglGetPixelFormatAttribivEXT, wglGetPixelFormatAttribfvEXT, and
    the <piAttribIList> and <pfAttribIList> of wglChoosePixelFormatEXT:

        WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB             0x20A9

    Accepted by the <cap> parameter of Enable, Disable, and IsEnabled,
    and by the <pname> parameter of GetBooleanv, GetIntegerv, GetFloatv,
    and GetDoublev:

        FRAMEBUFFER_SRGB                             0x8DB9
Run Code Online (Sandbox Code Playgroud)

因此,如果您有一个配置了这种sRGB像素格式的窗口在OpenGL中启用sRGB光栅化模式glEnable(GL_FRAMEBUFFER_SRGB);,则线性色彩空间渲染操作的结果将在sRGB色彩空间中进行转换.

另一种方法是将所有内容渲染到屏幕外FBO和后处理着色器中的颜色转换.

但这只是渲染信号链的输出端.您还以纹理的形式获得输入信号.这些通常是图像,其像素值非线性存储.因此,在将这些图像用于线性图像操作之前,必须首先将这些图像带入线性颜色空间.让我们暂时忽略,将非线性颜色空间映射到线性颜色空间会打开几个蠕虫本身 - 这就是为什么sRGB颜色空间如此荒谬,即避免这些问题.

因此,为了解决这个问题,我们EXT_texture_sRGB引入了一个扩展,它变得非常重要,它从未经历过ARB,但直接进入OpenGL规范本身:看看GL_SRGB…内部纹理格式.

在用于采样样本之前,使用此格式加载的纹理将经历sRGB到线性RGB颜色空间转换.这给出了适合于线性渲染操作的线性像素值,然后当进入主屏幕帧缓冲区时,结果可以有效地转换为sRGB.



关于整个问题的个人注释:在屏幕上的帧缓冲区中呈现目标设备颜色空间中的图像恕我直言是一个巨大的设计缺陷.在没有疯狂的情况下,没有办法在这样的设置中做正确的事情.

真正想要的是将屏幕上的帧缓冲区置于线性的接触色空间中; 自然的选择是CIEXYZ.渲染操作自然会在相同的接触颜色空间中进行.在接触颜色空间中进行所有图形操作,避免了在尝试通过名为sRGB的非线性圆孔推动名为线性RGB的方形钉时涉及的上述蠕虫的打开.

虽然我不太喜欢Weston/Wayland的设计,但至少它提供了实际实现这种显示系统的机会,通过让客户端渲染并且合成器在接触颜色空间中操作并应用输出设备的颜色配置文件在最后的后处理步骤中.

接触颜色空间的唯一缺点是,必须使用深色(即每个颜色通道> 12位).事实上,即使使用非线性RGB,8位也是完全不足的(非线性有助于掩盖缺乏可察觉的分辨率).


更新

我加载了一些图像(在我的例子中是.png和.bmp图像)并检查了原始二进制数据.在我看来,好像图像实际上是在RGB颜色空间中,就好像我将像素值与图像编辑程序与我在程序中得到的字节数组进行比较,它们完美匹配.由于我的图像编辑器给了我RGB值,这表示存储在RGB中的图像.

确实是的.如果在信号链中的某处应用了非线性变换,但是所有像素值都未从图像中修改为显示,则该非线性已经预先应用于图像的像素值.这意味着,图像已经处于非线性色彩空间中.

2 - "在大多数计算机上,有效扫描输出LUT是线性的!这意味着什么?

我不确定我能在你的回复中找到这个想法的结果.

这个想法在紧接着的部分详细阐述,其中我展示了您放入普通(OpenGL)帧缓冲区的值如何直接进入监视器,未经修改.sRGB的想法是"将值放入图像中,就像它们被发送到监视器一样,并构建消费者显示以遵循该sRGB颜色空间".

据我所知,经过实验,我测试了所有显示器的输出线性值.

你是如何测量信号响应的?您是否使用经过校准的功率计或类似设备来测量显示器响应信号时发出的光强度?你不能相信你的眼睛,因为就像我们所有的感官一样,我们的眼睛有一个对数信号响应.


更新2

对我来说,唯一可以看到你说的是真实的方式就是图像编辑器给我的sRGB空间值.

确实如此.由于色彩管理作为事后的想法被添加到所有广泛使用的图形系统中,因此大多数图像编辑器在其目标色彩空间中编辑像素值.请注意,sRGB的一个特定设计参数是,它应该仅仅追溯性地指定非受管,直接值转移颜色操作,因为它们(并且大部分仍然完成)在消费者设备上完成.由于根本没有颜色管理,因此图像中包含的值和在编辑器中操作的值必须已经是sRGB.这种方法可以使用很长时间,因为长线图像不是在线性渲染过程中合成的; 在后面的情况下,渲染系统必须考虑目标颜色空间.

我拍了一个截图然后使用图像编辑程序来查看像素的值是什么

这当然只给出了扫描输出缓冲区中的原始值,而没有应用伽玛LUT和显示非线性.