LibGDX - 使用着色器将纹理覆盖在另一个纹理上方

时间:2014-06-29 22:40:09

标签: libgdx glsl opengl-es-2.0 fragment-shader

我正在尝试混合从FBO获得的两种不同纹理(场景和云)并在四边形上绘制它们。

uniform sampler2D u_texture;
uniform sampler2D u_texture2;  
uniform vec2 u_res;

void main(void)
{   
vec2 texCoord = gl_FragCoord.xy / u_res.xy;

vec4 sceneColor = texture2D(u_texture, texCoord);
vec4 addColor = texture2D(u_texture2, texCoord);    

gl_FragColor = sceneColor+addColor;
} 

glBlendFunc是

Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

我尝试了glBlendFunc的所有组合,上面的组合是最好的组合。

创建FBO:

fbClouds = new FrameBuffer(Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
fbScene = new FrameBuffer(Format.RGB565, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
fbMix = new FrameBuffer(Format.RGB565, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);

创建云:

fbClouds.begin();
    Gdx.gl.glClearColor(0, 0, 0, 0); // to make it transparent
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
    modelBatch.begin(cam);      
    for (Integer e : drawOrder) {
        if(isVisible(cam, clouds[e])){
            drawLightning(e, modelBatch);           
            modelBatch.render(clouds[e], cloudShader);
            modelBatch.flush();             
        }
    }
    modelBatch.end();
    fbClouds.end();

渲染代码:

Gdx.gl20.glDisable(GL20.GL_BLEND);
//Gdx.gl20.glEnable(GL20.GL_BLEND);
//Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
fbMix.begin();
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

    mixShader.begin();
    fbScene.getColorBufferTexture().bind(1);
    mixShader.setUniformi("u_texture", 1);

    fbClouds.getColorBufferTexture().bind(0);
    mixShader.setUniformi("u_texture2", 0);

    mixShader.setUniformf("u_res", Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

    quad.render(mixShader, GL20.GL_TRIANGLES);

    mixShader.end();
 fbMix.end();

所以,我得到了意想不到的结果(云有绝对的白色,虽然它们应该是灰色的):

unexpected result

如果我使用modelbatch渲染云,结果应该是: expected result

在不丢失颜色的情况下混合两个纹理的正确方法是什么?

1 个答案:

答案 0 :(得分:1)

用于将两个FBO绘制到屏幕上的混合功能应该是无关紧要的,因为没有通过它们显示,对吧?因此,在绘制FBO之前应该关闭混合,否则就会浪费GPU循环来混合FBO和你的清晰颜色。

它变成白色的原因是你将灰色添加到蓝色而不首先使蓝色变暗。通常,当您将透明对象绘制到屏幕时,可以使用如下的混合函数:GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA。这意味着你将精灵的颜色乘以其透明度(有效地使透明颜色变暗),然后将背景乘以精灵的alpha的倒数(从而使将添加到精灵的不透明像素的像素变暗,这样它们就不会太多亮)。

在您的情况下,您希望在片段着色器中模拟相同的内容,因为您尝试在着色器中混合两个纹理,然后将它们输出到屏幕。

因此,如果你的云FBO有一个alpha通道,你可以在片段着色器中执行此操作,你就可以了:

void main()
{   
    vec2 texCoord = gl_FragCoord.xy / u_res.xy;

    vec4 sceneColor = texture2D(u_texture, texCoord);
    vec4 addColor = texture2D(u_texture2, texCoord);    

    gl_FragColor = addColor*addColor.a + sceneColor*(1-addColor.a);
} 

但是,您的云的FBO没有Alpha通道,因此您需要更改某些内容。

你可以做的一件事是让你的FBO颜色纹理使用RGBA4444,因此它有一个alpha通道,然后小心地绘制你的云,这样他们也可以写入alpha通道。这有点复杂,因为你必须使用一个单独的混合函数,你可以分别为RGB和A通道选择两个不同的混合函数。我以前没有这样做过。虽然它应该是可能的,但之前我还没有尝试过这种方法,因为我认为4位颜色看起来很糟糕。

或者,如果您的云都是单色的,您可以将alpha信息编码为其中一个颜色通道。为此,您需要自定义用于将云绘制到FBO的片段着色器。它看起来像这样:

vec4 textureColor = texture2D(u_texture, v_texCoord);
gl_FragColor = vec4(textureColor.r * textureColor.a, textureColor.a, 0, textureColor.a);

这样做是将云的单色在R通道中加上alpha预乘,然后将alpha放在G通道中。我们想要预先乘以alpha,这样我们就可以简单地将编码的云精灵添加到场景中。这是因为当您在已经绘制的精灵中半透明的区域中绘制已经绘制的精灵前面的内容时,您需要使G编码的alpha变亮,以使像素在最终的FBO图像中更加不透明。由于我们使用的是预乘的alpha,请使用混合函数GL_ONE,GL_ONE_MINUS_SRC_ALPHA绘制云。

(这是一个轻微的近似,因为目的地的G编码alpha在混合函数的第二部分变暗了一点,但我查看了数学并且它似乎可以接受。近似导致稍微透明云。)

所以现在云FBO看起来像一堆黄色,如果你把它画到屏幕上的话。我们只需稍微调整上面的片段着色器即可使用编码数据:

void main()
{   
    vec2 texCoord = gl_FragCoord.xy / u_res.xy;

    vec4 sceneColor = texture2D(u_texture, texCoord);
    vec4 addColor = texture2D(u_texture2, texCoord);    

    gl_FragColor = vec4(addColor.r*addColor.g) + sceneColor*(1-addColor.g);
} 

如果你想为纯云彩以外的其他东西着色,你可以添加一个统一的颜色色调:

void main()
{   
    vec2 texCoord = gl_FragCoord.xy / u_res.xy;

    vec4 sceneColor = texture2D(u_texture, texCoord);
    vec4 addColor = texture2D(u_texture2, texCoord);    

    gl_FragColor = u_cloudTint*vec4(addColor.r*addColor.g) + sceneColor*(1-addColor.g);
}