【问题标题】:fragment shader blending and gl_LastFragColor undeclared identifier on android 2.3, OPENGL ES 2.0android 2.3、OPENGL ES 2.0 上的片段着色器混合和 gl_LastFragColor 未声明标识符
【发布时间】:2014-05-03 20:12:45
【问题描述】:

主要问题:

1) 仅当目标颜色不透明时,如何让片段着色器不将当前像素更新为透明像素?

2) gl_LastFragColor 是否适用于 android 2.3 OpenGL ES 2.0?

背景:

我正在尝试使用批量纹理使用 OpenGL ES 2.0 编写文本。片段着色器中的挑战是仅在目标像素颜色的 alpha 为 1 时才更新文本的像素颜色,我知道这意味着像素不属于文本。

文本图集的纹理存储在一个位图中:Bitmap.Config.ALPHA_8

几个小时后,我发现了三个问题:

第一个问题:不知何故,alpha 1 被用作不透明而 0 被用作透明。

第二个问题:假设我显示“Hi”。虽然我的 draworder 应该通过纹理 H,然后是 i 来绘制“Hi”,但它的做法是:“i”,然后是“H”。

我知道这一点,因为如果我手动将“Hi”的顶点顺序反转为从“i”开始,那么我会得到我的文本。由于问题 3,这是一个问题。

第三个问题:我相信这是主要问题。假设字母 H 的纹理框的宽度正好为 0.5,但我使用宽度为 1 的纹理,其中第一个 0.25 是白色,alpha 1,下一个 0.5 是白色,alpha 0,最后一个 0.25 是白色,alpha 1。同样为“我”。

当“i”首先被纹理化,然后“H”被纹理化后,宽度将相交(故意,例如“DD”和“dd”具有不同的组合宽度)。但是在“i”之后渲染“H”之后,它会将其宽度上的所有像素更新为透明,并且大部分字母 o 消失了。如果先渲染“i”然后渲染“H”并且禁用混合,则这是正确的行为。

所以我尝试在更新之前使用 gl_LastFragColor 检查前一个像素,但我得到:未声明的标识符。所以我被这个片段代码卡住了:

片段代码:

vec4 myTexture;
myTexture = texture2D(u_Texture, v_TexCoordinate);

if( myTexture[3] < 0.01){ // The texture is Bitmap.Config.ALPHA_8. If alpha is 0 . . .
    gl_FragColor = vec4(0.0,0.0,0.0,0.0);  // 0.0 is somehow transparent.
}
else{
    gl_FragColor = vec4(1.0,1.0,1.0,1.0); // display text in white. Somehow 1.0 is opaque.
    gl_FragColor = myTexture;

}

我称draw为:

    GLES20.glEnable (GLES20.GL_BLEND);
    GLES20.glBlendFunc (GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
    //      GLES20.glBlendFunc (GLES20.GL_ONE_MINUS_SRC_ALPHA, GLES20.GL_SRC_ALPHA); This makes alpha of 1 transparent!!


    GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, buffers[2]);
    GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrderLength, GLES20.GL_UNSIGNED_SHORT, 0);

    GLES20.glDisable (GLES20.GL_BLEND);

附:如果我将 alpha 从 1 切换到 0,或者等效的技巧,我会遇到同样的问题,透明度倒置。

【问题讨论】:

    标签: android opengl-es


    【解决方案1】:

    我认为您可以在不使用gl_LastFragColor 的情况下完成您想要实现的目标(正如 Andon 所指出的,这不是标准 OpenGL ES 的一部分)。

    我会研究使用目标 alpha 的混合函数。您可能需要使用它来找出完全符合您要求的内容,但请从以下内容开始:

    glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
    

    对于帧缓冲区中 alpha 值已为 1.0 的任何像素,帧缓冲区将保持不变,如果帧缓冲区中的 alpha 值为 0.0,则将帧缓冲区像素替换为当前片段。

    要使用目标 alpha,请在创建 GLSurfaceView 时选择具有 alpha 平面的帧缓冲区配置。在调用 setRenderer 之前添加它会解决这个问题:

    setEGLConfigChooser(8, 8, 8, 8, 16, 0);
    

    还要确保在 glClearColor 调用中使用 0.0 作为 alpha 的明确值。

    为了确保不绘制纹理的透明部分,您通过将gl_FragColor 设置为完全透明所做的操作应该适用于此。另一种选择是您 discard 着色器中的那些片段。

    【讨论】:

    • 嗯。看来我必须做一些功课来学习 setEGLCOnfigChooser。感谢您的意见。
    • 我已经考虑过这一点,但是帧缓冲区中 alpha 通道位于 0.01.0 之间的像素呢?您提到的像素格式中有 8 位目标 alpha,因此它有很多机会成为 0 或 1 以外的东西:-\。
    • @AndonM.Coleman:根据海报的描述,听起来他们可能只处理完全透明/不透明的值。如果涉及半透明几何,它肯定会变得更加棘手。如果不详细了解应用程序和期望的结果,很难说出最佳方法是什么。我认为混合方法与目标 alpha 方法值得考虑。
    【解决方案2】:

    那是不适合便携的。

    gl_LastFragColor 是 NV 和 Apple 通过(几乎相同的)扩展添加的东西。

    Apple 的扩展名为GL_APPLE_shader_framebuffer_fetch,您不会在 Android 上说扩展。 NV 具有相同的扩展名,只需将 APPLE 替换为 NV 并确保您拥有必要的 Tegra GPU。

    假设您觉得完全可以将自己限制在运行 NV Tegra GPU 的 Android 设备上,您也许可以通过将以下行放在着色器顶部来在 GLSL 着色器中获得此功能:

    #extension GL_NV_shader_framebuffer_fetch : require
    

    如果其他供应商支持此功能,他们可能会使用GL_EXT_shader_framebuffer_fetch。然而,最重要的是,这是一个扩展,在 iOS 以外的任何地方都不能广泛使用。

    【讨论】:

    • 谢谢。这澄清了这部分。你对为什么混合效果不好有什么建议吗?
    • 我把它作为回答,因为我想出了第二部分(运气不错!),但你回答的第一部分几乎没有在线信息。
    【解决方案3】:

    我想通了。我看到 here 使用了discard 的一个很好的解释。

    我的最终代码是:

    void main()
    {
    vec4 myTexture;
    myTexture = texture2D(u_Texture, v_TexCoordinate);
    
    if( myTexture[3] < 0.01){
        discard;
    }
    else{
        gl_FragColor = myTexture;
    
    }
    
    }
    

    Discard 实际上不会覆盖像素。

    【讨论】:

    • 我在回答中提到了 discard 作为建议解决方案的一部分。但我认为仅此一项并不能解决您所描述的全部问题。您说您不想在帧缓冲区中绘制之前已经绘制过的像素。就这样,您仍然可以在else 的情况下写入像素,而与帧缓冲区中已有的内容无关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多