在 OpenGL ES 中混合不能像我认为的那样工作

Posted

技术标签:

【中文标题】在 OpenGL ES 中混合不能像我认为的那样工作【英文标题】:Blending in OpenGL ES not working like I think it should 【发布时间】:2015-10-20 00:32:38 【问题描述】:

我已将我的问题简化为画一个正方形来说明。我想绘制一个纹理,它将 alpha 通道从 0 淡化到 255 并垂直恢复到 0,以便它与背景混合。现在它使背景变暗,我不知道为什么。这是在 ios 9 上使用 OpenGL ES。

首先,我启用我认为我想要的混合:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

然后我用绿色填充缓冲区:

glClearColor(0, 0.5, 0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);

我将这张图片用作纹理:

我使用 GL_TRIANGLES 绘制以下顶点 (x, y, z,r, g, b, a,tx, ty):

0.4, 0.3, 0, 1, 0, 0, 1, 0, 0,
0.6, 0.3, 0, 1, 0, 0, 1, 1, 0,
0.4, 0.5, 0, 1, 0, 0, 1, 0, 1,
0.6, 0.3, 0, 1, 0, 0, 1, 1, 0,
0.4, 0.5, 0, 1, 0, 0, 1, 0, 1,
0.6, 0.5, 0, 1, 0, 0, 1, 1, 1,

我希望顶点的红色分量能够调节纹理,因为这是我的片段着色器:

varying lowp vec4 DestinationColor;

varying lowp vec2 TexCoordOut;
uniform sampler2D Texture;

void main(void) 
    gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut);

确实如此,但结果并不是简单的红绿绿。它在边缘变暗,我希望只画绿色:

here 类似问题的答案说我应该使用 glTexEnv(GL_BLEND)。不过,我认为这是一个 OpenGL ES 1 API。

有谁知道我怎样才能达到理想的混合效果?我希望红色在绿色中褪色。顶部和底部应该是绿色的,而不是更深的。不应有明显的水平边缘。

编辑:我正在添加更多图片来说明我的问题。这是 GIMP 中的两个粉红色点:

当我将一个放在另一个之上时(在 GIMP 中),它们很好地融合在一起。它们还与绿色背景完美融合:

当我在 OpenGL ES 中重新创建它时,我得到了变暗,这在粉红色点被覆盖的地方尤其明显。这次我做了一个很窄的,这样你可以更好地看到效果:

我尝试了 glBlendFunc 和 glBlendFuncSeparate,得到了相同的效果。请注意,背景 alpha 为 1。

【问题讨论】:

我对混合功能有点生疏,但是您的屏幕截图看起来类似于 Photoshop 的“乘法”。我认为您需要不同的混合功能,但现在不能完全回答哪个... 某处应该有常用混合函数的“备忘单”,供参考…… 我使用了这个混合功能,因为它被推荐为“正常”。 GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 应该意味着 color_blended = color_src * alpha_src + color_destination * (1 - alpha_src)。这就是为什么我很困惑。假设 alpha 为 0.1,红色为 1,绿色为 0.5,那么这些边应该是 0.1(1, 0, 0) + (1-0.1)(0,0.5,0) = (0.1, 0.45, 0),对?如果您打开一个颜色比较网站并将#1A7300(混合示例)与#008000(背景)进行比较,它并没有那么暗。 是的,它是您在混合位图(精灵)时使用的功能之一,具体取决于位图是否具有“预乘 alpha”(尽管我不记得这是哪一个)。我认为另一个在两个参数之一中使用 GL_ONE(同样,不记得哪个)。 记住source是传入的值(你要覆盖的那个),destination是帧缓冲区中已经存在的值:opengl.org/sdk/docs/man2/xhtml/glBlendFunc.xml 【参考方案1】:

您的问题很可能在于显示 Alpha 通道本身,而不是颜色。由于您使用直接混合功能,它会将 Alpha 通道设置为低于 1.0 的值,然后您将其视为显示的预乘颜色值:RGB = RGB*A。您应该使用单独的混合函数来单独处理 Alpha 通道:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE)

这将按照您的预期混合颜色,但将目标 alpha 值保持为 1.0,您设置为清晰颜色。

【讨论】:

这是非常有用的信息,这看起来是一种更好的混合方式。但我很遗憾地说它对我没有影响。 可能是您的纹理是从图像加载的,因此颜色是 alpha 预乘的?当您将图像转换为 RGBA 原始数据并且颜色 RGB 与 alpha 相乘产生阴影结果时,基本上会发生同样的事情。如果 RGB 部分相同或是否缩放(必须相同),请尝试检查一些半透明像素 好主意!我没有想到纹理本身可能有问题。我刚刚看了看,我正在使用(iOS 常量)kCGImageAlphaPremultipliedLast 将纹理绘制到内存中。我猜你几乎肯定找到了我问题的根源。使用 kCGImageAlphaLast 我根本没有得到任何数据,所以我将继续努力。我很快就会回来验证这确实是解决方案。 我已经确认问题确实是纹理。我还没有解决它,但我会标记你是正确的,因为我现在可以在调试器中清楚地看到问题。 奇怪的是你不能用“alpha last”创建。也许您应该添加此代码以及您尝试过的变体,解释问题所在。【参考方案2】:

如果您的设备使用默认值,即假定像素数据被预乘,那么您可能会得到与您所看到的结果类似的结果。我的建议是将您的 alpha 通道图像转换为简单的灰度,然后将其保存为 PNG 并验证它是灰度而不是 BGRA 格式。然后,以正常方式将这个每像素 8 位的数据加载到纹理中,而不必担心任何预乘问题(灰度不包含和 alpha 通道)。接下来,通过从灰度测试中读取单个字节,在着色器中执行预乘步骤,然后将现有 DestinationColor 的 3 个分量乘以该线性 alpha 值。例如,这个预乘步骤让我的着色器在 iOS 上工作。

【讨论】:

以上是关于在 OpenGL ES 中混合不能像我认为的那样工作的主要内容,如果未能解决你的问题,请参考以下文章

柔软、透明的笔触纹理没有像我预期的那样混合

无法理解 size_hint。为啥这个 .kv 简单的代码不能像我预期的那样工作?

OpenGL ES之混合的概念和使用

在 iOS 上的 OpenGL ES 着色器中混合多个纹理会导致反向行为

IOS OpenGL ES GPUImage 图像透明混合 GPUImageAlphaBlendFilter

IOS OpenGL ES GPUImage 图像源混合 GPUImageSourceOverBlendFilter