OpenGL学习17——混合
2021-07-05 本文已影响0人
蓬篙人
混合(Blending)
- 混合(Blending) 在OpenGL中一般作为实现物体透明度的技术。一个物体的透明度由它颜色中的alpha值定义。
1. 丢弃片元
- 在一些渲染场景中我们可能并不关心部分透明度,而是根据纹理的颜色决定显示或完全不显示。如下面的青草纹理,我们只想显示绿色的青草,而背景部分则完全丢弃。(图片取自书中)
青草纹理图像 - 要加载含alpha值的纹理,我们需要告诉OpenGL我们的纹理包含alpha通道。
glTexImage2D(GL_TEXTURE_2D, 0, FL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
- 对于存在透明度的颜色,我们在片元着色器中需要使用颜色的四个分量进行计算。
void main()
{
FragColor = texture(texture1, TexCoords);
}
下面我们使用深度测试的例子,添加一些青草纹理来展示混合的效果。
- 首先,创建一个位置矢量数组来代表青春在场景中的位置。
std::vector<glm::vec3> vegation;
vegation.push_back(glm::vec3(-1.5f, 0.0f, -0.48f))
vegation.push_back(glm::vec3( 1.5f, 0.0f, 0.51f))
vegation.push_back(glm::vec3( 0.0f, 0.0f, 0.7f));
vegation.push_back(glm::vec3(-0.3f, 0.0f, -2.3f));
vegation.push_back(glm::vec3( 0.5f, 0.0f, -0.6f));
- 这里为了简单,我们直接将青草的纹理贴到四方形上。顶点数据可以参考立方体某个面的顶点数据。下面我们给出青草的渲染代码。
// grass
glBindVertexArray(grassVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture);
model = glm::mat4(1.0f);
for (int i = 0; i < vegation.size(); i++)
{
model = glm::mat4(1.0f);
model = glm::translate(model, vegation[i]);
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
-
渲染效果。
青春纹理渲染效果 - 在片元着色器中,我们根据alpha通道的值,使用内置指令
discard
来丢片片元。
void main()
{
//FragColor = texture(texture1, TexCoords);
vec4 texColor = texture(texture1, TexCoords);
if(texColor.a < 0.1)
discard;
FragColor = texColor;
}
-
渲染效果。
青草透明渲染效果
2. 混合
- 直接根据alpha值丢弃片元无法让我们渲染半透明图像,要渲染不同透明度的图像,我们首先需要启用混合。
glEnable(GL_BLEND);
- OpenGL中的混合采用下述公式:
- :源颜色矢量,片元着色器输出的颜色。
- :目标颜色矢量,当前存储在颜色缓冲区的颜色矢量值。
- :源因子值,源颜色矢量的alpha值的影响因子。
- :目标因子值,目标颜色矢量的alpha值的影响因子。
- 当片元着色器运行后,且所有测试通过了,混合公式将作用于片元着色器的颜色输出和当前颜色缓冲区中的值。
- 下面,我们用一个红色四方形和绿色四方形的混合来简单作简单展示。(图片取自书中)
混合的四方形 - 假设我们使用60%的绿色和40%红色进行混合,对应上面的公式,我们的计算应该是这样:
- 混合效果如下。(图片取自书中)
四方形混合 - 在OpenGL中,我们可以使用函数
glBlendFunc
设置源和目标颜色矢量的影响因子。
glBlendFunc(GLenum sfactor, GLenum dfactor);
- 常用的影响因子如下表所示:
选项 | 值 |
---|---|
GL_ZERO | 影响因子为0 |
GL_ONE | 影响因子为1 |
GL_SRC_COLOR | 影响因子等于源颜色矢量 |
GL_ONE_MINUS_SRC_COLOR | 影响因子等于1减去源颜色矢量 |
GL_DST_COLOR | 影响因子等于目标颜色矢量 |
GL_ONE_MINUS_DST_COLOR | 影响因子等于1减去目标颜色矢量 |
GL_SRC_ALPHA | 影响因子等于源颜色矢量的alpha分量值 |
GL_ONE_MINUS_SRC_ALPHA | 影响因子等于1减去源颜色矢量的alpha分量值 |
GL_DST_ALPHA | 影响因子等于目标颜色矢量的alpha分量值 |
GL_ONE_MINUS_DST_ALPHA | 影响因子等于1减去目标颜色矢量的alpha分量值 |
GL_CONSTANT_COLOR | 影响因子等于常量颜色矢量 |
GL_ONE_MINUS_CONSTANT_COLOR | 影响因子等于1减去常量颜色矢量 |
GL_CONSTANT_ALPHA | 影响因子等于常量颜色矢量的alpha分量 |
GL_ONE_MINUS_CONSTANT_ALPHA | 影响因子等于1减去常量颜色矢量的alpha分量 |
- 对于表格上的颜色矢量常量,我们可以使用
glBlendColor
函数设置。 - OpenGL允许我们使用
glBlendFuncSeparate
函数分别设置RGB和alpha通道的颜色值。
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
- OpenGL甚至还允许我们使用函数
glBlendEquation
函数修改公式中源和目标部分的操作符。-
GL_FUNC_ADD
:默认,两个颜色矢量进行加操作,。 -
GL_FUNC_SUBTRACT
:两个颜色矢量进行减操作,。 -
GL_FUNC_REVERSE_SUBTRACT
:两个颜色矢量转换顺序进行减操作,。 -
GL_MIN
:按颜色矢量的分量取最小值,。 -
GL_MAX
:按颜色矢量的分量取最大值,。
-
glBlendEquation(GLenum mode);
3. 渲染半透明纹理
- 下面我使用前面的例子,将青草纹理替换为一张窗体纹理来渲染混合的场景。首先,我们启用混合并设置混合公式参数。
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- 其次,因为我们使用混合计算颜色值,因此片元着色器中无需根据alpha值进行片元丢弃。
void main()
{
FragColor = texture(texture1, TexCoords);
}
-
渲染效果。
半透明渲染1 - 问题:上面的渲染场景,我们可以看到前面的窗体遮挡了部分后面的窗体。这是因为当写入深度缓冲区时,深度测试并不关心片元是否有透明度,因此透明部分的深度值也写入深度缓冲区。这导致后面的窗体也进行深度测试,并丢弃了片元。要解决这个问题,我们需要将窗体按最远到最近进行排序和绘制。
- 对于包含透明物体的场景,我们一般的绘制顺序如下:
- 绘制所有非透明物体对象。
- 对所有透明物体对象进行排序。
- 按排序顺序绘制透明物体对象。
- 针对上述场景,我们简单采用物体位置矢量与相机位置矢量之间的距离排序,并将距离和相应的位置矢量存储到STL类库中的
map
数据结构中。
std::map<float, glm::vec3> sorted;
for (unsigned int i = 0; i < vegation.size(); i++)
{
float distance = glm::length(camera.Position - vegation[i]);
sorted[distance] = vegation[i];
}
- 渲染时我们将
map
进行反转,按最远到最近的顺序进行绘制。
for (std::map<float, glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it)
{
model = glm::mat4(1.0f);
model = glm::translate(model, it->second);
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
-
渲染效果。
半透明渲染2