1

我最近开始研究适用于 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 参数,但找不到合适的组合来绘制我想要的东西。我附上了一些图片,展示了我想要实现的目标以及我目前拥有的目标。有什么建议么?

在此处输入图像描述

解决方案

感谢@ 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捕获工具造成的):

在此处输入图像描述

4

2 回答 2

1

如果您使用函数设置 glBlendFunc 并将 glBlendEquation方程一起使用,则目标颜色的计算如下:(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)GL_FUNC_ADD

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

例如,如果您C_dest = 1C_src = 0.5然后混合A_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 次混合后目标颜色已经改变(除了GL_ZERO)。

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

glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LESS );

// do the drawing with the color

glDisable( GL_DEPTH_TEST );

或者可以使用模板测试。例如,可以将模板测试设置为仅在模板缓冲区等于 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 );

扩展答案

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

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

片段着色器也可以访问该discard命令。执行时,此命令会导致片段的输出值被丢弃。因此,片段不会继续到下一个流水线阶段,并且任何片段着色器输出都会丢失。

片段着色器

if ( color.a < 1.0/255.0 )
    discard;
于 2017-07-28T17:18:21.420 回答
0

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

如果您想使用精确到像素的边缘进行路径/描边/填充渲染,您可能会使用模板蒙版和模板测试来获得某个地方,但在这种情况下您不能做透明度 - 只是布尔运算符。

于 2017-07-28T14:49:28.063 回答