【问题标题】:opengl - blending with previous contents of framebufferopengl - 与帧缓冲区的先前内容混合
【发布时间】:2011-01-11 09:09:01
【问题描述】:

我正在通过帧缓冲区对象渲染到纹理,当我绘制透明图元时,这些图元与在该单个绘制步骤中绘制的其他图元正确混合,但它们未与帧缓冲区的先前内容正确混合。

有没有办法将纹理的内容与传入的新数据正确混合?

编辑:要求提供更多信息,我将尝试更清楚地解释;

我使用的混合模式是 GL_SRC_ALPHA、GL_ONE_MINUS_SRC_ALPHA。 (我相信这通常是标准的混合模式)

我正在创建一个跟踪鼠标移动的应用程序。它绘制将前一个鼠标位置连接到当前鼠标位置的线条,并且由于我不想在每一帧中再次绘制线条,我想我会绘制到纹理,从不清除纹理,然后用它绘制一个矩形纹理以显示它。

这一切都很好,除了当我在纹理上绘制 alpha 小于 1 的形状时,它不能与纹理的先前内容正确融合。假设我在纹理上绘制了一些带有 alpha = .6 的黑线。几个绘制周期后,我在这些线上绘制一个 alpha = .4 的黑色圆圈。圆圈“下方”的线条被完全覆盖。虽然圆圈不是纯黑色(它与白色背景完美融合),但圆圈下方没有您所期望的“暗线”。

但是,如果我在同一帧中绘制线条和圆圈,它们会正确融合。我的猜测是纹理与之前的内容不融合。就像它只与 glclearcolor 混合一样。 (在这种情况下是 )

【问题讨论】:

  • 混合应该适用于帧缓冲区中的任何内容。 (这就是混合所做的)。提供更多数据(你用的是什么混合模式,你把什么叫做混合“不正确”?
  • 我已经更新了更多信息。感谢您的快速回复。

标签: opengl alphablending framebuffer render-to-texture


【解决方案1】:

我认为这里有两个可能的问题。

请记住,所有叠加线都在这里混合两次。一次是它们被混合到 FBO 纹理中,另一次是 FBO 纹理被混合到场景中。

因此,第一种可能性是您在 FBO 叠加层中将一条线绘制在另一条线之上时未启用混合。当您在关闭混合的情况下绘制到 RGBA 表面时,当前的 alpha 会直接写入 FBO 叠加层的 alpha 通道。然后稍后当您在场景中混合整个 FBO 纹理时,该 alpha 使您的线条半透明。因此,如果您对“世界”进行了混合,但没有在叠加元素之间进行混合,则可能没有发生混合。

另一个相关问题:当​​您在 FBO 中以“标准”混合模式(src alpha,1 - src alpha)将一条线混合到另一条线时,“混合”部分的 alpha 通道将包含一个混合 of 两个叠加元素的 alpha。这可能不是你想要的。

例如,如果您在叠加层中相互绘制两条 50% 的 alpha 线,为了在对 FBO 进行 blit 时获得等效效果,您需要 FBO 的 alpha 为...75%。 (即 1 - (1-.5) * (1-0.5),如果您只在场景上绘制两条 50% 的 alpha 线会发生这种情况。但是当您绘制两条 50% 的线时,您将在 FBO 中获得 50% 的 alpha(50% 与...50% 的混合。

这带来了最后一个问题:通过在将线条混合到世界各地之前将它们预先混合,您正在改变绘制顺序。而你可能有:

blend(blend(blend(background color, model), first line), second line);

现在你将拥有

blend(blend(第一行,第二行),blend(背景颜色,模型))。

换句话说,将覆盖线预混合到 FBO 中会更改混合顺序,从而以您可能不希望的方式更改最终外观。

首先,解决这个问题的简单方法是:不要使用 FBO。我意识到这是一种“去重新设计你的应用程序”的答案,但使用 FBO 并不是最便宜的,而且现代 GL 卡非常擅长绘制线条。所以一种选择是:不是将线条混合到 FBO 中,而是将线条几何图形写入顶点缓冲区对象 (VBO)。每次只需将 VBO 扩展一点。如果您一次绘制少于 40,000 条线,这几乎肯定会与您之前所做的一样快。

(如果你走这条路线,一个提示:使用 glBufferSubData 写入行,而不是 glMapBuffer - 映射可能很昂贵,并且不适用于许多驱动程序的子范围......最好让驱动程序复制几个新顶点。)

如果这不是一个选项(例如,如果您绘制混合形状类型或使用混合 GL 状态,这样“记住”您所做的事情比仅仅累积顶点要复杂得多)那么您可能想改变你画入 VBO 的方式。

基本上你需要做的是启用单独的混合;将叠加层初始化为黑色 + 0% alpha (0,0,0,0) 并通过“标准混合”RGB 进行混合,但添加混合 Alpha 通道。这对于 Alpha 通道仍然不太正确,但通常更接近 - 没有这个,过度绘制的区域将太透明。

然后,在绘制 FBO 时,使用“预乘”alpha,即 (one, one-minus-src-alph)。

这就是为什么需要最后一步的原因:当您绘制到 FBO 中时,您已经将每个绘制调用乘以其 alpha 通道(如果混合已打开)。由于您在黑色上绘制,因此绿色 (0,1,0,0.5) 线现在是深绿色 (0,0.5,0,0.5)。如果 alpha 打开并且您再次正常混合,则重新应用 alpha 并且您将拥有 0,0.25,0,0.5。)。只需按原样使用 FBO 颜色,就可以避免第二次 alpha 乘法。

这有时被称为“预乘”alpha,因为 alpha 已经乘以 RGB 颜色。在这种情况下,您希望它得到正确的结果,但在其他情况下,程序员使用它来提高速度。 (通过预乘,它在执行混合操作时每像素移除一个 mult。)

希望对您有所帮助!当图层没有按顺序混合时正确混合变得非常棘手,并且在旧硬件上无法单独混合,因此每次都简单地绘制线条可能是最不痛苦的路径。

【讨论】:

  • 感谢您的深入回复!很抱歉,我花了这么长时间才回复你,但如果我有更多的积分可以给你,我会的!谢谢!
【解决方案2】:

正如 Ben Supnik 所说,最好的方法是使用单独的颜色和 Alpha 混合函数来渲染整个场景。如果您使用经典的非预乘混合函数,请尝试使用 glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) 将场景渲染到 FBO。和 glBlendFuncSeparateOES(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) 将 FBO 渲染到屏幕上。

它不是 100% 准确,但在大多数情况下不会产生意外的透明度。

请记住,旧硬件和某些移动设备(主要是 OpenGL ES 1.x 设备,如最初的 iPhone 和 3G)不支持单独的混合功能。 :(

【讨论】:

    【解决方案3】:

    用透明黑色 (0, 0, 0, 0) 清除 FBO,从后到前用

    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    

    并用

    绘制FBO
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    

    得到准确的结果。

    正如 Ben Supnik 所写,FBO 包含的颜色已经与 Alpha 通道相乘,因此不再使用 GL_SRC_ALPHA 进行此操作,而是使用 GL_ONE 进行绘制。目标颜色通常使用 GL_ONE_MINUS_SRC_ALPHA 进行衰减。

    以这种方式在缓冲区中混合 alpha 通道的原因是不同的:

    结合透明度的公式是

    resultTr = sTr * dTr
    

    (我使用 s 和 d 是因为与 OpenGL 的源和目标平行,但正如您所见,顺序并不重要。)

    用不透明度(alpha 值)写成

        1 - rA = (1 - sA) * (1 - dA)
    <=> rA = 1 - (1 - sA) * (1 - dA)
           = 1 - 1 + sA + dA - sA * dA
           =         sA + (1 - sA) * dA
    

    这与默认blend equation GL_FUNC_ADD的混合函数(源和目标因子)(GL_ONE、GL_ONE_MINUS_SRC_ALPHA)相同。


    顺便说一句:

    上面回答了问题中的具体问题,但是如果您可以轻松选择绘制顺序,理论上可能更好地将premultiplied颜色从前到后绘制到缓冲区中

        glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
    

    否则使用相同的方法。

    我的理由是显卡可能能够跳过已经固定的区域的着色器执行。不过我还没有测试过,所以在实践中可能没有什么区别。

    【讨论】:

    • 我想说的是,大约 5 年后,我再次在谷歌上搜索了这个问题,阅读了你的帖子,心里想“哇,这解决了我的问题,非常简单,非常适合!”。然后我想自己“我想知道是谁问了最初的问题”。
    • @staticfloat 呵呵,很高兴你收到了。我正在为其他尝试渲染嵌套 UI 控件的人查找此内容(我仍然不擅长实际的图形编程。)之前的答案向我提示了单独的混合模式,但我认为它会如果一个精确的解决方案是不可能的,那就太奇怪了。再想一想,这个解决方案只有在你必须绘制 之前的前一个缓冲区时才是最佳的。我会为您可以选择顺序的情况添加注释,例如将 GUI 控件渲染到缓冲区时,因为这样做的人不太可能在这里结束。
    • 像魅力一样工作!这帮助我将透明文本从 FBO 渲染到屏幕。至于理解它为什么/如何工作,我想我必须做更多的阅读:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-06-26
    • 2018-11-01
    • 1970-01-01
    • 2017-09-24
    • 1970-01-01
    • 2015-11-04
    • 1970-01-01
    相关资源
    最近更新 更多