【问题标题】:LibGDX - overlay texture above another texture using shaderLibGDX - 使用着色器将纹理覆盖在另一个纹理之上
【发布时间】:2014-06-29 22:40:09
【问题描述】:

我正在尝试混合从 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();

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

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

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

【问题讨论】:

  • 它变成了亮白色,因为你在亮蓝色中添加了灰色。但我不确定你的描述到底发生了什么。绘制到其中一个 FBO 时使用的混合函数是什么,还是用于将所有 FBO 绘制到屏幕上?在您使用的片段着色器中,您需要使用云纹理的 alpha 乘以云纹理,并使用(1- 云纹理的 alpha)乘以天空纹理。但是您需要确保您的云的 FBO 支持 alpha 并写入该 alpha 通道。有点绕。也许有更简单的方法?
  • @Tenfour04,我应该使用哪个公式?你能根据我的着色器源代码给我看吗?云可以是红色或绿色,没关系。在任何情况下它们都会变白!
  • 我仍然不清楚你到底在做什么。如果你能澄清一下,我想我有一个可行的解决方案。您在顶部显示一个片段着色器。我想你的意思是你把天空和太阳画到一个 FBO,把云画到另一个 FBO。假设这是对的,你是如何使用 ModelInstance 绘制云的?你画云的 FBO 的清晰颜色是什么?您提到的混合函数是用于将云绘制到其 FBO 中,还是您用于将具有两个 FBO 的 Quad 绘制到主缓冲区?
  • 您这样做是为了节省填充率,因为云层重叠很多,对吧?有什么理由不能将天空和太阳直接绘制到主缓冲区并且只为云使用一个 FBO?
  • @Tenfour04,是的,我使用 ModelBatch 将云绘制到一个 FBO,该 FBO 被降采样两次,而场景的其他元素则绘制到另一个 FBO。我必须这样做以增加 fps(如您在上面的屏幕截图中所见)。我将Format.RGB565 用于云的FBO,清晰的颜色是黑色。我使用blend func 将带有两个FBO 的Quad 绘制到主缓冲区。我不知道如何将主缓冲区(太阳和天空)与云 FBO 混合(以防我直接在主缓冲区中绘制它们(太阳和天空))。因此,我使用上面的着色器和blend func 在 Quad 上绘制它们。我希望它足够清楚。

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


【解决方案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,因此我们可以简单地将编码的云精灵添加到场景中。这是因为当您在已绘制的 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);
} 

【讨论】:

  • 这真是个好答案。谢谢你。如果我对您的理解正确,我应该使用 FBO Format.RGBA8888Gdx.gl.glClearColor(0, 0, 0, 0); 来让云变得透明。以及其他 FBO 的任意格式(我使用 RGB565),包括mix FBO。另外,我必须在混合时完全禁用blending。但是当我混合这两种纹理时,云看起来是透明的(我透过它们看到了地平线)但现在有了颜色。我更新了问题,所以你可以看到我在做什么。
  • 所以,花了一些时间后,我得到了与上面截图几乎相同的结果,但是云的颜色是半透明的。但我需要有不透明的云。换句话说,使用这个着色器我失去了云的深度。
  • 不确定您使用的是我的两种方法中的哪一种,“Alternatively”之前或之后的部分。如果您使用第一种方法,则需要使用单独的混合函数。您可能必须使用第一种方法来防止云稍微透明。您需要标准的 GL_SRC_ALPHA、GL_ONE_MINUS_SRC_ALPHA 用于 RGB,以及 GL_ONE、GL_ONE 用于 A。另请注意,如果针对 Android,您必须指定 RGBA4444,而不是 RGBA8888。并非所有设备都支持 RGBA8888。
  • 非常感谢!我不知道glBlendFuncSeparate 功能。但是当我按照你所说的将Gdx.gl20.glBlendFuncSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE, GL20.GL_ONE); 应用于云时,它几乎开始按我的意愿工作。虽然,颜色和原来有点不一样,云有黑边。
  • 我不使用 Photoshop,只使用 Gimp。方法可能类似。在 Gimp 中: 1) 扁平化图像。 2)将图像的alpha通道复制到一个新的灰度层中。 (在 Gimp 中,这可以通过添加图层蒙版 -> 图层的 Alpha 通道来完成。然后选择蒙版并将其复制并粘贴为新图层。) 3) 将新图层的模式设置为正片叠底。 4)创建一个填充黑色的新图层,并将其放在其他两层下方。 5) 拼合图像。 6) 将 alpha 通道添加回图像。 (在 Gimp 中添加一个 Alpha 通道。将 Alpha 图像粘贴回去,因为它仍然在步骤 3 的剪贴板中,然后应用蒙版。)
猜你喜欢
  • 2021-04-07
  • 1970-01-01
  • 2011-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-20
  • 1970-01-01
相关资源
最近更新 更多