将自透明纹理渲染到帧缓冲区时发生奇怪的融合 [英] Strange blending when rendering self-transparent texture to the framebuffer
问题描述
我正在尝试将自透明纹理渲染到帧缓冲区,但我没有得到我所猜到的:先前在帧缓冲区上渲染的所有内容都将被忽略,并且此纹理与我清理主画布的颜色混合在一起.
I'm trying to render self-transparent textures to the framebuffer, but I'm getting not what I guessed: everything previously rendered on the framebuffer gets ignored, and this texture blends with the colour I cleaned my main canvas.
这就是我想要的,但不使用帧缓冲区:
That's what I would like to get, but without using framebuffers:
package test;
import com.badlogic.gdx.*;
import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.graphics.g2d.*;
public class GdxTest extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
@Override
public void create () {
batch = new SpriteBatch();
Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
pixmap.setColor(1, 1, 1, 1);
pixmap.fillRectangle(0, 0, 1, 1);
// Generating a simple 1x1 white texture
img = new Texture(pixmap);
pixmap.dispose();
}
@Override
public void render () {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.setColor(1, 1, 1, 1);
batch.draw(img, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch.setColor(0, 0, 0, 0.5f);
batch.draw(img, 0, 0, 300, 300);
batch.end();
}
}
它可以正常工作:
http://i.stack.imgur.com/wpFNg.png
这就是我使用framebuffer的结果(我不明白为什么第二个渲染纹理不与前一个融合,就像没有framebuffer一样):
And that's what I get with using of framebuffer (I can't understand why the second rendered texture doesn't blend with the previous one, as it do without framebuffer):
package test;
import com.badlogic.gdx.*;
import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.graphics.g2d.*;
import com.badlogic.gdx.graphics.glutils.*;
public class GdxTest extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
FrameBuffer buffer;
TextureRegion region;
@Override
public void create () {
batch = new SpriteBatch();
Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
pixmap.setColor(1, 1, 1, 1);
pixmap.fillRectangle(0, 0, 1, 1);
// Generating a simple 1x1 white texture
img = new Texture(pixmap);
pixmap.dispose();
// Generating a framebuffer
buffer = new FrameBuffer(Pixmap.Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
region = new TextureRegion(buffer.getColorBufferTexture());
region.flip(false, true);
}
@Override
public void render () {
// Filling with red shows the problem
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
buffer.begin();
batch.begin();
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setColor(1, 1, 1, 1);
batch.draw(img, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch.setColor(0, 0, 0, 0.5f);
batch.draw(img, 0, 0, 300, 300);
batch.end();
buffer.end();
batch.begin();
batch.setColor(1, 1, 1, 1);
batch.draw(region, 0, 0);
batch.end();
}
}
结果不可预测:
http://i.stack.imgur.com/UdDKD.png
那么如何使帧缓冲版本像第一个版本那样工作? ;)
So how could I make the framebuffer version work the way the first version does? ;)
推荐答案
简单的答案是在渲染到屏幕时禁用混合.
The easy answer is to disable blending when rendering to the screen.
但是,我想很好地理解为什么要使用FBO会发生这种情况.因此,让我们逐步了解一下实际发生的情况.
But I think it is good to understand why this is happening if you want to use FBO. So let's walk through what's actually going on.
First make sure to understand what the color of the texture and the color of the batch (the vertex color) does: they are multiplied. So when setting the batch color to 0,0,0,0.5
and the texture pixel (texel) is 1,1,1,1
this will result in a value of 1*0,1*0,1*0,1*0.5
= 0,0,0,0.5
.
接下来,请确保了解混合的工作原理.默认情况下启用混合,并且将使用SRC_ALPHA
和ONE_MINUS_SRC_ALPHA
功能.这意味着源值(纹理像素)乘以源alpha,目标值(屏幕像素)乘以1减去源alpha.因此,如果您的屏幕像素值为1,1,1,1
,而纹理像素的值为0,0,0,0.5
,则屏幕像素将设置为:(0.5*0, 0.5*0, 0.5*0, 0.5*0.5) + ((1-0.5)*1, (1-0.5)*1, (1-0.5)*1, (1-0.5)*1)
,即(0,0,0,0.25) + (0.5, 0.5, 0.5, 0.5)
= (0.5, 0.5, 0.5, 0.75)
.
Next make sure to understand how blending works. Blending is enabled by default and will use the SRC_ALPHA
and ONE_MINUS_SRC_ALPHA
functions. This means that the source value (the texel) is multiplied by the source alpha and that the destination value (the screen pixel) is multiplied by one minus the source alpha. So if your screen pixel has the value 1,1,1,1
and your texel has the value 0,0,0,0.5
then the screen pixel will be set to:(0.5*0, 0.5*0, 0.5*0, 0.5*0.5) + ((1-0.5)*1, (1-0.5)*1, (1-0.5)*1, (1-0.5)*1)
which is (0,0,0,0.25) + (0.5, 0.5, 0.5, 0.5)
= (0.5, 0.5, 0.5, 0.75)
.
因此,让我们在您的第一个代码中看看它如何工作:
So let's see how that works for you in your first code:
- 用
1, 0, 0, 1
清除屏幕,换句话说:屏幕的每个像素都包含值1, 0, 0, 1
. - 然后用每个纹理元素值
1,1,1,1
渲染一个完整的矩形,屏幕上的每个像素现在都包含值1, 1, 1, 1
. - 然后您渲染一个较小的矩形,每个texel值
0,0,0,0.5
,屏幕那部分的每个像素 现在都包含值0.5, 0.5, 0.5, 0.75
.
- You clear the screen with
1, 0, 0, 1
, in other words: every pixel of the screen contains the value1, 0, 0, 1
. - Then you render a full rectangle with each texel value
1,1,1,1
, every pixel of the screen now contains the value1, 1, 1, 1
. - Then you render a smaller rectangle with each texel value
0,0,0,0.5
, every pixel on that part of the screen now contains the value0.5, 0.5, 0.5, 0.75
.
已经对这个问题有感觉吗?让我们看看第二个代码中发生了什么:
Got a feeling about the issue already? Let's see what happens in your second code:
- 用
1, 0, 0, 1
清除屏幕:屏幕的每个像素都包含值1, 0, 0, 1
. - 您绑定FBO并用
1, 1, 1, 1
清除它:FBO的每个像素都包含值1, 1, 1, 1
. - 您向FBO渲染每个texel值
1,1,1,1
的完整矩形:FBO的每个像素现在都包含值1,1,1,1
. - 您使用每个texel值
0,0,0,0.5
渲染一个较小的矩形,FBO那个部分上的每个像素 现在都包含值0.5, 0.5, 0.5, 0.75
. - 然后您再次将屏幕绑定为渲染目标,该目标的每个像素仍包含值
1, 0, 0, 1
. - 最后,将FBO纹理渲染为整个屏幕的矩形,从而使这些纹理像素与屏幕像素混合.对于较小的矩形,这意味着将
0.5, 0.5, 0.5, 0.75
乘以0.75和1, 0, 0, 1
乘以1-0.75 = 0.25进行混合,将得到0.375, 0.375, 0.375, 0.5625
和0.25, 0, 0, 0.25
.所以最终的颜色是0.625, 0.375, 0.375, 0,8125
- You clear the screen with
1, 0, 0, 1
: every pixel of the screen contains the value1, 0, 0, 1
. - You bind the FBO and clear it with
1, 1, 1, 1
: every pixel of the FBO contains the value1, 1, 1, 1
. - You render a full rectangle with each texel value
1,1,1,1
to the FBO: every pixel of the FBO now contains the value1,1,1,1
. - You render a smaller rectangle with each texel value
0,0,0,0.5
, every pixel on that part of the FBO now contains the value0.5, 0.5, 0.5, 0.75
. - Then you bind the screen again as the render target of which each pixel still contains the value
1, 0, 0, 1
. - Finally you render the FBO texture as full rectangle to the screen, causing these texels to be blended with the screen pixels. For the smaller rectangle this means blending
0.5, 0.5, 0.5, 0.75
multiplied by 0.75 and1, 0, 0, 1
multiplied by 1-0.75=0.25, which will result in0.375, 0.375, 0.375, 0.5625
and0.25, 0, 0, 0.25
. So the final color is0.625, 0.375, 0.375, 0,8125
请务必了解此过程,否则会引起一些令人沮丧的怪异问题.如果您发现难以遵循,则可以拿笔和纸,手动计算每个步骤的值.
Make sure to understand this process, otherwise it can cause quite some frustrating weird issues. If you find it hard to follow then you could take pen and paper and manually calculate the value for each step.
这篇关于将自透明纹理渲染到帧缓冲区时发生奇怪的融合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!