opengl - 我是否需要 Gamma 校正现代计算机/显示器上的最终色彩输出

标签 opengl graphics rendering gamma

我一直认为我的 Gamma 校正管道应如下所示:

  • 由于samma格式的所有美工程序都会对其文件进行校正,因此对(GL_SRGB8_ALPHA8)中加载的所有纹理都使用sRGB格式。在着色器中从GL_SRGB8_ALPHA8纹理采样时,OpenGL将自动转换为线性空间。
  • 在线性空间中进行所有光照计算,后处理等。
  • 编写将在屏幕上显示的最终颜色时,转换回sRGB空间。

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

    我的假设受到挑战,好像我在最后阶段 Gamma 校正时我的色彩比应有的要明亮。我设置为使用值(value){255, 106 ,0}的灯光绘制纯色,但是渲染时得到{255, 171 ,0}(由打印屏幕和颜色选择确定) )。我不是橙色,而是黄色。如果我在最后一步不正确 Gamma ,我将得到正确的正确值{255, 106 ,0}。

    根据some resources的说法,现代LCD屏幕模仿CRT Gamma 。他们总是吗?如果不是,我该如何确定 Gamma 校正?我在其他地方出错吗?

    编辑1

    我现在注意到,即使我用光写的颜色是正确的,但我使用纹理颜色的地方还是不正确的(但是如果不使用 Gamma 校正,那会比我期望的要暗得多)。我不知道这种差异来自何处。

    编辑2

    在尝试用GL_RGBA8代替GL_SRGB8_ALPHA8作为纹理后,即使在照明计算中使用纹理值时,一切看起来也很完美(如果将光强度的一半减半,则输出颜色值将减半)。

    我的代码不再在任何地方考虑 Gamma 校正,并且我的输出看起来正确。

    这让我更加困惑,是否不再需要/使用 Gamma 校正?

    编辑3-回应datenwolf's answer

    经过更多的实验之后,我对这里的几点感到困惑。

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

    我已经加载了一些图像(在我的情况下是.png和.bmp图像)并检查了原始二进制数据。在我看来,图像实际上是在 RGB 颜色空间中,就好像我将像素值与图像编辑程序与程序中得到的字节数组进行比较时,它们完美匹配。由于我的图像编辑器为我提供了RGB值,因此这将指示以RGB存储的图像。

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

    如果这些图像实际上存储在线性RGB空间中的磁盘上,我应该如何(以编程方式)知道何时指定图像在sRGB空间中?有什么方法可以查询功能更强大的图像加载器可能提供的信息吗?还是由图像创建者将其图像保存为经过 Gamma 校正(或不经过 Gamma 校正),这意味着建立约定并遵循给定项目的约定。我问了几个艺术家,他们都不知道 Gamma 校正是什么。

    如果我指定的图像是sRGB,除非我最后进行 Gamma 校正,否则它们将太暗(如果显示器使用sRGB,这是可以理解的,但请参阅第2点)。

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

    我不确定在您的回复中能否找到这个想法。

    据我所知,经过试验,我测试过的所有显示器都具有输出线性值。如果我绘制一个全屏四边形并在带有 no gamma校正的着色器中使用硬编码值对其进行着色,则监视器将显示我指定的正确值。

    我从您的答案和结果中在上面引用的句子使我相信,现代显示器输出线性值(即,不模拟CRT Gamma )

    我们应用程序的目标平台是PC。对于该平台(不包括使用CRT或真正的旧显示器的人),对您对#1的反应是否是合理的,然后对#2的而不是 Gamma 正确(即不执行最终的RGB-> sRGB转换-手动还是使用GL_FRAMEBUFFER_SRGB)?

    如果是这样,那么GL_FRAMEBUFFER_SRGB打算在什么平台上使用(或者今天在哪里可以使用),或者使用线性RGB的显示器真的是新的(鉴于GL_FRAMEBUFFER_SRGB于2008年推出)?

    -

    我与学校的其他图形开发人员进行了交谈,从声音上看,他们都没有考虑过 Gamma 校正,也没有发现任何不正确的地方(有些人甚至没有意识到)。一位开发人员特别说,他在考虑 Gamma 时得到了不正确的结果,因此他决定不担心 Gamma 。 鉴于我上线/查看的项目有冲突的信息,所以我不确定要在目标平台上的项目中做什么。

    编辑4-回应datenwolf's updated answer

    Yes, indeed. If somewhere in the signal chain a nonlinear transform is applied, but all the pixel values go unmodified from the image to the display, then that nonlinearity has already been pre-applied on the image's pixel values. Which means, that the image is already in a nonlinear color space.



    如果我正在检查显示器上的图像,您的回答对我来说很有意义。可以肯定地说,我说的是检查图像的字节数组时,我的意思是我检查的是内存中用于纹理的数值,而不是屏幕上输出的图像(我做了对点#所做的工作) 2)。对我来说,我唯一能看到您说的是真实的方法是,如果图像编辑器在sRGB空间中为我提供值。

    还要注意,我确实尝试检查监视器上的输出,以及修改的纹理颜色(例如,将其除以一半或加倍),并且输出看起来正确(使用我在下面介绍的方法进行测量)。

    How did you measure the signal response?



    不幸的是,我的测量方法远比您的粗略。当我说在显示器上进行实验时,我的意思是我将纯色全屏四边形输出,其颜色在着色器中硬编码为普通的OpenGL帧缓冲区(写入时不进行任何颜色空间转换)。当我输出白色,75%灰度,50%灰度,25%灰度和黑色时,将显示正确的颜色。现在,我对正确颜色的解释很可能是错误的。我拍摄了一个屏幕截图,然后使用图像编辑程序查看像素的值是什么(以及视觉评估以确保这些值有意义)。如果我理解正确,如果我的显示器是非线性的,则需要先执行RGB-> sRGB转换,然后再将其呈现给显示设备,以使其正确。

    我不会撒谎,我觉得我在这里变得有些不对劲。我在想,我可能会困扰我的第二点困惑(最终的RGB-> sRGB转换)的解决方案将是可调整的亮度设置,并将其默认设置为在我的设备上看起来正确的设置(无 Gamma 校正)。

    最佳答案

    首先,您必须了解应用于颜色通道的非线性映射通常不仅仅是简单的幂函数。 sRGB非线性可以近似x ^ 2.4,但这并不是真正的问题。无论如何,您的主要假设或多或少是正确的。
    如果纹理以更常见的图像文件格式存储,则它们将包含呈现给图形扫描输出的值。现在有两种常见的硬件方案:

  • 扫描输出接口(interface)输出线性信号,然后显示设备将在内部应用非线性映射。老式的CRT显示器由于其物理特性而呈非线性:放大器只能向电子束中注入(inject)这么多的电流,磷光体会饱和等等。这就是为什么首先引入整个 Gamma 值来建模CRT显示器非线性的原因。
  • 现代的LCD和OLED显示器在驱动放大器中使用梯形电阻,或者在图像处理器中具有 Gamma 斜坡查找表。
  • 但是,某些设备是线性的,要求图像生成设备为扫描仪上所需的输出颜色配置文件提供适当的匹配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;
    }
    
    图形输出是使用Modeline配置的
    "1440x900_60.00"  106.50  1440 1528 1672 1904  900 903 909 934 -HSync +VSync
    
    (因为这与平板电脑运行的模式相同,而我正在使用克隆模式)
  • gamma = 2 LUT在绿色通道上。
  • 蓝色通道上的
  • linear(gamma = 1)LUT

    这就是一条扫描线的信号的样子(上部曲线:Ch2 =绿色,下部曲线:Ch1 =蓝色):

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

    现在,经过这一小弯路后,我们知道去往主帧缓冲区的像素值按原样去:OpenGL线性斜坡没有进一步变化,仅当应用了非线性扫描LUT时,它才改变了发送到显示器的信号。
    无论哪种方式,您提供给扫描输出的值(这意味着屏幕上的帧缓冲区)都将在信号链中的某个点进行非线性映射。对于所有标准消费类设备,此映射将根据sRGB标准进行,因为它是最小的公因数(即sRGB色彩空间中表示的图像可以在大多数输出​​设备上复制)。
    由于大多数程序(例如Web浏览器)都假定输出经过sRGB来显示色彩空间映射,因此它们只是将标准图像文件格式的像素值照原样复制到屏幕上的帧中,而无需执行色彩空间转换,因此暗示这些图像中的颜色值在sRGB颜色空间中(或者,如果图像颜色配置文件不是sRGB,则它们通常只会转换为sRGB);正确的操作(如果且仅当写入帧缓冲区的颜色值未更改地扫描到显示器上;假设扫描的LUT是显示器的一部分时),才可以转换为显示器期望的指定颜色配置文件。
    但这暗示着,屏幕上的帧缓冲区本身位于sRGB颜色空间中(我不想就这是多么愚蠢而split恼,让我们接受这个事实)。
    如何将其与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
    
    因此,如果您的窗口配置了sRGB像素格式,并在OpenGL中使用glEnable(GL_FRAMEBUFFER_SRGB);启用sRGB光栅化模式,则线性色彩空间渲染操作的结果将在sRGB色彩空间中转换。
    另一种方法是将所有内容渲染到屏幕外的FBO中,并在后处理着色器中进行颜色转换。
    但这仅仅是渲染信号链的输出端。您还可以获取纹理形式的输入信号。这些通常是图像,其像素值非线性存储。因此,在将这些图像用于线性图像操作之前,必须先将此类图像带入线性色彩空间。让我们暂时忽略一下,将非线性色彩空间映射到线性色彩空间本身就会打开几条蠕虫 jar 头-这就是sRGB色彩空间如此之小以至于可以避免这些问题的原因。
    因此,为了解决这个问题,引入了 EXT_texture_sRGB 扩展,它是如此重要,以至于它从来没有成为ARB,而是直接进入了OpenGL规范本身:看一下GL_SRGB…内部纹理格式。
    以此格式加载的纹理在用于获取样本之前,先经过sRGB到线性RGB颜色空间转换。这样可以提供适合线性渲染操作的线性像素值,然后在转到主屏幕上的帧缓冲区时,可以将结果有效地转换为sRGB。

    关于整个问题的个人说明:在目标设备色彩空间的IMHO屏幕上的帧缓冲区中显示图像是一个巨大的设计缺陷。在没有疯狂的情况下,无法在这样的设置中正确完成所有事情。
    人们真正想要的是将屏幕上的帧缓冲区放在线性的接触色空间中。自然的选择是CIEXYZ。渲染操作自然会在相同的接触色空间中进行。在接触色空间中进行所有图形操作,避免了上述蠕虫 jar 的打开,该蠕虫 jar 试图将名为线性RGB的方形钉穿过名为sRGB的非线性圆孔 push 。
    而且,尽管我不太喜欢Weston / Wayland的设计,但至少它提供了机会来实际实现这样的显示系统,方法是让客户端渲染并让合成器在接触颜色空间中操作并应用输出设备的颜色配置文件在最后的后处理步骤中。
    接触色空间的唯一缺点是必须使用深色(即每个颜色通道> 12位)。实际上,即使使用非线性RGB,8位也是完全不够的(非线性有助于弥补可见分辨率的不足)。

    更新资料

    I've loaded a few images (in my case both .png and .bmp images) and examined the raw binary data. It appears to me as though the images are actually in the RGB color space, as if I compare the values of pixels with an image editing program with the byte array I get in my program they match up perfectly. Since my image editor is giving me RGB values, this would indicate the image stored in RGB.


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

    2 - "On most computers the effective scanout LUT is linear! What does this mean though?


    我不确定在您的回复中能否找到这个想法。

    在紧随其后的部分中将详细阐述这种想法,在此部分中,我将展示如何将您放入普通(OpenGL)帧缓冲区中的值直接未经修改地直接发送到监视器。 sRGB的想法是“将值准确地放入图像中,然后将其发送到监视器,并建立消费者显示以遵循sRGB颜色空间”。

    From what I can tell, having experimented, all monitors I've tested on output linear values.


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

    更新2

    To me the only way I could see what you're saying to be true then is if the image editor was giving me values in sRGB space.


    确实是这样。由于事后才将色彩管理添加到所有广泛使用的图形系统中,因此大多数图像编辑器都会在其目标色彩空间中编辑像素值。请注意,sRGB的一个特定设计参数是,它仅应追溯指定在消费类设备上已完成(而且大部分仍完成)的非托管,直接值转移色彩操作。由于根本不进行颜色管理,因此图像中包含的值和在编辑器中进行操作的值必须已经在sRGB中。只要不能在线性渲染过程中合成长图像,此操作就可以使用很长时间。在以后的情况下,渲染系统必须考虑目标色彩空间。

    I take a screenshot and then use an image editing program to see what the values of the pixels are


    当然,这只为您提供了扫描缓冲区中的原始值,而没有应用 Gamma LUT和显示非线性。

  • 关于opengl - 我是否需要 Gamma 校正现代计算机/显示器上的最终色彩输出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23026151/

    相关文章:

    c++ - 解析 wavefront obj 文件格式

    c - 如何直接访问显卡的输出?

    widget - Flutter Row 在构建时消失了吗?

    ruby-on-rails - 渲染 View 花费的时间太长

    linux - 如何在路径上呈现(快速)文本?

    c - OpenGL 纹理初始化/渲染问题

    opengl - OpenGL 中的计算着色器有指令限制吗?

    c++ - OpenGL 大 3D 纹理 (>2GB) 非常慢

    c++ - Opengl 中的视差法线贴图问题,GLSL

    math - 查找至少部分位于任意方向矩形内的所有像素