我认为您从 cmets 和其他答案中获得了一些有用的方向,但没有详细说明解决方案。与其只给出结果,不如让我来介绍一下,以便您下次知道如何自己解决。
我假设您使用GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 混合函数将半透明对象从后向前绘制。你没有直接提到这一点,但它是相当标准的,并且与你所看到的一致。正确的解决方案还需要帧缓冲区中的 alpha 组件,但您已经拥有了,否则在读取 alpha 时您将一无所获。
为了说明整个过程,我将在途中使用两个示例。我将只列出颜色的 (R, A) 组件,G 和 B 的行为就像 R。
- 绘制一个颜色为
(R1, 1.0) 的图层,然后在其上绘制一个带有(R2, 0.4) 的图层。
- 绘制一个颜色为
(R1, 0.5) 的图层,然后在其上绘制一个带有(R2, 0.4) 的图层。
背景颜色是(Rb, 0.0),对于这种混合,您总是希望使用 0.0 的 alpha 值来清除。
首先,让我们计算一下我们想要为颜色实现的结果:
-
对于案例 1,绘制第一层完全覆盖了背景,因为它的 alpha = 1.0。然后我们在上面混合第二层。由于它的 alpha = 0.4,我们保留第一层的 60%,并添加第二层的 40%。所以我们想要的颜色是
0.6 * R1 + 0.4 * R2
-
对于情况 1,绘制第一层会保留 50% 的背景背景,因为它的 alpha = 0.5。所以到目前为止的颜色是
0.5 * Rb + 0.5 * R1
然后我们在上面混合第二层。我们再次保留之前颜色的 60%,并添加第二层的 40%。所以我们想要的颜色是
0.6 * (0.5 * Rb + 0.5 * R1) + 0.4 * R2
= 0.3 * Rb + 0.3 * R1 + 0.4 * R2
现在,让我们弄清楚我们希望 alpha 的结果是什么:
使用GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 进行混合可为您提供所需的颜色结果。但正如您所注意到的,不适用于 alpha。对示例进行计算:
-
案例1:绘制图层1,SRC_ALPHA为1.0,源值为S = (R1, 1.0),目标值为D = (Rb, 0.0)。所以混合函数的计算结果为
SRC_ALPHA * S + ONE_MINUS_SRC_ALPHA * D
= 1.0 * (R1, 1.0) + 0.0 * (Rb, 0.0)
= (R1, 1.0)
这被写入帧缓冲区,并成为绘制第 2 层的目标值。第 2 层的源是(R2, 0.4)。用 0.4 评估 SRC_ALPHA 给出
SRC_ALPHA * S + ONE_MINUS_SRC_ALPHA * D
= 0.4 * (R2, 0.4) + 0.6 * (R1, 1.0)
= (0.4 * R2 + 0.6 * R1, 0.76)
-
案例2:绘制图层1,SRC_ALPHA为0.5,源值为S = (R1, 0.5),目标值为D = (Rb, 0.0)。所以混合函数的计算结果为
SRC_ALPHA * S + ONE_MINUS_SRC_ALPHA * D
= 0.5 * (R1, 0.5) + 0.5 * (Rb, 0.0)
= (0.5 * R1 + 0.5 * Rb, 0.25).
这被写入帧缓冲区,并成为绘制第 2 层的目标值。第 2 层的源是(R2, 0.4)。用 0.4 评估 SRC_ALPHA 给出
SRC_ALPHA * S + ONE_MINUS_SRC_ALPHA * D
= 0.4 * (R2, 0.4) + 0.6 * (0.5 * R1 + 0.5 * Rb, 0.25)
= (0.4 * R2 + 0.3 * R1 + 0.3 * Rb, 0.31).
所以我们确认了您已经知道的:我们得到了所需的颜色,但 alpha 值错误。我们如何解决这个问题?我们需要一个不同的 alpha 混合函数。幸运的是,OpenGL 有glBlendFuncSeparate(),它允许我们做到这一点。我们只需要弄清楚 alpha 使用什么混合函数。以下是思考过程:
假设我们已经渲染了一些半透明对象,总 alpha 为A1,存储在帧缓冲区中。到目前为止,我们渲染的内容吸收了总光的一部分 A1,并让一部分 1.0 - A1 通过。我们在上面渲染另一个带有 alpha A2 的层。该层吸收了之前通过的光的一部分A2,因此它吸收了所有光的额外(1.0 - A1) * A2。我们需要将此添加到已经吸收的光量中,这样现在总共吸收了(1.0 - A1) * A2 + A1。
剩下要做的就是将其转换为 OpenGL 混合方程。 A2 是源值S,A1 是目标值D。所以我们想要的 alpha 结果变成了
(1.0 - A1) * A2 + A1
= (1.0 - A1) * S + 1.0 * D
我所说的A1是framebuffer中的alpha值,在混合函数规范中称为DST_ALPHA。所以我们使用ONE_MINUS_DST_ALPHA 来匹配我们的源乘数1.0 - A1。我们使用GL_ONE 来匹配目标乘数1.0。
所以alpha的blend函数参数是(GL_ONE_MINUS_DST_ALPHA, GL_ONE),完整的blend函数调用是:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE_MINUS_DST_ALPHA, GL_ONE);
我们可以再用示例再次检查 alpha 的数学:
-
案例1:绘制图层1,DST_ALPHA为0.0,源值为S = (.., 1.0),目标值为D = (.., 0.0)。所以混合函数的计算结果为
ONE_MINUS_DST_ALPHA * S + ONE * D
= 1.0 * (.., 1.0) + 1.0 * (.., 0.0)
= (.., 1.0)
这被写入帧缓冲区,并成为绘制第 2 层的目标值。第 2 层的源是 (.., 0.4),DST_ALPHA 现在是 1.0。评估第 2 层的混合方程给出了
ONE_MINUS_DST_ALPHA * S + ONE * D
= 0.0 * (.., 0.4) + 1.0 * (.., 1.0)
= (.., 1.0)
我们得到了所需的 alpha 值1.0!
-
案例2:绘制图层1,DST_ALPHA为0.0,源值为S = (.., 0.5),目标值为D = (.., 0.0)。所以混合函数的计算结果为
ONE_MINUS_DST_ALPHA * S + ONE * D
= 1.0 * (.., 0.5) + 1.0 * (.., 0.0)
= (.., 0.5)
这被写入帧缓冲区,并成为绘制第 2 层的目标值。第 2 层的源是(.., 0.4),DST_ALPHA 现在是 0.5。评估第 2 层的混合方程给出了
ONE_MINUS_DST_ALPHA * S + ONE * D
= 0.5 * (.., 0.4) + 1.0 * (.., 0.5)
= (.., 0.7)
我们得到了所需的 alpha 值 0.7!