OpenGL进阶之混合Blending

Posted 木大白易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenGL进阶之混合Blending相关的知识,希望对你有一定的参考价值。

本系列文章为Learn OpenGL个人学习总结!
OpenGL入门(一)之认识OpenGL和创建Window
OpenGL入门(二)之渲染管线pipeline,VAO、VBO和EBO
OpenGL入门(三)之着色器Shader
OpenGL入门(四)之纹理Texture
OpenGL入门(五)之Matrix矩阵操作和坐标系统
OpenGL进阶(一)之帧缓冲FrameBuffer
OpenGL进阶(二)之像素缓冲PixelBuffer

简介

OpenGL中,混合(Blending)通常是实现物体透明度(Transparency)的一种技术。透明就是说一个物体(或者其中的一部分)不是纯色(Solid Color)的,它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度结合。
透明的物体可以是完全透明的(让所有的颜色穿过),或者是半透明的(它让颜色通过,同时也会显示自身的颜色)。一个物体的透明度是通过它颜色的alpha值来决定的。Alpha颜色值是颜色向量的第四个分量。

  • alpha1.0–>物体不透明
  • alpha0.0–>物体完全透明

丢弃片段

有些图片并不需要半透明,只需要根据纹理颜色值,显示一部分,或者不显示一部分,没有中间情况。

这里我们仍然使用前边FBO一节中的例子,绘制几颗草!

加载一个带透明通道的纹理:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

另外,我们要告诉OpenGL怎么处理alpha值,可以使用discard指令在片段着色器中检测一个片段的alpha值是否低于某个阈值时,将其丢弃:

const char *fragmentShaderSource = "#version 330 core\\n"
    "in vec2 texCoord;\\n"
    "out vec4 color;\\n"
    "uniform sampler2D texture1;\\n" 
    "void main()\\n"
    "\\n"
    "   vec4 texColor = texture(texture1, texCoord);\\n"
    "    if(texColor.a < 0.1)\\n"    //alpha值低于0.1则丢弃片段
    "        discard;\\n"
    "   color = texColor;\\n" 
    "\\0";

另外,需要注意一个问题:

注意,当采样纹理的边缘的时候,OpenGL会对边缘的值和纹理下一个重复的值进行插值(因为我们将它的环绕方式设置为了GL_REPEAT。这通常是没问题的,但是由于我们使用了透明值,纹理图像的顶部将会与底部边缘的纯色值进行插值。这样的结果是一个半透明的有色边框,你可能会看见它环绕着你的纹理四边形。要想避免这个,每当你alpha纹理的时候,请将纹理的环绕方式设置为GL_CLAMP_TO_EDGE:

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

混合

要想渲染有多个透明度级别(半透明的窗户)的图像,我们需要启用混合(Blending):

//开启blending
glEnable(GL_BLEND);

启用了混合之后,我们需要告诉OpenGL它该如何混合。OpenGL中的混合是通过下面这个方程来实现的:

  • C s o u r c e C_source Csource:源颜色向量。这是源自纹理的颜色向量。
  • C d e s t i n a t i o n C_destination Cdestination:目标颜色向量。这是当前储存在颜色缓冲中的颜色向量。
  • F s o u r c e F_source Fsource:源因子值。指定了alpha值对源颜色的影响。
  • F d e s t i n a t i o n F_destination Fdestination:目标因子值。指定了alpha值对目标颜色的影响。

我们可以通过glBlendFunc()来设置因子值,来觉得原图片和目标图片各自对于混合后的贡献!

//设置src和dst的影响因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

这样我们就能绘制出半透明的窗户纹理了!

深度测试带来的透明纹理遮挡问题

深度测试和混合一起使用的话会产生一些麻烦。当写入深度缓冲时,深度缓冲不会检查片段是否是透明的,所以透明的部分会和其它值一样写入到深度缓冲中。结果就是窗户的整个四边形不论透明度都会进行深度测试。即使透明的部分应该显示背后的窗户,深度测试仍然丢弃了它们。

注意,对于草这种全透明的物体,我们可以选择丢弃透明的片段而不是混合它们,这样就解决了这些头疼的问题(没有深度问题)。

要想让混合在多个物体上工作,我们需要最先绘制最远的物体,最后绘制最近的物体。普通不需要混合的物体仍然可以使用深度缓冲正常绘制,所以它们不需要排序。但我们仍要保证它们在绘制(排序的)透明物体之前已经绘制完毕了。当绘制一个有不透明和透明物体的场景的时候,大体的原则如下:

  1. 先绘制所有不透明的物体。
  2. 对所有透明的物体排序。
  3. 按顺序绘制所有透明的物体。

排序透明物体的一种方法是,从观察者视角获取物体的距离。
这里可以参考一下前边相机和坐标系统一节:OpenGL入门(五)之Matrix矩阵操作和坐标系统

    std::map<float, glm::vec3> sorted;
    for (unsigned int i = 0; i < windows.size(); i++)
    
        float distance = glm::length(glm::vec3(0.0f, 0.0f, 3.0f) - windows[i]);
        sorted[distance] = windows[i];
    

这里的glm::vec3(0.0f, 0.0f, 3.0f)就是世界坐标中camera的位置!

然后绘制的时候,逆序遍历这个map即可!

以上是关于OpenGL进阶之混合Blending的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL进阶之混合Blending

◮OpenGL-混合

◮OpenGL-混合

OpenGL透明与混色效果

Unity3D Shader官方教程翻译----Shader语法:Pass的Blending(混合)

Directx11学习笔记十八 Blending混合