点精灵alpha混合问题(Android / OpenGL ES 2.0)

时间:2017-07-28 13:26:54

标签: android opengl-es blending point-sprites

我最近开始研究适用于Android的OpenGL ES,并正在开发绘图应用程序。我已经实现了一些基础知识,如点精灵,路径平滑和FBO双缓冲。目前我正在使用glBlendFunc,更具体地说,当我使用相同的颜色/ alpha值将两个纹理彼此靠近时,alpha会被添加,因此它会在精灵的交叉处显得更暗。这是一个问题,因为如果很多点靠近在一起,则不会保留笔划不透明度,因为颜色往往更不透明而不是保持相同的不透明度。有没有办法让纹理在交点上具有相同的颜色,即交叉像素具有相同的alpha值,但保留其余像素的alpha值?

以下是我如何完成应用的相关部分:

  • 用于绘制点精灵列表我使用这样的混合:

    GLES20.glEnable(GLES20.GL_BLEND);
    GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
    
  • 该应用程序使用带纹理的FBO,在此处首先渲染每个笔刷笔触,然后将此纹理渲染到主屏幕。混合函数有:

    GLES20.glEnable(GLES20.GL_BLEND);
    GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
    
  • OpenGL ES 2.0不支持alpha掩码;

  • 在应用程序的任何位置都没有使用DEPTH_TEST函数;
  • 点精灵的纹理是具有透明背景的PNG;
  • 该应用程序支持纹理遮罩,这意味着一个纹理用于​​形状,一个纹理用于​​内容;
  • 我的片段着色器看起来像这样:

    precision mediump float;
    
    uniform sampler2D uShapeTexture;
    uniform sampler2D uFillTexture;
    uniform float vFillScale;
    
    varying vec4 vColor;
    varying float vShapeRotation;
    varying float vFillRotation;
    varying vec4 vFillPosition;
    
    vec2 calculateRotation(float rotationValue) {
        float mid = 0.5;
        return vec2(cos(rotationValue) * (gl_PointCoord.x - mid) + sin(rotationValue) * (gl_PointCoord.y - mid) + mid,
                cos(rotationValue) * (gl_PointCoord.y - mid) - sin(rotationValue) * (gl_PointCoord.x - mid) + mid);
    }
    
    void main() {
        // Calculations.
        vec2 rotatedShape = calculateRotation(vShapeRotation);
        vec2 rotatedFill = calculateRotation(vFillRotation);
        vec2 scaleVector = vec2(vFillScale, vFillScale);
        vec2 positionVector = vec2(vFillPosition[0], vFillPosition[1]);
    
        // Obtain colors.
        vec4 colorShape = texture2D(uShapeTexture, rotatedShape);
        vec4 colorFill = texture2D(uFillTexture, (rotatedFill * scaleVector) + positionVector);
    
        gl_FragColor = colorShape * colorFill * vColor;
    }
    
  • 我的顶点着色器是这样的:

    attribute vec4 aPosition;
    attribute vec4 aColor;
    attribute vec4 aJitter;
    attribute float aShapeRotation;
    attribute float aFillRotation;
    attribute vec4 aFillPosition;
    attribute float aPointSize;
    
    varying vec4 vColor;
    varying float vShapeRotation;
    varying float vFillRotation;
    varying vec4 vFillPosition;
    
    uniform mat4 uMVPMatrix;
    
    void main() {
        // Sey position and size.
        gl_Position = uMVPMatrix * (aPosition + aJitter);
        gl_PointSize = aPointSize;
    
        // Pass values to fragment shader.
        vColor = aColor;
        vShapeRotation = aShapeRotation;
        vFillRotation = aFillRotation;
        vFillPosition = aFillPosition;
    }
    

我尝试过使用glBlendFunc参数但我找不到合适的组合来绘制我想要的东西。我附上了一些图片,展示了我想要实现的目标以及我目前拥有的内容。有什么建议吗?

enter image description here

解决方案

由于@ Rabbid76,最后设法通过几行来正常工作。首先,在绘制到FBO之前,我必须配置深度测试功能:

GLES20.glEnable(GLES20.GL_DEPTH_TEST);
GLES20.glDepthFunc(GLES20.GL_LESS);

// Drawing code for FBO.

GLES20.glDisable(GLES20.GL_DEPTH_TEST);

然后在我的片段着色器中,我必须确保所有具有alpha<掩码中的1被丢弃如下:

...
vec4 colorMask = texture2D(uMaskTexture, gl_PointCoord);
if (colorMask.a < 1.0)
    discard;
else
    gl_FragColor = calculatedColor;

结果是(闪烁是由于Android模拟器和gif捕获工具):

enter image description here

2 个答案:

答案 0 :(得分:1)

如果设置glBlendFunc 使用函数(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)并使用 glBlendEquation使用等式GL_FUNC_ADD,然后是目标颜色 计算如下:

C_dest = C_src * A_src + C_dest * (1-A_src)

如果您将C_dest = 1C_src = 0.5A_src = 0.5混为一谈,那么:

C_dest = 0.75 = 1 * 0.5 + 0.5 * 0.5

如果您重复混合相同颜色C_src = 0.5A_src = 0.5,则目标颜色变为更暗

C_dest = 0.625 = 0.75 * 0.5 + 0.5 * 0.5

由于新目标颜色始终是原始目标颜色和源颜色的函数,因此在混合2次时颜色不能保持等同,因为目标颜色在第一次混合后已经更改({{1除外) }})。

您必须避免将任何片段混合两次。如果所有片段都绘制到相同的深度(2D),那么您可以使用深度测试:

GL_ZERO

或者可以使用模板测试。例如,模板测试可以设置为仅在模板缓冲区等于0时通过。 每次要写入片段时,模板缓冲区都会递增:

glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LESS );

// do the drawing with the color

glDisable( GL_DEPTH_TEST );

扩展答案

请注意,您可以discard不应绘制的片段。 如果精灵纹理中的片段的alpha通道为0,则应将其丢弃。

注意,如果丢弃片段,则不会写入颜色,深度和模板缓冲区。

  

片段着色器还可以访问glClear( GL_STENCIL_BUFFER_BIT ); glEnable( GL_STENCIL_TEST ); glStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); glStencilFunc( GL_EQUAL, 0, 255 ); // do the drawing with the color glDisable( GL_STENCIL_TEST ); 命令。执行时,此命令会导致丢弃片段的输出值。因此,片段不会继续进行下一个流水线阶段,并且任何片段着色器输出都会丢失。

片段着色器

discard

答案 1 :(得分:0)

使用OpenGL ES 2.0中的固定功能混合无法做到这一点,因为你想要的并不是实际的alpha混合。你想要的是一个逻辑操作(例如max(src,dst)),这与OpenGL ES混合的工作方式有很大不同。

如果你想用像素精确边进行路径/笔触/填充渲染,你可能会使用模板蒙版和模板测试,但在这种情况下你不能做透明 - 只是布尔运算符。