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

标签 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

混合两种纹理而不丢失颜色的正确方法是什么?

最佳答案

用于将两个 FBO 绘制到屏幕上的混合函数应该是无关紧要的,因为它们不会显示任何内容,对吧?因此,在绘制 FBO 之前应该关闭混合,否则您会浪费 GPU 周期将 FBO 与透明颜色混合。

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

在您的情况下,您希望在片段着色器中模拟相同的东西,因为您试图在将两个纹理输出到屏幕之前在着色器中混合它们。

因此,如果您的云 FBO 有 Alpha channel ,您可以在片段着色器中执行此操作,然后就可以开始了:

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 channel ,因此您需要进行一些更改。

您可以做的一件事是让您的 FBO 颜色纹理使用 RGBA4444,以便它具有 Alpha channel ,然后仔细绘制云,以便它们也写入 Alpha channel 。这会有点复杂,因为您必须使用单独的混合函数,在其中分别为 RGB 和 A channel 选择两个不同的混合函数。我以前没有这样做过。虽然这应该是可能的,但我以前从未尝试过这种方法,因为我认为 4 位颜色看起来很糟糕。

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

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

它的作用是将云的单色放入 R channel 并预乘 alpha,并将 alpha 放入 G channel 。我们想要预乘 alpha,这样我们就可以简单地将编码的云 Sprite 添加到场景中。这是因为,当您在已绘制的 Sprite 前面的已绘制 Sprite 中半透明的区域中绘制某些内容时,您希望增亮 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);
} 

关于LibGDX - 使用着色器将纹理覆盖在另一个纹理之上,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24480901/

相关文章:

java - 如何将 Chartboost 依赖项添加到我的 LibGDX 项目?

android - 将多个纹理传递给 LibGDX 中的着色器

android - 如何改变移动物体的位置 - Box2D

c++ - OpenGL alpha 混合突然停止

python - glsl 解析器使用 pyparsing 给出 AttributeErrors

javascript - 圆柱体部分可见 WebGL

android-studio - Libgdx 桌面版本无法编译

c++ - 如何为光线转换生成相机光线

iPhone glShader二进制

opengl-es - 什么时候应该在 OpenGL ES 2 中使用 glDeleteBuffers、glDeleteShader 和 glDeleteProgram?