我们知道,材料属性和光照参数可以极大地增加图形的逼真度,但除此之外,我们在对现实世界进行建模时,有许多效果是通过混合颜色的方式实现的。透明的物体,像是玻璃水杯,在它后面发射过来的光会与透明物体的颜色混合在一起。这种透明在OpenGL中的实现方式,是通过首先绘制背景物体,然后把前景物体(比如水杯)与颜色缓冲区中已经存在的颜色进行混合而实现的。在这一过程中,颜色的alpha值成分发挥了重要作用。
颜色的混合功能
在一般情况下,OpenGL在渲染时把颜色值存放在颜色缓冲区中,把每个片段(像素)的深度值存放在深度缓冲区中。当深度检测被关闭时,新的颜色值简单地覆盖颜色缓冲区中已经存在的颜色值;当深度检测被打开时,新的颜色值只有当它比原来的颜色更接近临近的裁剪平面时才会替换原来的颜色。当然,这是在OpenGL的混合功能被关闭的情况下。当混合功能被启用时,新的颜色会与颜色缓冲区中原有的颜色进行组合。通过对这些颜色进行不同的组合,可以产生许多种不同的效果。
在介绍颜色混合之前,我们需要首先明确目标颜色和源颜色这两个术语的概念。首先,已经存储在颜色缓冲区中的颜色称为目标颜色,这个颜色包含了单独的红、绿、蓝成分以及一个可选的alpha值。其次,作为当前渲染命令的结果进入颜色缓冲区中的颜色称为源颜色,它同样也包含了四种颜色成分(红、绿、蓝和可选的alpha值成分)。我们正是通过对目标颜色和源颜色进行不同的组合操作,来实现颜色混合的功能的。启用混合功能:
glEnable(GL_BLEND);
当混合功能被启用时,源颜色和目标颜色的组合方式是由混合方程式来控制的。在默认情况下,使用的混合方程式如下所示:
其中,Cf是最终计算产生的颜色,Cs是源颜色,Cd是目标颜色。S是源混合因子,D是目标混合因子。这两个混合因子可以通过下面的这个函数进行设置:
glBlendFunc(GLenum S, GLenum D);
可以看出,形参S和D都是枚举值,而不是可以直接指定的实际值。只谈概念有些模糊,让我们通过一个常见的混合函数示例来说明上述概念的实际应用:
// 设置源混合因子和目标混合因子
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.0f,0.0f, 0.0f, 0.0f)
Cs = 源颜色 = (0.0f, 0.0f, 1.0, 0.6f)
S = 源颜色alpha值 = 0.6
D = 1 - 源颜色alpha值 = 0.4
所以,根据混合方程式:
相当于:Cf = (蓝 * 0.6)+ (红 * 0.4)
最终产生的颜色是目标颜色红色与源颜色蓝色进行缩放后的组合,并且源颜色的alpha值越高,添加的源颜色成分就越多,目标颜色所保留的成分就越少。这个混合函数经常用于在一些不透明的物体前面绘制透明物体的效果。这种技巧在实现时需要首先绘制背景物体,然后再绘制经过混合的透明物体。它的效果会令你惊讶,如图所示:
让我们通过代码,来看一下Reflection示例程序的制作思路:
void RenderScene()
{
// 清空颜色缓冲区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// ----------------------- 首先绘制地板下方的球体 --------------------
// 保存矩阵状态
glPushMatrix();
// 将光源0的位置摆放到地板下方
glLightfv(GL_LIGHT0, GL_POSITION, fLightPosMirror);
// 保存矩阵状态
glPushMatrix();
// 设置顺时针环绕的一面为多边形的正面,对多边形进行镜像
glFrontFace(GL_CW);
// 在Y轴使用-1的缩放因子来反转Y轴
glScalef(1.0f, -1.0f, 1.0f);
// 绘制地板下方的球体
DrawSphere();
// 设置逆时针环绕的一面为多边形的正面
glFrontFace(GL_CCW);
// 恢复矩阵状态
glPopMatrix();
// ----------------- 通过对地板设置透明的颜色混合效果,来实现反射的幻觉 -----------------
// 关闭光照计算
glDisable(GL_LIGHTING);
// 打开颜色混合
glEnable(GL_BLEND);
// 设置混合因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 绘制地板
DrawGround();
// 关闭颜色混合
glDisable(GL_BLEND);
// 打开光照计算
glEnable(GL_LIGHTING);
// -------------------- 最后绘制地板上方的球体 ---------------------------
// 将光源0的位置摆放到地板上面
glLightfv(GL_LIGHT0, GL_POSITION, fLightPos);
// 绘制地板上方的球体
DrawSphere();
// 恢复矩阵状态
glPopMatrix();
// 执行缓冲区的交换
glutSwapBuffers();
}
我们在进行场景的绘制时,首先以上下颠倒的方式,最先绘制地板下方的球体;然后打开混合,在球体的上面绘制一层透明的地板;最后恢复颠倒后的坐标系,绘制地板上方的球体。这三部分完成后,一幅地板反射球体的幻觉画面就完成了,如图所示:
这一部分的示例程序完整代码已经放到了GitHub上,有需要的朋友可以参考一下(https://github.com/dxm3dp/OpenGL-06-Reflection点击打开链接)。