OpenGL/GLSE alpha 遮罩

Posted

技术标签:

【中文标题】OpenGL/GLSE alpha 遮罩【英文标题】:OpenGL/GLSE alpha masking 【发布时间】:2016-11-29 22:23:57 【问题描述】:

我正在使用 OpenGL/GLSL 实现一个绘画应用程序。 有一个功能,即用户使用带有图案图像的画笔绘制“蒙版”,同时背景根据画笔位置而变化。看视频了解一下:video

我使用 CALayer 的遮罩(ios 的东西)来实现这个效果(在视频上)。但是这种实现非常昂贵,fps 非常低。所以我决定为此使用OpenGL。 对于 OpenGL 实现,我使用 Stencil 缓冲区进行遮罩,即:

glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, 0);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
// Draw mask (brush pattern)
glStencilFunc(GL_EQUAL, 1, 255);
// Draw gradient background
// Display the buffer
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];

问题:Stencil 缓冲区不适用于 alpha,这就是为什么我不能为画笔使用半透明图案。

问题:如何使用 OpenGL/GLSL 但不使用 Stencil 缓冲区 从视频中实现这种效果?

【问题讨论】:

你说背景变了。所以你已经生成了合适的背景?如果是,背景是否定义为全屏纹理? @MaticOblak ,是的,我为每次绘制迭代生成了适当的背景。我使用着色器生成渐变,作为输入数据,我使用触摸坐标(“开始”和“结束”触摸)。 【参考方案1】:

由于您的背景已经生成(来自 cmets),那么您可以简单地在着色器中使用 2 个纹理来绘制每个段。您需要重新绘制所有这些,直到用户抬起手指为止。

所以假设你有一个纹理,上面有一个白色的脚印,带有 alpha 通道 footprintTextureID 和一个背景纹理“backgroundTextureID”。您需要使用 activeTexture 1 和 2 绑定两个纹理,并将 2 作为均匀传递到着色器中。

现在在您的顶点着色器中,您需要从该位置生成相对纹理坐标。应该有类似于gl_Position = computedPosition;的行,所以你需要添加另一个varying值:

backgroundTextureCoordinates = vec2((computedPosition.x+1.0)*0.5, (computedPosition.y+1.0)*0.5);

或者如果你需要垂直翻转

backgroundTextureCoordinates = vec2((computedPosition.x+1.0)*0.5, (-computedPosition.y+1.0)*0.5):

(这个等式的原因是输出顶点在区间[-1,1]中,但纹理使用[0,1][-1,1]+1 = [0,2]然后[0,2]*0.5 = [0,1])。

好的,假设您正确绑定了所有这些,您现在只需将片段着色器中的颜色相乘即可获得混合颜色:

uniform sampler2D footprintTexture;
varying lowp vec2 footprintTextureCoordinate;

uniform sampler2D backgroundTexture;
varying lowp vec2 backgroundTextureCoordinates;

void main() 
    lowp vec4 footprintColor = texture2D(footprintTexture, footprintTextureCoordinate);
    lowp vec4 backgroundColor = texture2D(backgroundTexture, backgroundTextureCoordinates);
    gl_FragColor = footprintColor*backgroundColor;

如果您愿意,您可以乘以足迹中的 alpha 值,但这只会失去灵活性。在足迹纹理变为白色之前,它没有任何区别,所以它是您的选择。

【讨论】:

嘿。感谢您的建议,我认为这对我有用。今天试一下。 嘿,这个解决方案有效,谢谢!但我遇到了新问题——我不确定什么是清除纹理的正确方法。你该怎么做?基本上我使用 FBO 来绘制纹理。要清除这个纹理,我应该清除这个FBO还是直接清除一个纹理? 当纹理作为颜色缓冲区附加到帧缓冲区时,您可以将其用作任何其他颜色缓冲区。因此,只需调用 glClear 即可将纹理清除为您之前设置的特定颜色。只需确保在调用时绑定了正确的帧缓冲区。还有其他方法,例如您可以调用文本子图像将新数据发送到纹理,但这种方法对性能非常重要,只能清除它。另一种方法是简单地在整个纹理上绘制一个矩形,这似乎又是一种矫枉过正。 感谢您的澄清,但我已经解决了。 glClear 不适用于我的情况,所以我不得不使用着色器。另外,我已将此答案标记为“已接受”。再次感谢。【参考方案2】:

Stencil 是一个布尔开/关测试,因此正如您所说,它无法应对 alpha。

唯一适用于 Alpha 的 GL 技术是混合,但由于帧之间的颜色变化,您不能简单地将其在单次传递中扁平化为单个图层。

在我看来,这听起来像是您需要在屏幕外缓冲区中维护多个独立图层,然后每帧将它们混合在一起以形成屏幕上显示的内容。这使您可以完全独立于每帧更新每一层的方式。

【讨论】:

这是我的想法。您实际上可以创建一个 FBO,它会使用颜色蒙版绘制背景颜色以禁用 alpha,然后仅使用 alpha 通道绘制图像。但我想说这里的问题是当一条路径交叉时会发生什么?我希望如果您创建一个圆圈,那么第一个(底部)图像将是红色的,而与第一个重叠的最后一个(顶部)图像将是绿色的。但这与使用后台方法相冲突... 即使这样做也更容易只有背景纹理(拖动时会发生变化),然后使用具有 2 个纹理(足迹图像和背景一个)的着色器,然后操作背景纹理坐标对应于背景纹理位置。这种方式根本不需要 FBO。 是的,好电话。我也在考虑两个独立的屏幕外缓冲区。顺便说一句,我以前没有尝试过混合缓冲区,所以你能给我一些建议吗?例如,有两个屏幕外缓冲区:A - 带有画笔笔划的缓冲区,B - 背景缓冲区?如何混合和绘制这两者? 那么在您的情况下,您将需要使用 2 个纹理。每个 FBO 都需要一个附加的纹理(甚至不需要渲染缓冲区)。然后使用活动纹理能够同时分配两个纹理并将它们传递给着色器。如果确保 FBO 与主缓冲区具有相同的尺寸,则可以在纹理上使用 GL_NEAREST 参数来获得性能。但是如果您需要 POT 纹理,可能需要做更多的工作;使用大于缓冲区的第一个 POT 尺寸,然后使用目标缓冲区大小的视口。然后调整纹理坐标。

以上是关于OpenGL/GLSE alpha 遮罩的主要内容,如果未能解决你的问题,请参考以下文章

libgdx open gles 2.0 模板 alpha 遮罩

遮罩层的实现

如何给C4D添加多个层遮罩?

flash cs5.5中如何实现边缘模糊遮罩

如何用css实现半透明遮罩层效果

bootstrap弹框去除遮罩层效果