OpenGL中,混合(Blending)通常是实现物体透明度(Transparency)的一种技术。透明就是说一个物体(或者其中的一部分)不是纯色(Solid Color)的,它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度结合。一个有色玻璃窗是一个透明的物体,玻璃有它自己的颜色,但它最终的颜色还包含了玻璃之后所有物体的颜色。这也是混合这一名字的出处,我们混合(Blend)(不同物体的)多种颜色为一种颜色。所以透明度能让我们看穿物体。
丢弃片段
我们首先考虑一种最简单的情况,全透明纹理。在这种情况下,纹理中要么是全透明的,要么是完全不透明的图案。当着这种纹理被加载时我们可以简单地保留不透明的部分,并将透明的部分丢弃。幸运的是,在片段着色器中存在着discard
语法,它会保证片段不会被进一步处理,所以就不会进入颜色缓冲。其使用如下所示:
#version 330 core
out vec4 FragColor;in vec2 TexCoords;uniform sampler2D texture1;void main()
{vec4 texColor = texture(texture1, TexCoords);if(texColor.a < 0.1)discard;FragColor = texColor;
}
混合
虽然丢弃片段简单实用,但是它不能让我们渲染半透明的图像。对于纹理中的片段,要么渲染,要么完全丢弃它。要想渲染有多个透明度级别的图像,我们需要启用混合(Blending)。和OpenGL大多数的功能一样,我们可以启用GL_BLEND
来启用混合:
glEnable(GL_BLEND);
OpenGL中的混合是通过下面这个方程来实现的:
- \(\overline{C}_{source}\):源颜色向量。这是源自纹理的颜色向量。
- \(\overline{C}_{destination}\):目标颜色向量。这是当前储存在颜色缓冲中的颜色向量。
- \(F_{source}\):源因子值。指定了alpha值对源颜色的影响。
- \(F_{destination}\):目标因子值。指定了alpha值对目标颜色的影响。
片段着色器运行完成后,并且所有的测试都通过之后,这个混合方程(Blend Equation)才会应用到片段颜色输出与当前颜色缓冲中的值(当前片段之前储存的之前片段的颜色)上。源颜色和目标颜色将会由OpenGL自动设定,但源因子和目标因子的值可以由我们来决定。我们先来看一个简单的例子:
我们有两个方形,我们希望将这个半透明的绿色方形绘制在红色方形之上。红色的方形将会是目标颜色(所以它应该先在颜色缓冲中),我们将要在这个红色方形之上绘制这个绿色方形。
问题来了:我们将因子值设置为什么?
嘛,我们至少想让绿色方形乘以它的alpha值,所以我们想要将\(F_{source}\)设置为源颜色向量的alpha值,也就是0.6。接下来就应该清楚了,目标方形的贡献应该为剩下的alpha值。如果绿色方形对最终颜色贡献了60%,那么红色方块应该对最终颜色贡献了40%,即1.0 - 0.6。所以我们将\(F_{destination}\)设置为1减去源颜色向量的alpha值。这个方程变成了:
结果就是重叠方形的片段包含了一个60%绿色,40%红色的一种脏兮兮的颜色:
最终的颜色将会被储存到颜色缓冲中,替代之前的颜色。
这样子很不错,但我们该如何让OpenGL使用这样的因子呢?正好有一个专门的函数,叫做glBlendFunc
。
glBlendFunc(GLenum sfactor, GLenum dfactor)
函数接受两个参数,来设置源和目标因子。OpenGL为我们定义了很多个选项,我们将在下面列出大部分最常用的选项。注意常数颜色向量\(\overline{C}_{constant}\)可以通过glBlendColor
函数来另外设置。
为了获得之前两个方形的混合结果,我们需要使用源颜色向量的alpha作为源因子,使用1−alpha
作为目标因子。这将会产生以下的glBlendFunc
:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
也可以使用glBlendFuncSeparate
为RGB和alpha通道分别设置不同的选项:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
这个函数和我们之前设置的那样设置了RGB分量,但这样只能让最终的alpha分量被源颜色向量的alpha值所影响到。
OpenGL甚至给了我们更多的灵活性,允许我们改变方程中源和目标部分的运算符。当前源和目标是相加的,但如果愿意的话,我们也可以让它们相减。glBlendEquation(GLenum mode)
允许我们设置运算符,它提供了三个选项:
GL_FUNC_ADD
:默认选项,将两个分量相加:\(\overline{C}_{result} = Src + Dst.\)
。
GL_FUNC_SUBTRACT
:将两个分量相减: \(\overline{C}_{result} = Src - Dst.\)
。
GL_FUNC_REVERSE_SUBTRACT
:将两个分量相减,但顺序相反: \(\overline{C}_{result} = Dst - Src.\)
。
通常我们都可以省略调用glBlendEquation
,因为GL_FUNC_AD
D对大部分的操作来说都是我们希望的混合方程,但如果你真的想打破主流,其它的方程也可能符合你的要求。
不要打乱渲染顺序
渲染半透明纹理时会出现在不同深度下的半透明纹理可能丢失的情况,因此要保持一定的渲染顺序,一般来说都是由远及近的顺序。普通不需要混合的物体仍然可以使用深度缓冲正常绘制,所以它们不需要排序。但我们仍要保证它们在绘制(排序的)透明物体之前已经绘制完毕了。当绘制一个有不透明和透明物体的场景的时候,大体的原则如下:
- 先绘制所有不透明的物体。
- 对所有透明的物体排序。
- 按顺序绘制所有透明的物体。