OpenGL ES 2.0 PNG alpha通道

9
我正在学习如何在Android中使用OpenGL ES 2.0。我一直试图简单地在屏幕中央显示一张纹理,这很容易,但是我似乎无法正确地使用PNG透明度。根据我的设置,图像要么显示为黑色背景,要么整个图像会与背景颜色稍微混合。我按照实际教程进行操作,但到目前为止从未使用过透明度,因此我尝试了搜索到的代码,并可能错过了一个重要步骤。我花了很多时间搜索解决这个问题,但没有看到任何答案涉及我的设置。我尝试了各种glBlendFunc等组合,但都没用。如果我尝试粘贴所有可能与此相关的代码块,那么这个问题似乎会非常臃肿,所以我将很乐意发布你们要求的任何代码片段。我非常感谢您提供的任何想法和建议。
编辑:以下是我的片段着色器,我认为这是原因所在。这是我从未真正找到过合适的关于透明度的例子的唯一部分,而其他部分都与我在其他地方看到的相匹配。
        final String fragmentShader =           
        "precision mediump float;       \n"  
        + "varying vec2 v_Color;          \n"     
        + "uniform sampler2D s_baseMap;   \n"
        + "void main()                    \n"     
        + "{                              \n"
        + "  vec4 baseColor;              \n"
        + "  baseColor = texture2D( s_baseMap, v_Color );   \n"
        + "   gl_FragColor = baseColor;     \n"    
        + "}                              \n"; 

这段代码并没有明确使用alpha通道,毕竟它是来自一个不使用alpha通道的示例。但由于我对片段着色器并不了解,而且当将图像混合到背景中时似乎“有点”有效,所以我认为它以某种形式与alpha通道一起工作,并且只是设置有误。

编辑:这里是“loadTexture”方法。它与我正在学习的OpenGL ES 2.0书籍中的示例大致相同,但进行了一些修改,这些修改似乎使图像更接近正常工作。

    private int loadTexture ( InputStream is )
{
    int[] textureId = new int[1];
    Bitmap bitmap;
    bitmap = BitmapFactory.decodeStream(is);
    byte[] buffer = new byte[bitmap.getWidth() * bitmap.getHeight() * 4];

    for ( int y = 0; y < bitmap.getHeight(); y++ )
        for ( int x = 0; x < bitmap.getWidth(); x++ )
        {
            int pixel = bitmap.getPixel(x, y);
            buffer[(y * bitmap.getWidth() + x) * 4 + 0] = (byte)((pixel >> 16) & 0xFF);
            buffer[(y * bitmap.getWidth() + x) * 4 + 1] = (byte)((pixel >> 8) & 0xFF);
            buffer[(y * bitmap.getWidth() + x) * 4 + 2] = (byte)((pixel >> 0) & 0xFF);
        }

    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bitmap.getWidth() * bitmap.getHeight() * 4);
    byteBuffer.put(buffer).position(0);

    GLES20.glGenTextures ( 1, textureId, 0 );
    GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, textureId[0] );

    GLES20.glTexImage2D ( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(), 0, 
                          GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, byteBuffer );

    GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR );
    GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR );
    GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE );
    GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE );

    return textureId[0];
}

我明白代码在做什么,但是由于我的知识不足,有些地方还是让我感到困惑,可能是我忽略了一些显而易见的东西。

我没有看到其他任何可能导致问题的代码部分,但编程总是充满着意外(尤其是在OpenGL世界中),所以如果你认为其他原因是导致问题的根源,我也会把它们发给你。非常抱歉给你带来这么多麻烦!


在将PNG图片加载到纹理中时,确保Alpha通道得到保留(而不是加载RGB图像或创建RGB纹理)。当然,除了设置正确的glBlendFunc之外,还要确保调用glEnable(GL_BLEND)。但是,一些代码肯定会更有帮助。也许只需要加载图像和创建纹理的代码以及设置混合状态的代码。当然,由于您是新手,请不要忘记了解有关接受和点赞功能的信息。 - Christian Rau
片段着色器似乎是有效的,因为它正确地将纹理的 alpha 值委托给了片段颜色。因此,您无法避免发布一些加载和纹理创建代码。 - Christian Rau
你可以尝试使用禁用混合的gl_FragColor = vec4(baseColor.aaa, 1.0)。这应该会给你一个带有 alpha 通道的灰度图像。 - trenki
当我尝试使用gl_FragColor设置时,似乎每次都会给我一个纯黑色的框。显然,这不太对,但我还不确定这对我的设置意味着什么。我也会在主贴中发布我正在使用的“loadTexture”方法,因为那是代码中唯一让我有点困惑的部分,所以我认为它可能隐藏在那里。 - DaeKo
你是怎么让它工作的?通过更改着色器还是更改loadTexture()函数? - Ashwini Shahapurkar
3个回答

9
改变
 for ( int y = 0; y < bitmap.getHeight(); y++ )
    for ( int x = 0; x < bitmap.getWidth(); x++ )
    {
        int pixel = bitmap.getPixel(x, y);
        buffer[(y * bitmap.getWidth() + x) * 4 + 0] = (byte)((pixel >> 16) & 0xFF);
        buffer[(y * bitmap.getWidth() + x) * 4 + 1] = (byte)((pixel >> 8) & 0xFF);
        buffer[(y * bitmap.getWidth() + x) * 4 + 2] = (byte)((pixel >> 0) & 0xFF);
    }

转换成

 for ( int y = 0; y < bitmap.getHeight(); y++ )
    for ( int x = 0; x < bitmap.getWidth(); x++ )
    {
        int pixel = bitmap.getPixel(x, y);
        buffer[(y * bitmap.getWidth() + x) * 4 + 0] = (byte)((pixel >> 16) & 0xFF);
        buffer[(y * bitmap.getWidth() + x) * 4 + 1] = (byte)((pixel >> 8) & 0xFF);
        buffer[(y * bitmap.getWidth() + x) * 4 + 2] = (byte)((pixel >> 0) & 0xFF);
        buffer[(y * bitmap.getWidth() + x) * 4 + 3] = (byte)((pixel >> 24) & 0xFF);
    }

如果需要包含alpha信息,则只需添加

GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
GLES20.glEnable(GLES20.GL_BLEND); 

在绘制纹理之前,记得禁用GL_BLEND。完成后立即禁用。

3
您最可能希望使用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)作为混合函数,并确保将纹理的alpha值写入gl_FragColor片段着色器输出变量。为了让所有这些都能正常工作,您上传的纹理数据必须包含alpha值,并且必须使用支持alpha通道的纹理格式(RGBA,RGBA8等)。您可以通过将alpha值路由到RGB颜色分量并检查所得到的图像来验证此内容。 编辑: 在您的图像加载代码中,您忘记复制alpha通道!请尝试 davebytes 提供的建议。

如果我在glBlendFunc上使用那个设置,图像根本不会显示出来。似乎glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)对我来说效果最好,或者至少最接近正常工作。我还应该提到,当图像混合到背景中时,它似乎也会去除图像的透明部分。我认为我的问题在于片段着色器,因为我对它了解不多,它大部分是从一个没有使用透明度的示例代码复制而来。我将在主贴中发布它。 - DaeKo

1

你的初始着色器没问题。Alpha通道在颜色操作中是固有的,但根据混合/模式等可能不会应用。

如果你将fragcolor = base.aaa,而纹理变成黑色,那就意味着你的纹理数据是“错误”的。

看看你的纹理加载,是的,它是错误的。你从未复制过alpha通道,只复制了RGB通道。假设Java将字节数组清零,所有alpha通道都将为零,这将导致你的黑盒子,当你启用alpha混合时,图像将“消失”。

为了简化你的生活,你可以直接正常加载位图,并使用GLUtils助手上传,而不是直接使用glTexImage2d:

  bitmap = BitmapFactory.decodeStream(is);  
  GLES20.glGenTextures ( 1, textureId, 0 );
  GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, textureId[0] );
  GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

大致如此。然后启用混合,如果未预乘,请使用src+invsrc混合模式,并进行渲染,您应该得到所需的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接