opengl - 与之前的framebuffer内容混合

sta*_*oat 17 opengl alphablending framebuffer render-to-texture

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

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

编辑:更多信息需要,我会尝试更清楚地解释;

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

我正在创建一个跟踪鼠标移动的应用程序.它绘制了将前一个鼠标位置连接到当前鼠标位置的线条,因为我不想在每个帧上再次绘制线条,我想我会绘制一个纹理,从不清除纹理然后只绘制一个矩形它上面的纹理来显示它.

这一切都很好,除了当我在纹理上绘制alpha小于1的形状时,它不能与纹理的先前内容正确混合.假设我有一些黑色线条,alpha = .6被绘制到纹理上.一对夫妇画了一个周期之后,我在那些线上画了一个alpha = .4的黑色圆圈.圆圈"下方"的线条被完全覆盖.虽然圆圈不是扁平的黑色(它与白色背景正确混合),但圆圈下方没有"深色线条",正如您所期望的那样.

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

Ben*_*nik 27

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

请记住,所有叠加线在此处混合两次.一旦它们被混合到FBO纹理中,并且再次将FBO纹理混合到场景上时.

所以第一种可能性是在FBO叠加层中绘制一条线而不是另一条线时没有启用混合.当您在混合关闭时绘制RGBA曲面时,当前的alpha将直接写入FBO叠加层的Alpha通道.然后,当您将整个FBO纹理混合到场景上时,该alpha会使您的线条变得半透明.因此,如果您对"世界"进行混合,而不是在叠加元素之间进行混合,则可能不会发生混合.

另一相关的问题:当在"标准"混合模式混合一行比另一个(SRCα,1 - SRC阿尔法)在FBO中,"混合"部分的α信道是要包含共混物的的阿尔法两个叠加元素.这可能不是你想要的.

例如,如果您在叠加层中相互绘制两条50%的alpha线,为了在blit FBO时获得相同的效果,则需要FBO的alpha值为... 75%.(也就是说,1 - (1 - .5)*(1-0.5),如果你只是在你的场景中绘制了两条5​​0%的alpha线,那会发生什么.但是当你绘制两条50%的线时,你会在FBO中获得50%的阿尔法(混合50%和... 50%.

这就提出了最后一个问题:通过在将它们混合到世界之前预先混合线条,您将更改绘制顺序.虽然你可能有:

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

现在你将拥有

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

换句话说,将叠加线预混合到FBO中会改变混合的顺序,从而以您可能不想要的方式改变最终外观.

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

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

如果这不是一个选项(例如,如果你绘制混合的形状类型或使用GL状态的混合,这样"记住"你所做的比复制顶点要复杂得多)那么你可能想要改变你如何画入VBO.

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

然后,在绘制FBO时,使用"pre-multiplied"alpha,即(one,one-dec-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颜色.在这种情况下,您希望它获得正确的结果,但在其他情况下,程序员使用它来获得速度.(通过预乘,它会在执行混合操作时删除每个像素的多个.)

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


Tam*_*chi 18

用透明的黑色(0,0,0,0)清除FBO,然后从中拉到前面

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
Run Code Online (Sandbox Code Playgroud)

和FBO一起画

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
Run Code Online (Sandbox Code Playgroud)

得到确切的结果.

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

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

结合透明度的公式是

resultTr = sTr * dTr
Run Code Online (Sandbox Code Playgroud)

(我使用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
Run Code Online (Sandbox Code Playgroud)

与混合函数(源和目标因子)(GL_ONE,GL_ONE_MINUS_SRC_ALPHA)的默认混合方程GL_FUNC_ADD相同.


作为旁白:

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

    glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
Run Code Online (Sandbox Code Playgroud)

否则使用相同的方法.

我的理由是,显卡可能能够跳过已经很稳固的区域的着色器执行.我没有测试过这个,所以它在实践中可能没有任何区别.

  • 我想说〜5年后,我再次在Google上搜索这个问题,阅读你的帖子,并自言自语"哇,这解决了我的问题,很简单,就这么完美!".然后我心里想着"我想知道是谁问了原来的问题". (3认同)

小智 5

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