混合Blending

OpenGL中,混合(Blending)通常是实现物体透明度(Transparency)的一种技术。透明就是说一个物体(或者其中的一部分)不是纯色(Solid Color)的,它的颜色是物体本身的颜色和它背后其它物体的颜色的不同强度结合。一个有色玻璃窗是一个透明的物体,玻璃有它自己的颜色,但它最终的颜色还包含了玻璃之后所有物体的颜色。这也是混合这一名字的出处,我们混合(Blend)(不同物体的)多种颜色为一种颜色。所以透明度能让我们看穿物体。 我们目前一直使用的纹理有三个颜色分量:红、绿、蓝。但一些材质会有一个内嵌的alpha通道,对每个纹素(Texel)都包含了一个alpha值

  1. 丢弃片元 cpp vec4 texColor = texture(texture1, TexCoords); //对于透明处的丢弃 if(texColor.a < 0.1) discard; FragColor = texColor;

  2. 混合 > 虽然直接丢弃片段很好,但它不能让我们渲染半透明的图像。我们要么渲染一个片段,要么完全丢弃它。要想渲染有多个透明度级别的图像,我们需要启用混合(Blending)。和OpenGL大多数的功能一样,我们可以启用GL_BLEND来启用混合: glEnable(GL_BLEND); 启用了混合之后,我们需要告诉OpenGL它该如何混合。

    OpenGL中的混合是通过下面这个方程来实现的: \[ \overline{C_{result}} = \overline{C_{source}}*\overline{F_{source}} + \overline{C_{destination}}*\overline{F_{destination}} C_{source}:源颜色向量。这是源自纹理的颜色向量。 C_{destination}:目标颜色向量。这是当前储存在颜色缓冲中的颜色向量。 F_{source}:源因子值。指定了alpha值对源颜色的影响。 F_{destination}:目标因子值。指定了alpha值对目标颜色的影响。 \]

    TODO

    行为 描述
    GL_KEEP 保持当前储存的模板值
    GL_ZERO 将模板值设置为0
    GL_REPLACE 将模板值设置为glStencilFunc函数设置的ref值
    GL_INCR 如果模板值小于最大值则将模板值加1
    GL_INCR_WRAP 与GL_INCR一样,但如果模板值超过了最大值则归零
    GL_DECR 如果模板值大于最小值则将模板值减1
    GL_DECR_WRAP 与GL_DECR一样,但如果模板值小于0则将其设置为最大值
    GL_INVERT 按位翻转当前的模板缓冲值
  3. 渲染半透明纹理 > glBlendFunc(GLenum sfactor, GLenum dfactor)函数接受两个参数,来设置源和目标因子

    选项
    GL_ZERO 因子等于0
    GL_ONE 因子等于1
    GL_SRC_COLOR 因子等于源颜色向量C¯source
    GL_ONE_MINUS_SRC_COLOR 因子等于1−C¯source
    GL_DST_COLOR 因子等于目标颜色向量C¯destination
    GL_ONE_MINUS_DST_COLOR 因子等于1−C¯destination
    GL_SRC_ALPHA 因子等于C¯source的alpha 分量
    GL_ONE_MINUS_SRC_ALPHA 因子等于1−C¯source的alpha分量
    GL_DST_ALPHA 因子等于C¯destination的alpha分量
    GL_ONE_MINUS_DST_ALPHA 因子等于1−C¯destination的alpha分量
    GL_CONSTANT_COLOR 因子等于常数颜色向量C¯constant
    GL_ONE_MINUS_CONSTANT_COLOR 因子等于1−C¯constant
    GL_CONSTANT_ALPHA 因子等于C¯constant的alpha分量
    GL_ONE_MINUS_CONSTANT_ALPHA 因子等于1−C¯constant的alpha分量

    也可以使用glBlendFuncSeparate为RGB和alpha通道分别设置不同的选项: glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); 我们也可以让它们相减。glBlendEquation(GLenum mode)允许我们设置运算符,它提供了三个选项: GL_FUNC_ADD:默认选项,将两个分量相加:C¯result=Src+Dst。 GL_FUNC_SUBTRACT:将两个分量相减: C¯result=Src−Dst。 GL_FUNC_REVERSE_SUBTRACT:将两个分量相减,但顺序相反:C¯result=Dst−Src。

    1. 同其他参数一样,
        glEnable(GL_BLEND); //开启混合选项
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //设置混合函数
  4. 排序 如果你仔细看的话,你可能会注意到有些不对劲。最前面窗户的透明部分遮蔽了背后的窗户?这为什么会发生呢? 发生这一现象的原因是,深度测试和混合一起使用的话会产生一些麻烦。当写入深度缓冲时,深度缓冲不会检查片段是否是透明的,所以透明的部分会和其它值一样写入到深度缓冲中。结果就是窗户的整个四边形不论透明度都会进行深度测试。即使透明的部分应该显示背后的窗户,深度测试仍然丢弃了它们。 所以我们不能随意地决定如何渲染窗户,让深度缓冲解决所有的问题了。这也是混合变得有些麻烦的部分。要想保证窗户中能够显示它们背后的窗户,我们需要首先绘制背后的这部分窗户。这也就是说在绘制的时候,我们必须先手动将窗户按照最远到最近来排序,再按照顺序渲染。

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

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

    这里使用距离相机距离进行排序 cpp std::map<float, glm::vec3> sorted; for (unsigned int i = 0; i < windows.size(); i++) { float distance = glm::length(camera.Position - windows[i]); sorted[distance] = windows[i]; }


文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录