OpenGL 渲染技巧之颜色混合

1,751 阅读4分钟

​ 我们知道,材料属性和光照参数可以极大地增加图形的逼真度,但除此之外,我们在对现实世界进行建模时,有许多效果是通过混合颜色的方式实现的。透明的物体,像是玻璃水杯,在它后面发射过来的光会与透明物体的颜色混合在一起。这种透明在OpenGL中的实现方式,是通过首先绘制背景物体,然后把前景物体(比如水杯)与颜色缓冲区中已经存在的颜色进行混合而实现的。在这一过程中,颜色的alpha值成分发挥了重要作用。

颜色的混合功能

​ 在一般情况下,OpenGL在渲染时把颜色值存放在颜色缓冲区中,把每个片段(像素)的深度值存放在深度缓冲区中。当深度检测被关闭时,新的颜色值简单地覆盖颜色缓冲区中已经存在的颜色值;当深度检测被打开时,新的颜色值只有当它比原来的颜色更接近临近的裁剪平面时才会替换原来的颜色。当然,这是在OpenGL的混合功能被关闭的情况下。当混合功能被启用时,新的颜色会与颜色缓冲区中原有的颜色进行组合。通过对这些颜色进行不同的组合,可以产生许多种不同的效果。

在介绍颜色混合之前,我们需要首先明确目标颜色和源颜色这两个术语的概念。

  • 目标颜色:已经存储在颜色缓冲区中的颜色称为目标颜色,这个颜色包含了单独的红、绿、蓝成分以及一个可选的alpha值。

  • 源颜色:作为当前渲染命令的结果进入颜色缓冲区中的颜色称为源颜色,它同样也包含了四种颜色成分(红、绿、蓝和可选的alpha值成分)。

我们正是通过对目标颜色和源颜色进行不同的组合操作,来实现颜色混合的功能的。启用混合功能:

glEnable(GL_BLEND);

当混合功能被启用时,源颜色和目标颜色的组合方式是由混合方程式来控制的。在默认情况下,使用的混合方程式如下所示:

Cf = (Cs * S) + (Cd * D);
Cf:最终计算参数的颜色
Cs:源颜色
Cd:目标颜色
S:源混合因子
D:目标混合因子

S和D这两个混合因子可以通过下面的这个函数进行设置:

glBlendFunc(GLenum S, GLenum D);

可以看出,形参S和D都是枚举值,而不是可以直接指定的实际值。在OpenGL有以下几个枚举可以用来设置混合因子

image-20200712162409334

例如我们将混合因子设置为如下值时:

// 设置源混合因子和目标混合因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

根据上面的混合函数,如果颜色缓冲区中的目标颜色为红色(1.0f,0.0f, 0.0f, 0.0f);源颜色为一种蓝色(0.0f, 0.0f, 1.0, 0.6f),它的alpha值为0.6,那么计算得出的最终颜色为:

Cd = 目标颜色 = (1.0f0.0f0.0f0.0f)
Cs = 源颜色 = (0.0f, 0.0f, 1.0, 0.6f)
S = 源颜色alpha值 = 0.6
D = 1 - 源颜色alpha值 = 0.4
所以,根据混合方程式:
Cf = (Cs * S) + (Cd * D);
相当于:Cf = (蓝 * 0.6)+ (红 * 0.4);

最终产生的颜色是目标颜色红色与源颜色蓝色进行缩放后的组合,并且源颜色的alpha值越高,添加的源颜色成分就越多,目标颜色所保留的成分就越少。这个混合函数经常用于在一些不透明的物体前面绘制透明物体的效果。这种技巧在实现时需要首先绘制背景物体,然后再绘制经过混合的透明物体。

示例程序

image-20200712162857297

我们通过代码来看看上图程序的设计思路

void RenderScene(void)
{
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
   
    //1.开启混合
    glEnable(GL_BLEND);
    //2.开启组合函数 计算混合颜色因子
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    //定义4种颜色
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    GLfloat vRed1[] = { 1.0f, 0.0f, 0.0f, 0.5f };
    GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 0.5f };
    GLfloat vBlue[] = { 0.0f, 0.0f, 1.0f, 0.5f };
    GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 0.5f };
    
    //3.使用着色器管理器
    //*使用 单位着色器
    //参数1:简单的使用默认笛卡尔坐标系(-1,1),所有片段都应用一种颜色。GLT_SHADER_IDENTITY
    //参数2:着色器颜色
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    //4.容器类开始绘制
    squareBatch.Draw();
    
    //5.召唤场景的时候,将4个固定矩形绘制好
    //使用 单位着色器
    //参数1:简单的使用默认笛卡尔坐标系(-1,1),所有片段都应用一种颜色。GLT_SHADER_IDENTITY
    //参数2:着色器颜色
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vGreen);
    greenBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed1);
    redBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlue);
    blueBatch.Draw();
    

    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlack);
    blackBatch.Draw();
    
    //5.关闭混合功能
    glDisable(GL_BLEND);
    //同步绘制命令
    glutSwapBuffers();
}

通过键盘来控制中间图形的移动

void SpecialKeys(int key, int x, int y)
{
    GLfloat stepSize = 0.025f;
    
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[7];
    
    if(key == GLUT_KEY_UP)
        blockY += stepSize;
    
    if(key == GLUT_KEY_DOWN)
        blockY -= stepSize;
    
    if(key == GLUT_KEY_LEFT)
        blockX -= stepSize;
    
    if(key == GLUT_KEY_RIGHT)
        blockX += stepSize;
    
    
    if(blockX < -1.0f) blockX = -1.0f;
    if(blockX > (1.0f - blockSize * 2)) blockX = 1.0f - blockSize * 2;;
    if(blockY < -1.0f + blockSize * 2)  blockY = -1.0f + blockSize * 2;
    if(blockY > 1.0f) blockY = 1.0f;
    
    
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize*2;
    
    vVerts[3] = blockX + blockSize*2;
    vVerts[4] = blockY - blockSize*2;
    
    vVerts[6] = blockX + blockSize*2;
    vVerts[7] = blockY;
    
    vVerts[9] = blockX;
    vVerts[10] = blockY;
    
    squareBatch.CopyVertexData3f(vVerts);
    
    glutPostRedisplay();
}

通过glutPostRedisplay函数来主动触发RenderScene函数的重绘。