美文网首页
OpenGL学习17——混合

OpenGL学习17——混合

作者: 蓬篙人 | 来源:发表于2021-07-05 13:07 被阅读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中的混合采用下述公式:
      \overline{C}_{result}=\overline{\color{green}{C}}_{source} * \color{green}{F}_{source}+\overline{\color{red}{C}}_{destination} * \color{red}{F}_{destination}
      • \overline{\color{green}{C}}_{source}:源颜色矢量,片元着色器输出的颜色。
      • \overline{\color{red}{C}}_{destination}:目标颜色矢量,当前存储在颜色缓冲区的颜色矢量值。
      • \color{green}{F}_{source}:源因子值,源颜色矢量的alpha值的影响因子。
      • \color{red}{F}_{destination}:目标因子值,目标颜色矢量的alpha值的影响因子。
    • 当片元着色器运行后,且所有测试通过了,混合公式将作用于片元着色器的颜色输出和当前颜色缓冲区中的值。
    • 下面,我们用一个红色四方形和绿色四方形的混合来简单作简单展示。(图片取自书中
      混合的四方形
    • 假设我们使用60%的绿色和40%红色进行混合,对应上面的公式,我们的计算应该是这样:
      \overline{C}_{result}= \left( \begin{matrix} \color{red}{0.0} \\ \color{green}{1.0} \\ \color{blue}{0.0} \\ \color{purple}{0.6} \end{matrix} \right) * \color{green}{0.6} + \left( \begin{matrix} \color{red}{1.0} \\ \color{green}{0.0} \\ \color{blue}{0.0} \\ \color{purple}{1.0} \end{matrix} \right) * (\color{red}{1} - \color{red}{0.6})
    • 混合效果如下。(图片取自书中
      四方形混合
    • 在OpenGL中,我们可以使用函数glBlendFunc设置源和目标颜色矢量的影响因子。
    glBlendFunc(GLenum sfactor, GLenum dfactor);
    
    • 常用的影响因子如下表所示:
    选项
    GL_ZERO 影响因子为0
    GL_ONE 影响因子为1
    GL_SRC_COLOR 影响因子等于源颜色矢量\overline{\color{green}{C}}_{source}
    GL_ONE_MINUS_SRC_COLOR 影响因子等于1减去源颜色矢量1-\overline{\color{green}{C}}_{source}
    GL_DST_COLOR 影响因子等于目标颜色矢量\overline{\color{red}{C}}_{destination}
    GL_ONE_MINUS_DST_COLOR 影响因子等于1减去目标颜色矢量1-\overline{\color{red}{C}}_{destination}
    GL_SRC_ALPHA 影响因子等于源颜色矢量\overline{\color{green}{C}}_{source}的alpha分量值
    GL_ONE_MINUS_SRC_ALPHA 影响因子等于1减去源颜色矢量\overline{\color{green}{C}}_{source}的alpha分量值
    GL_DST_ALPHA 影响因子等于目标颜色矢量\overline{\color{red}{C}}_{destination}的alpha分量值
    GL_ONE_MINUS_DST_ALPHA 影响因子等于1减去目标颜色矢量\overline{\color{red}{C}}_{destination}的alpha分量值
    GL_CONSTANT_COLOR 影响因子等于常量颜色矢量\overline{\color{blue}{C}}_{constant}
    GL_ONE_MINUS_CONSTANT_COLOR 影响因子等于1减去常量颜色矢量\overline{\color{blue}{C}}_{constant}
    GL_CONSTANT_ALPHA 影响因子等于常量颜色矢量\overline{\color{blue}{C}}_{constant}的alpha分量
    GL_ONE_MINUS_CONSTANT_ALPHA 影响因子等于1减去常量颜色矢量\overline{\color{blue}{C}}_{constant}的alpha分量
    • 对于表格上的颜色矢量常量,我们可以使用glBlendColor函数设置。
    • OpenGL允许我们使用glBlendFuncSeparate函数分别设置RGB和alpha通道的颜色值。
    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
    
    • OpenGL甚至还允许我们使用函数glBlendEquation函数修改公式中源和目标部分的操作符。
      • GL_FUNC_ADD:默认,两个颜色矢量进行加操作,\overline{C}_{result}=\color{green}{Src}+\color{red}{Dst}
      • GL_FUNC_SUBTRACT:两个颜色矢量进行减操作,\overline{C}_{result}=\color{green}{Src}-\color{red}{Dst}
      • GL_FUNC_REVERSE_SUBTRACT:两个颜色矢量转换顺序进行减操作,\overline{C}_{result}=\color{red}{Dst}-\color{green}{Src}
      • GL_MIN:按颜色矢量的分量取最小值,\overline{C}_{result}=min(\color{red}{Dst},\color{green}{Src})
      • GL_MAX:按颜色矢量的分量取最大值,\overline{C}_{result}=max(\color{red}{Dst},\color{green}{Src})
    glBlendEquation(GLenum mode);
    

    3. 渲染半透明纹理

    • 下面我使用前面的例子,将青草纹理替换为一张窗体纹理来渲染混合的场景。首先,我们启用混合并设置混合公式参数。
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    • 其次,因为我们使用混合计算颜色值,因此片元着色器中无需根据alpha值进行片元丢弃。
    void main()
    {   
        FragColor = texture(texture1, TexCoords);
    }
    
    • 渲染效果。


      半透明渲染1
    • 问题:上面的渲染场景,我们可以看到前面的窗体遮挡了部分后面的窗体。这是因为当写入深度缓冲区时,深度测试并不关心片元是否有透明度,因此透明部分的深度值也写入深度缓冲区。这导致后面的窗体也进行深度测试,并丢弃了片元。要解决这个问题,我们需要将窗体按最远到最近进行排序和绘制。
    • 对于包含透明物体的场景,我们一般的绘制顺序如下:
        1. 绘制所有非透明物体对象。
        1. 对所有透明物体对象进行排序。
        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

    相关文章

      网友评论

          本文标题:OpenGL学习17——混合

          本文链接:https://www.haomeiwen.com/subject/yanbultx.html