2010-01-31 106 views
15

我正在通过framebuffer对象渲染到纹理,并且在绘制透明图元时,图元与在该单个绘图步骤中绘制的其他图元正确混合,但它们没有正确混合与帧缓冲区的以前的内容。opengl - 与framebuffer的先前内容混合

有没有一种方法可以将纹理内容与新数据正确混合?

编辑:更多的信息请求,我会试图更清楚地解释;

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

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

这一切都正常工作,除了当我绘制alpha小于1的形状到纹理上时,它不会与纹理的先前内容正确混合。假设我在纹理上绘制了一些alpha = .6的黑线。一对夫妇抽签后,我在这些线上绘制一个alpha = .4的黑色圆圈。圆下方的线条被完全覆盖。尽管圆形不是平坦的黑色(它与白色背景适当融合),但圆形下面没有“黑色线条”,正如您所期望的那样。

但是,如果我在同一个框架中绘制线条和圆形,它们就会适当地混合。我的猜测是纹理不会与之前的内容混合。这就像它只与glclearcolor混合。 (在这种情况下,它是< 1.0f,1.0f,1.0f,1.0f>)

+2

混合应该适用于framebuffer中的任何内容。 (这是什么混合_does_)。提供更多的数据(你使用什么混合模式,你称之为混合“不正确”?) – Bahbar 2010-01-31 10:10:45

+0

我已经更新了更多的信息,感谢您的快速回复 – staticfloat 2010-01-31 20:28:26

回答

26

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

请记住,所有覆盖线都在这里混合两次。一旦混合到FBO纹理中,并再次在场景中混合FBO纹理时。

因此,第一种可能性是,在FBO覆盖图中绘制一条线时不启用混合。当您在混合关闭的情况下绘制RGBA曲面时,当前alpha将直接写入FBO叠加层的Alpha通道。之后,当您将FBO的整个纹理融合到场景中时,该alpha会让您的线条变得透明。因此,如果您对“世界”进行混合但不在叠加元素之间进行混合,则可能不会发生混合。

另一个相关问题:当​​您在FBO中以“标准”混合模式(src alpha,1 - src alpha)将另一条线叠加到另一条线上时,“混合”部分的Alpha通道将包含混合这两个覆盖元素的alpha。这可能不是你想要的。例如,如果您在覆盖图中相互绘制两条50%alpha线,为了在FBO blit时获得等同效果,您需要FBO的alpha值为... 75%。 (也就是1 - (1-.5)*(1-0.5),如果你在场景中画了两条50%的alpha线,会发生什么,但是当你画出两条50%的线时,在FBO中获得50%alpha(50%与... 50%的混合)

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

混合(混合(混合(背景色,模型),第一行),第二行);

现在你将有

混合(混合(第一行,第二行),混合(背景颜色,型号))。

换句话说,将叠加线预先混合到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颜色。在这种情况下,你希望它得到正确的结果,但在其他情况下,程序员使用它来提高速度。 (通过预倍增,当执行混合操作时,它消除了每个像素的多个像素)。

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

+0

谢谢你的深刻反应!我花了这么长时间回到你身边,但是如果我有更多积分给你,我会的!谢谢! – staticfloat 2010-03-03 06:08:27

3

正如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)不支持分离的混合功能。 :(

13

清除FBO与透明的黑色(0,0,0,0)时,抽入回到前与

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 

(I使用的,因为并行的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色入缓冲器前到与

glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_ONE); 

和否则使用相同的方法。

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

+0

我想说〜〜5年后,我再次使用这个问题,阅读你的帖子,并思考对我自己“哇,这解决了我的问题,非常简单,而且完全适合!”然后我想:“我想知道谁在问原来的问题” – staticfloat 2015-04-03 10:32:24

+0

@staticfloat嘿,我很高兴达到了你。我正在寻找其他人试图呈现嵌套的UI控件(我在实际的图形编程方面显得很糟糕),以前的回答让我想起了单独的混合模式,但我认为如果确切的解决方案会很奇怪n是不可能的。再想一想,这个解决方案只有在你必须绘制_over_前一个缓冲区时才是最优的。我会为可以选择顺序的情况添加备注,例如将GUI控件渲染到缓冲区时,因为这样做的人不太可能最终落入此处。 – Tamschi 2015-04-04 00:17:34

+0

工程就像一个魅力!这帮助我将FBO的透明文本渲染到屏幕上。至于理解为什么/如何运作,我想我必须做更多的阅读:) – Arahman 2016-09-23 15:07:14