颜色的混合在现实世界中非常常见,例如隔着有色玻璃观看物体,此时在观察者严重呈现出来物体的颜色就是玻璃的颜色和物体的颜色的混合。
OpenGL在RGBA颜色模式下使用函数glenable(GL_BLEND)开启混色功能,使用glDisable(GL_BLEDN)关闭混色功能。混色功能开启之后,最终显示的颜色的RGBA分量是两个颜色各自的RGBA分量共同决定的。
源因子和目标因子
需要混合的两个颜色一个称为源颜色,一个称为目标颜色,而定义一个颜色是源颜色还是目标颜色的唯一依据是:先绘制的颜色是“目标颜色”,后绘制的是“源颜色”,跟物体的深度无关。
混色运算的实现是源颜色和目标颜色分别乘上一个“源因子”和“目标因子”后经过算术运算实现的。使用函数glBlenFunc来设置源因子和目标因子。函数原型是:
void glBlendFunc (GLenum sfactor, GLenum dfactor);
第一个参数用于设置源因子,第二个参数用于设置目标因子,这两个参数可以是多种值,下边是一些比较常用的设置:
- GL_ZERO:表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。
- GL_ONE: 表示使用1.0作为因子,实际上相当于完全的使用了这种颜色参与混合运算。
- GL_SRC_ALPHA:表示使用源颜色的alpha值来作为因子。
- GL_DST_ALPHA:表示使用目标颜色的alpha值来作为因子。
- GL_ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
- GL_ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。
如果不对源因子和目标因子进行设置,默认情况是采用设置glBlendFunc(GL_ONE,GL_ZERO),即完全使用源颜色,覆盖目标颜色。
最常用的设置是glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);这种情况表示源颜色乘以自身的Alpha值,目标颜色乘以1.0减去源颜色的Alpha值,这样一来,源颜色的Alpha值越大,则产生的新颜色中源颜色所占比例就越大,而目标颜色所占比例则减少。这种情况相当于使用源颜色Alpha的数值大小简单模拟玻璃(源颜色)的不透明度。
深度缓存
深度缓存记录了每一个像素距离观察点的距离,启用深度缓存功能之后,程序会忠实的执行只绘制最上层像素点,覆盖比最上层像素点深度更深的像素点,而颜色透明度则被忽略。
要解决这一问题,需要在绘制半透明模型之前“冻结”深度缓存区,将深度缓存区设置为只读的,这样一来,虽然半透明物体被绘制上去了,深度缓存区还保持在原来的状态,即当前绘制的模型的深度不会影响到深度缓冲区的状态。等绘制完所有需要混色的模型之后,再把深度缓存区设置为可读可写状态。
调用glDepthMask(GL_FALSE),可以把深度缓存区设置为只读形式,使用glDepthMask(GL_TRUE)将深度缓存区设置为可读可写形式。
示例
以下程序的功能是在三维空间中绘制3个平面,颜色分别是RGB,R在最下边,G在中间,B在最上层,实现3个颜色的混色,可以看到,RGB三者重合的区域混合出了白色:
程序代码:
#include <glut.h>
GLfloat angle = 0.0f;
void myDisplay(void)
{
//List 1 定义模型视图矩阵
if(!glIsList((GLuint)1))
{
glNewList(1,GL_COMPILE);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60,1,2,50);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0,-10,10,0,0,0,0,1,0);
glEndList();
}
glEnable(GL_DEPTH_TEST); //深度缓存
glEnable(GL_BLEND); //开启混色
//设置源因子和目标因子
glBlendFunc(GL_ONE,GL_ONE);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glCallList(1);
glDepthMask(GL_FALSE); //设置深度缓冲区为只读模式
glRotatef(angle,1,0,0);
glRotatef(angle,0,1,0);
glRotatef(angle,1,0,1);
glBegin(GL_QUADS);
//最底层的面 先绘制的是目标,红色框
glColor4f(1,0,0,0.5);
glVertex3f(-2,-5,-5);
glVertex3f(8,-5,-5);
glVertex3f(8,5,-5);
glVertex3f(-2,5,-5);
//中间的面,第二绘制的源1 绿色框
glColor4f(0,1,0,0.5);
glVertex3f(-4.5,-5,-2);
glVertex3f(5.5,-5,-2);
glVertex3f(5.5,5,-2);
glVertex3f(-4.5,5,-2);
//顶层的面,第三绘制的源2 蓝色框
glColor4f(0,0,1,0.5);
glVertex3f(-7,-5,1);
glVertex3f(3,-5,1);
glVertex3f(3,5,1);
glVertex3f(-7,5,1);
glEnd();
glDepthMask(GL_TRUE); //恢复深度缓冲区读写
glutSwapBuffers();
}
void myIdle(void)
{
angle+=0.2f;
if( angle >= 360.0f )
angle = 0.0f;
myDisplay();
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(200, 200);
glutInitWindowSize(400, 400);
glutCreateWindow("OpenGL");
glutDisplayFunc(&myDisplay);
glutIdleFunc(&myIdle);
glutMainLoop();
return 0;
}