将自透明纹理渲染到帧缓冲区时出现奇怪的混合
Strange blending when rendering self-transparent texture to the framebuffer
我正在尝试将自透明纹理渲染到帧缓冲区,但我得到的不是我猜的那样:之前在帧缓冲区上渲染的所有内容都被忽略了,并且该纹理与我清理主 canvas.
这就是我想要的,但不使用帧缓冲区:
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
这就是我使用帧缓冲区得到的结果(我不明白为什么第二个渲染纹理不与前一个纹理混合,就像没有帧缓冲区一样):
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
那么我怎样才能让帧缓冲版本像第一个版本那样工作呢? ;)
简单的答案是在渲染到屏幕时禁用混合。
但我认为如果您想使用 FBO,最好理解为什么会发生这种情况。那么让我们来看看实际发生了什么。
首先确保了解纹理颜色和批次颜色(顶点颜色)的作用:they are multiplied。因此,当将批处理颜色设置为 0,0,0,0.5
且纹理像素(纹素)为 1,1,1,1
时,这将导致值 1*0,1*0,1*0,1*0.5
= 0,0,0,0.5
.
接下来确保了解 blending 的工作原理。默认情况下启用混合,并将使用 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)
.
那么让我们看看在您的第一个代码中它是如何工作的:
- 您使用
1, 0, 0, 1
清除屏幕,换句话说:屏幕的每个像素都包含值 1, 0, 0, 1
。
- 然后用每个纹素值
1,1,1,1
渲染一个完整的矩形,现在屏幕的每个像素都包含值 1, 1, 1, 1
。
- 然后您使用每个纹素值
0,0,0,0.5
渲染一个较小的矩形,屏幕那部分 上的每个像素 现在都包含值 0.5, 0.5, 0.5, 0.75
。
已经对这个问题有了感觉?让我们看看您的第二个代码会发生什么:
- 您使用
1, 0, 0, 1
清除屏幕:屏幕的每个像素都包含值 1, 0, 0, 1
。
- 你绑定FBO并用
1, 1, 1, 1
清除它:FBO的每个像素都包含值1, 1, 1, 1
。
- 您将每个纹素值
1,1,1,1
的完整矩形渲染到 FBO:FBO 的每个像素现在都包含值 1,1,1,1
。
- 您使用每个纹素值
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
确保理解这个过程,否则它会导致一些令人沮丧的奇怪问题。如果您觉得难以遵循,那么您可以拿起笔和纸并手动计算每一步的价值。
我正在尝试将自透明纹理渲染到帧缓冲区,但我得到的不是我猜的那样:之前在帧缓冲区上渲染的所有内容都被忽略了,并且该纹理与我清理主 canvas.
这就是我想要的,但不使用帧缓冲区:
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
这就是我使用帧缓冲区得到的结果(我不明白为什么第二个渲染纹理不与前一个纹理混合,就像没有帧缓冲区一样):
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
那么我怎样才能让帧缓冲版本像第一个版本那样工作呢? ;)
简单的答案是在渲染到屏幕时禁用混合。
但我认为如果您想使用 FBO,最好理解为什么会发生这种情况。那么让我们来看看实际发生了什么。
首先确保了解纹理颜色和批次颜色(顶点颜色)的作用:they are multiplied。因此,当将批处理颜色设置为 0,0,0,0.5
且纹理像素(纹素)为 1,1,1,1
时,这将导致值 1*0,1*0,1*0,1*0.5
= 0,0,0,0.5
.
接下来确保了解 blending 的工作原理。默认情况下启用混合,并将使用 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)
.
那么让我们看看在您的第一个代码中它是如何工作的:
- 您使用
1, 0, 0, 1
清除屏幕,换句话说:屏幕的每个像素都包含值1, 0, 0, 1
。 - 然后用每个纹素值
1,1,1,1
渲染一个完整的矩形,现在屏幕的每个像素都包含值1, 1, 1, 1
。 - 然后您使用每个纹素值
0,0,0,0.5
渲染一个较小的矩形,屏幕那部分 上的每个像素 现在都包含值0.5, 0.5, 0.5, 0.75
。
已经对这个问题有了感觉?让我们看看您的第二个代码会发生什么:
- 您使用
1, 0, 0, 1
清除屏幕:屏幕的每个像素都包含值1, 0, 0, 1
。 - 你绑定FBO并用
1, 1, 1, 1
清除它:FBO的每个像素都包含值1, 1, 1, 1
。 - 您将每个纹素值
1,1,1,1
的完整矩形渲染到 FBO:FBO 的每个像素现在都包含值1,1,1,1
。 - 您使用每个纹素值
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
确保理解这个过程,否则它会导致一些令人沮丧的奇怪问题。如果您觉得难以遵循,那么您可以拿起笔和纸并手动计算每一步的价值。