OpenGL Stencil - 排除透明像素

Posted

技术标签:

【中文标题】OpenGL Stencil - 排除透明像素【英文标题】:OpenGL Stencil - Exclude transparent pixels 【发布时间】:2015-03-27 15:30:04 【问题描述】:

我有这个纹理,我想使用模板缓冲区作为掩码:

然后我想在屏幕上绘制一个图像 IMG,它应该只出现在上面图像可见的地方(有颜色的地方),因此不包括顶部边缘上方和渐变下方的透明像素。

问题是每当我在屏幕上绘制图像 IMG 时,无论像素是否透明,它都会出现在绘制图像的任何地方。

所以我考虑过使用 ALPHA_TEST,但它在最新的 OpenGL 版本中已经消失,所以我尝试在我的片段着色器中丢弃带有 v_color.a == 0v_color.r == 0 的片段,但它什么也没做...

片段着色器:

#ifdef GL_ES
    #define LOWP lowp
    precision mediump float;
#else
    #define LOWP
#endif

uniform sampler2D u_texture;

varying LOWP vec4 v_color;
varying vec2 v_texCoords;

void main() 
    if (v_color.a == 0) 
        discard;
    

    gl_FragColor = v_color * texture2D(u_texture, v_texCoords);

顶点着色器:

attribute vec4 a_color;
attribute vec4 a_position;
attribute vec2 a_texCoord0;

uniform mat4 u_projTrans;

varying vec4 v_color;
varying vec2 v_texCoords;

void main() 
    v_color = a_color;
    v_texCoords = a_texCoord0;
    gl_Position = u_projTrans * a_position;

代码:

@Override
public void draw(final DrawConfig drawConfig) 
    this.applyTransform(drawConfig.getBatch(), this.computeTransform());

    // Draw mask to stencil buffer

    drawConfig.end();
    final GL20 gl = Gdx.gl;

    gl.glEnable(GL20.GL_STENCIL_TEST);
    gl.glStencilFunc(GL20.GL_ALWAYS, 1, 0xFF);
    gl.glStencilOp(GL20.GL_REPLACE, GL20.GL_REPLACE, GL20.GL_REPLACE);
    gl.glStencilMask(0xFF);
    gl.glColorMask(false, false, false, false);
    gl.glClearStencil(0);
    gl.glClear(GL20.GL_STENCIL_BUFFER_BIT);

    drawConfig.begin(Mode.BATCH);
    this.mBackground.setClippingEnabled(true); // The clipping area is a rectangle centered in the screen
    this.mBackground.draw(drawConfig);
    this.mBottomBorder.draw(drawConfig); // Border below the rectangle area
    this.mTopBorder.draw(drawConfig); // Border above the rectangle area
    drawConfig.end();

    gl.glColorMask(true, true, true, true);
    gl.glStencilFunc(GL20.GL_EQUAL, 1, 0xFF);
    gl.glStencilMask(0x00);

    // Draw elements

    this.mBackground.setClippingEnabled(false); // No clipping area, use stencil test
    this.mBackground.draw(drawConfig);
    this.mBottomBorder.draw(drawConfig);
    this.mTopBorder.draw(drawConfig);

    drawConfig.end();
    Gdx.gl.glDisable(GL20.GL_STENCIL_TEST);

    this.resetTransform(drawConfig.getBatch());

结果:

红色箭头显示了超出边界的部分,并且我想让它们消失。 黄色方块为剪裁区域,为您提供方便。

【问题讨论】:

该图像具有部分透明度,并且模板测试是全有或全无。无论如何,请发布您的代码。 如果您只是使用模板缓冲区来剪辑另一个纹理,您可以在片段着色器中自己进行混合。话虽如此,丢弃基于 alpha 的像素应该可行。也许发布您尝试使用的代码? 给定纹理过滤,您可能不想对采样的 alpha 值进行二进制 `== 0` 测试。考虑将其设为if (v_color.a <= 0.01)。然而,既然您已经发布了您的实际代码,很明显v_color 与纹理 alpha 无关 - 但是该值将被内插,并且它可能永远不会内插到精确的 0 @Mokosha 我更新了我的帖子。 好的,你想复制GL_ALPHA_TEST 的效果,对吧?该测试在片段着色器评估最终颜色后执行,而不是顶点颜色。所以如果你想自己实现这个,你需要测试gl_FragColor.a而不是v_color.a 【参考方案1】:

您尝试复制的固定功能 alpha 测试 (GL_ALPHA_TEST) 是按片段测试。它曾经发生在片段着色器完成之后,您可以使用discard 自己实现它。

但是,您当前的解决方案是使用插值的每顶点 alpha 值,这与您尝试用作蒙版的纹理无关。您需要测试分配给 gl_FragColor 的 alpha 值才能正确执行此操作:

void main() 
    gl_FragColor = v_color * texture2D(u_texture, v_texCoords);

    if (gl_FragColor.a == 0.0) 
        discard;
    

这是现代基于着色器的等价物:

glAlphaFunc (GL_NOTEQUAL, 0.0f); // Reject fragments with alpha == 0.0
glEnable    (GL_ALPHA_TEST);

【讨论】:

以上是关于OpenGL Stencil - 排除透明像素的主要内容,如果未能解决你的问题,请参考以下文章

渲染具有透明度的纹理时 OpenGL 不需要的像素

OpenGL 混合功能

在 OpenGL 中正确使用 stencil_texturing

裁剪以排除参差不齐的边缘上的任何透明度

OpenGL Stencil Buffer,它们的支持程度如何?

OpenGL Stencil:GL_REPLACE_VALUE_AMD 的可用性