【安卓OpenGLES】 开发入门(七):渲染半透明纹理之混合

4,222 阅读4分钟

本文正在参加「金石计划」

OpenGL混合基础

OpenGL中的混合简单理解就是两个颜色的中和色,比如常见的透明色,我们有一块玻璃,玻璃后面有个物体,那么要显示一种透明的效果,就需要使用到混合的概念。

透明物体可以是完全透明(它使颜色完全穿透)或者半透明的(它使颜色穿透的同时也显示自身颜色)。一个物体的透明度,被定义为它的颜色的alpha值,alpha颜色值使用颜色向量的第四个元素

img

混合方程

OpenGL中有两种混合方式:

  • 第一种:可以简单的给定一个透明度alpha的阈值,对于低于阈值alpha的片段进行丢弃,高于阈值的片段进行保留,可以得到一个混合后的效果。但是这种混合方式会使得部分区域完全保留,部分区域完全丢弃,不适合透明下的场景。
  • 第二种:采用一个混合公式对片段颜色进行混合。

OpenGL以下面的方程进行混合:

image-20230411104501095

这里简单说明下源向量和目标向量的概念:

源向量:就是将要使用的覆盖目标向量的颜色。

目标向量:将要被源向量覆盖的颜色向量。

通过定义也可以看出,我们在绘制的时候需要先绘制目标向量,再绘制源向量

如果顺序搞反了,先绘制了源向量,再绘制了目标向量,那么在绘制源向量的过程中,由于目标向量还不存在,就会出现目标向量被完全覆盖的情况(假设开启了深度测试)。

混合API详解

  • 开启混合

    glEnable(GL_BLEND);
    

    GL_BLEND用来启用混合(Blending) 功能。

  • 混合函数因子设置:

    void glBlendFunc(GLenum sfactor, GLenum dfactor)
    

    glBlendFunc接收两个参数:用来设置源和目标因子

    OpenGL为我们定义了很多选项,我们把最常用的列在下面:

    选项
    GL_ZERO00
    GL_ONE11
    GL_SRC_COLOR源颜色向量C_source
    GL_ONE_MINUS_SRC_COLOR1−C_source
    GL_DST_COLOR目标颜色向量C_destination
    GL_ONE_MINUS_DST_COLOR1−C_destination
    GL_SRC_ALPHAC_source alpha值
    GL_ONE_MINUS_SRC_ALPHA1−C_source alpha值
    GL_DST_ALPHAC_destination的alpha值
    GL_ONE_MINUS_DST_ALPHA1−C_destination的alpha值
    GL_CONSTANT_COLOR常颜色向量C_constant
    GL_ONE_MINUS_CONSTANT_COLOR1−C_constant
    GL_CONSTANT_ALPHAC_constant的alpha值
    GL_ONE_MINUS_CONSTANT_ALPHA1−C_constant的alpha值

    注意,颜色常数向量可以用glBlendColor函数分开来设置。

    为了将两个颜色按alpha透明度进行混合,我们需要把源颜色的alpha给源因子,1−alpha给目标因子,如下设置:

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    

    当然你也可以使用glBlendFuncSeparate函数来分别对RGBalpha因子设置:

    glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
    
  • 设置混合模式是相加还是相减

    void glBlendEquation(GLenum mode)
    

    三种模式可选:

    • GL_FUNC_ADD:默认的,彼此元素相加:result = Src+Dst;
    • GL_FUNC_SUBTRACT:彼此元素相减:result = Src−Dst;
    • GL_FUNC_REVERSE_SUBTRACT:彼此元素相减,但顺序相反:result = Dst−Src;

一般我们使用默认的GL_FUNC_ADD即可,如果需要实现一些其他效果,可以选择其他模式、

了解了以上api之后,下面我们来写个demo

混合操作实践

下面我们在之前代码基础上,给模型加上一个透明窗户的效果:渲染半透明纹理

image-20230411141112600

代码如下

  • 顶点着色器代码:
"#version 300 es \n"
"layout(location = 0) in vec3 vPosition;\n"
"layout(location = 1) in vec2 texCords;\n"
"out vec2 TexCords;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()"
"{ \n"
"   gl_Position = projection*view*model*vec4(vPosition,1.0f); \n"
"  TexCords = texCords; \n"
"} \n";
  • 片段着色器代码:
"#version 300 es \n"
"precision mediump float;\n"
"uniform sampler2D cubeTexture;"
"in vec2 TexCords;"
"out vec4 outColor;\n"
"void main()"
"{ \n"
"   vec3 _texture = vec3(texture(cubeTexture,TexCords));\n"
"   outColor = vec4(_texture, 0.6f); \n"
"} \n";
  • 绘制代码:
void MyGLRenderContext::OnDrawFrame() {
    isDrawing = true;
    beforeDraw();
    if(programObj == 0){
        return;
    }
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //清除buffer
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);
    glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
​
    //part1:绘制立方体
    {
        //总是写入模板缓冲区,并让模板缓冲区可写
        glStencilFunc(GL_ALWAYS,1,0xff);
        glStencilMask(0xff);
        //使用立方体程序着色器对象
        useProgram(programObj);
        //绑定VAO
        glBindVertexArray(VAO);
        //观察者位置
        setCordder(model);
        //绑定纹理
        setUniformTexture("textureColor",textureID);
        //开始绘制
        glDrawArrays(GL_TRIANGLES,0,36);
        //解绑VAO
        glBindVertexArray(GL_NONE);
    }
    //part2:绘制物体轮廓
    {
        //绘制模板缓冲区中不等于1的部分,并让模板缓冲区不可写,且屏蔽深度测试
        glStencilFunc(GL_NOTEQUAL,1,0xff);
        glStencilMask(0x00);
        glDisable(GL_DEPTH_TEST);
​
        //使用物体轮廓绘制的着色器对象
        useProgram(singleColorShader);
        glBindVertexArray(VAO);
        GLfloat scale = 1.1f;
        //第一次绘制时缩放到1.1倍
        if(isFirstDraw){
            modelScale = glm::scale(modelScale, glm::vec3(scale, scale, scale));
            isFirstDraw = false;
        }
        //设置坐标系的变换矩阵
        setCordder(modelScale);
        //开始绘制
        glDrawArrays(GL_TRIANGLES,0,36);
        //重置模板缓冲区参数并开启深度测试,释放资源
        glStencilMask(0xFF);
        glStencilFunc(GL_ALWAYS, 0, 0xFF);
        glEnable(GL_DEPTH_TEST);
        glBindVertexArray(GL_NONE);
        //解绑程序着色器对象
        glUseProgram(GL_NONE);
    }
    //part3:绘制窗户
    {
        //使用一个新的着色器对象
        useProgram(cubeProObj);
        //绑定窗户顶点数据
        glBindVertexArray(cubeVAO);
        //设置坐标系
        setCordder(modelCube);
        //设置纹理坐标:窗户
        setUniformTexture("cubeTexture",cubeTextureID);
        //绘制正方形
        glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
        isDrawing = false;
        glBindVertexArray(GL_NONE);
        //解绑程序着色器对象
        useProgram(GL_NONE);
    }
}
  • 最后效果:

ezgif-3-a9e39af762

最后:完整代码已经贴到github上了,大家可以自行下载查看。我是小余,我们下期见》》》