点精灵 alpha 混合问题(Android / OpenGL ES 2.0)
Point sprite alpha blending issue (Android / OpenGL ES 2.0)
我最近开始研究 Android 的 OpenGL ES,并且正在开发一个绘图应用程序。我已经实现了一些基础知识,例如点精灵、路径平滑和用于双缓冲的 FBO。目前我正在使用 glBlendFunc,更具体地说,当我将两个具有相同 color/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 捕获工具):
使用 OpenGL ES 2.0 中的固定功能混合无法做到这一点,因为您想要的实际上不是 alpha 混合。您想要的是逻辑运算(例如 max(src, dst)),这与 OpenGL ES 混合的工作方式大不相同。
如果你想用像素精确的边缘进行路径/描边/填充渲染,你可能会使用模板蒙版和模板测试,但在这种情况下你不能做透明度 - 只能布尔运算符。
如果您设置 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 = 1
与 C_src = 0.5
和 A_src = 0.5
混合,则:
C_dest = 0.75 = 1 * 0.5 + 0.5 * 0.5
如果您重复混合相同的颜色 C_src = 0.5
和 A_src = 0.5
,那么目标颜色会变得 更暗 :
C_dest = 0.625 = 0.75 * 0.5 + 0.5 * 0.5
由于新的目标颜色始终是原始目标颜色和源颜色的函数,因此混合2次时颜色不能保持一致,因为目标颜色在第一次混合后已经改变(除了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 );
答案的扩展
请注意,您可以 discard 不应绘制的片段。
如果精灵纹理中的片段的 alpha 通道为 0,则应丢弃它。
请注意,如果您丢弃片段,则不会写入颜色、深度和模板缓冲区。
Fragment shaders also have access to the discard
command. When executed, this command causes the fragment's output values to be discarded. Thus, the fragment does not proceed on to the next pipeline stages, and any fragment shader outputs are lost.
片段着色器
if ( color.a < 1.0/255.0 )
discard;
我最近开始研究 Android 的 OpenGL ES,并且正在开发一个绘图应用程序。我已经实现了一些基础知识,例如点精灵、路径平滑和用于双缓冲的 FBO。目前我正在使用 glBlendFunc,更具体地说,当我将两个具有相同 color/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 捕获工具):
使用 OpenGL ES 2.0 中的固定功能混合无法做到这一点,因为您想要的实际上不是 alpha 混合。您想要的是逻辑运算(例如 max(src, dst)),这与 OpenGL ES 混合的工作方式大不相同。
如果你想用像素精确的边缘进行路径/描边/填充渲染,你可能会使用模板蒙版和模板测试,但在这种情况下你不能做透明度 - 只能布尔运算符。
如果您设置 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 = 1
与 C_src = 0.5
和 A_src = 0.5
混合,则:
C_dest = 0.75 = 1 * 0.5 + 0.5 * 0.5
如果您重复混合相同的颜色 C_src = 0.5
和 A_src = 0.5
,那么目标颜色会变得 更暗 :
C_dest = 0.625 = 0.75 * 0.5 + 0.5 * 0.5
由于新的目标颜色始终是原始目标颜色和源颜色的函数,因此混合2次时颜色不能保持一致,因为目标颜色在第一次混合后已经改变(除了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 );
答案的扩展
请注意,您可以 discard 不应绘制的片段。 如果精灵纹理中的片段的 alpha 通道为 0,则应丢弃它。
请注意,如果您丢弃片段,则不会写入颜色、深度和模板缓冲区。
Fragment shaders also have access to the
discard
command. When executed, this command causes the fragment's output values to be discarded. Thus, the fragment does not proceed on to the next pipeline stages, and any fragment shader outputs are lost.
片段着色器
if ( color.a < 1.0/255.0 )
discard;