当启用时,OpenGL帧缓冲复制是否考虑伽马校正?

6
这是我的问题,当我加载纹理时,我会将它们加载为SRGB格式,以便将它们转换为线性空间。当我写入窗口系统提供的默认帧缓冲区时,我启用GL_FRAMEBUFFER_SRGB,以便将颜色从线性空间转换为SRGB空间。
问题现在是我正在渲染到离屏FBO中,然后将其复制到默认帧缓冲区中。在这样做时,开启或关闭GL_FRAMEBUFFER_SRGB没有任何效果。我已经尝试了在绑定默认帧缓冲区和绑定离屏FBO时都启用GL_FRAMEBUFFER_SRGB,但都没有成功。
然而,我发现如果我将GL_SRGB指定为绑定到离屏FBO的颜色缓冲区的纹理的内部格式,则工作正常。在这种情况下,当启用GL_FRAMEBUFFER_SRGB时,片段着色器对纹理的写入会将其从线性空间转换为SRGB空间。
我想知道的是,在从FBO复制到默认帧缓冲区时,好像GL_FRAMEBUFFER_SRGB转换不起作用。因为我想要在最后一步传输到默认帧缓冲区之前一直使用线性空间,那么在将离屏FBO复制到默认帧缓冲区时如何应用转换呢?我猜,如果以纹理的形式采样离屏FBO,并在默认帧缓冲区上渲染一个四边形,则会应用SRGB转换,但是否有没有使用framebufferblit进行此转换的方法呢?
1个回答

10
当使用GL_FRAMEBUFFER_SRGB时,开启或关闭都没有效果。我已经尝试过在默认帧缓冲区绑定和离屏FBO绑定时启用GL_FRAMEBUFFER_SRGB,但它们都不起作用。 GL_FRAMEBUFFER_SRGB并不是你所想的那样:它不会将任何帧缓冲区的格式从RGB转换为sRGB。只有当帧缓冲区的格式已经指定为sRGB时,例如通过将一个内部格式为GL_SRGB8GL_SRGB8_ALPHA8的纹理附加为颜色缓冲区到一个FBO上时,GL_FRAMEBUFFER_SRGB才会产生任何效果。如果您想要默认帧缓冲区进行sRGB转换,则必须使用特定于窗口系统的API来显式创建具有sRGB支持的PixelFormat/Visual/FBConfig/whatever。请参阅GL / GLX / WGL_ARB_FRAMEBUFFER_SRGB扩展规范以了解如何实现。
“然而,如果我指定GL_SRGB作为我绑定为离屏FBO的颜色缓冲区的纹理的内部格式,则可以正常工作。在此情况下,当启用GL_FRAMEBUFFER_SRGB时,片段着色器对纹理的写入会将其从线性空间转换为SRGB空间。”
“是的,这正是它应该工作的方式。GL_FRAMEBUFFER_SRGB启用位只是一种手段,用于禁用通常进行SRGB转换的格式上的转换,而不是相反。”
“我想知道,似乎从FBO到默认帧缓冲区复制时,GL_FRAMEBUFFER_SRGB转换未被应用。”
“让我们看一下最新的OpenGL规范: OpenGL 4.6核心规范,第18.3.1节“Blitting Pixel Rectangles”(我强调):”
当从读取缓冲区中获取值时,如果启用了FRAMEBUFFER_SRGB并且与读取缓冲区对应的帧缓冲附件的FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING的值为SRGB(参见第9.2.3节),则红色、绿色和蓝色分量将根据方程式8.17从非线性sRGB颜色空间转换而来。
当写入绘制缓冲区时,大多数模糊操作都会绕过片段管道。唯一影响模糊的片段操作是像素所有权测试、剪裁测试和sRGB转换(参见第17.3.7节)。颜色、深度和模板掩码(参见第17.4.2节)将被忽略。
第17.3.7节“sRGB转换”规定:
如果启用了FRAMEBUFFER_SRGB,并且与目标缓冲区对应的帧缓冲附件的FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING的值为SRGB(参见第9.2.3节),混合后的R、G和B值将通过[以下公式...]转换为非线性sRGB颜色空间。
请注意,第17.3.7节将明确指出,仅当启用GL_FRAMEBUFFER_SRGB时,在写入颜色缓冲区时才会应用从线性到sRGB的转换。
因此,这给我们留下了以下可能性:
  1. 源和目标颜色缓冲区都采用标准的非sRGB格式:复制像素值,不会进行任何sRGB转换,无论如何设置GL_FRAMEBUFFER_SRGB

  2. 源缓冲区采用sRGB格式,目标缓冲区采用非sRGB格式。只有在启用GL_FRAMEBUFFER_SRGB时才会进行从sRGB到线性的转换。

  3. 源缓冲区采用非sRGB格式,目标缓冲区采用sRGB格式。只有在启用GL_FRAMEBUFFER_SRGB时才会进行从线性到sRGB的转换。

  4. 源和目标均采用sRGB格式。只有在启用GL_FRAMEBUFFER_SRGB时才会进行从sRGB到线性和从线性到sRGB的转换。请注意,此处转换可能会被简化为无操作。这也包括双线性过滤的情况:GL规范不要求对sRGB源进行的双线性过滤在转换后在线性空间中应用,它同样可以在转换之前应用。

现在,故事可以结束了。但实际上并没有结束。随着GL历史的发展,使用sRGB格式的framebuffer blits的行为已经发生了许多变化。 在18.3.1节OpenGL 4.3 core profile specification中提到: 当从读取缓冲区中获取值时,如果对应于读取缓冲区的framebuffer attachment的FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING的值为SRGB(详见9.2.3节),则红色、绿色和蓝色分量将根据以下方程从非线性sRGB颜色空间转换而来。 [第二段与4.6中引用的内容相同] 这意味着在GL 4.3之前,无论GL_FRAMEBUFFER_SRGB的设置如何,源缓冲区的转换总是会进行,只要它具有sRGB格式。对于目标缓冲区,这个设置仍然是相关的。
现在在OpenGL 3.3中,没有提到使用sRGB格式时的blitting行为。相关章节是4.3.2“复制像素”,只有如下说明:
引用:

Blit操作绕过片段管道。影响blit的唯一片段操作是像素所有权测试和剪切。

这意味着它也将绕过目标缓冲区的线性到sRGB转换,并且完全不涉及源转换。
此外,驱动程序历史上也忽略了规范,并在涉及sRGB转换时做出了最佳决策。例如,参见文章“2015年3月OpenGL驱动程序状态和FB sRGB转换”。还有piglet OpenGL测试套件的这个不错的补丁,讨论问题并呈现一个有点悲伤的结论:
我认为简短的总结是:OpenGL中的sRGB几乎是最糟糕的。:(至少,我曾经交谈过的每个游戏开发者都这么说。呃。

然而,在我的经验中,大多数当前的GL > = 4.4驱动程序按照规定进行转换,因此情况并不那么糟糕了。但我也不会拿我的生命去打赌。


感谢您的详细回复。我相信窗口系统提供的原始默认帧缓冲区是SRGB缓冲区,因为当我直接渲染到该缓冲区并启用GL_FRAMEBUFFER_SRGB时,它会从线性空间转换为SRGB空间。所以您的意思是在从非SRGB缓冲区复制到SRGB缓冲区时,如果启用了GL_FRAMEBUFFER_SRGB标志,则在理论上应该进行从线性到SRGB空间的转换,但由于不同实现和标准的混乱情况,实际情况并非如此? - Zebrafish
我已经将离屏缓冲渲染到默认缓冲区作为四边形,并且在这种情况下SRGB转换有效,只是整个缓冲区是倒置的,我必须反转一些y顶点并将CCW设置为CW。我仍在努力理解OpenGL到底在做什么。 - Zebrafish
好的。您应该检查您正在使用哪个OpenGL上下文版本。由于行为明显已经改变,符合规范的实现必须根据上下文版本有所不同。然而,在现实世界的实现中仍存在很多分歧。作为解决方法,渲染纹理四边形似乎是一个好主意,但您仍应验证是否在所有目标供应商上真正获得了预期的结果。 - derhass
我已经等了很长时间,希望Vulkan能够更加普及。特别是像SDL或SFML这样的框架能够以多平台的方式支持上下文创建。你知道Vulkan是否会比我们现在使用的OpenGL更加标准化吗?例如,较少的功能将成为扩展,并且与OpenGL相比,着色器在不同平台上的可靠性将更高。 - Zebrafish
好的。Vulkan有类似于GL的扩展机制。将着色器编译器放在实现之外将避免不同实现之间的大量解析和语法问题。但是,如果Vulkan将与我们一起使用超过十年,那么看到它是否像GL一样积累了很多垃圾,并且如何处理向后兼容性将是有趣的。 - derhass

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接