混合可以实现很多效果,笔记单纯的记录混合的原理,和混合公式,混合函数的使用。最后分析书中的两个混合代码例子,两个例子结合起来说明了绘图顺序对混合最终效果的影响,及如何去规避这样的问题。在颜色中之前一直忽略的第四个分量alpha终于派上用场。在启用混合情况下,alpha常常用于把被处理片断的颜色值与已经存在帧缓冲区的像素颜色值进行组合。混合操作是在场景进行了光栅化并转换为像素之后,但是在最终的像素绘制到帧缓冲区之前进行。
一、源(新片断)因子和目标(旧片断)因子
在混合过程中,新旧片断的组合分两步进行。首先,需要如何计算源和目标混合因子(混合因子)。这两个因子都是RGBA四元组,分别与源颜色和目标颜色的RGBA相乘。然后两个RGBA四元组对应成分再进行组合(混合公式)。最终的RGBA颜色是通过下面的公式得出:
(Rs*Sr +Rd*Dr, Gs*Sg + Gd*Dg, Bs*Sb + Bd*Db, As*Sa + Ad*Da )
源颜色(Rs, Bs, Gs, As)、目标颜色(Rd, Gd, Bd, Ad)、源混合因子(Sr, Sg, Sb, Sa)、目标混合因子(Dr, Dg, Db, Da)
常量
|
RGB混合因子
|
alpha混合因子
|
GL_ZERO
|
(0,0,0)
|
0
|
GL_ONE
|
(1,1,1)
|
1
|
GL_SRC_COLOR
|
(Rs,Gs,Bs)
|
As
|
GL_ONE_MINUS_SRC_COLOR
|
(1,1,1)-(Rs,Gs,Bs)
|
1-As
|
GL_DST_COLOR
|
(Rd,Gd,Bd)
|
Ad
|
GL_ONE_MINUS_DST_COLOR
|
(1,1,1)-(Rd,Gd,Bd)
|
1-Ad
|
GL_SRC_ALPHA
|
(As,As,As) |
As
|
GL_ONE_MINUS_SRC_ALPHA
|
(1,1,1)-(As,As,As)
|
1-As
|
GL_DST_ALPHA
|
(Ad,Ad,AD)
|
Ad
|
GL_ONE_MINUS_DST_ALPHA
|
(1,1,1)-(Ad,Ad,Ad)
|
1-Ad
|
GL_CONSTANT_COLOR
|
(Rc,Gc,Bc)
|
Ac
|
GL_ONE_MINUS_CONSTANT_COLOR
|
(1,1,1)-(Rc,Gc,Bc)
|
1-Ac
|
GL_CONSTANT_ALPHA
|
(Ac,Ac,Ac)
|
Ac
|
GL_ONE_MINUS_CONSTANT_ALPHA
|
(1,1,1)-(Ac,Ac,Ac)
|
Ac
|
GL_SRC_ALPHA_SATURATE
|
(f,f,f)=min(As,1-Ad)
|
1
|
|
|
|
|
|
|
要指定混合因子可能需要用到:1、glBlendFunc( GLenum srcfactor, GLenum destfactor ),指定源和目标混合因子,RGB和ALPHA使用同一因子。2、glBlendFuncSeparate(GLenum srcRGB, GLenum destRGB, GLenum srcAlpha, GLenum destAlpha),如参数所示,分开指定RGB混合因子,和Alpha混合因子,允许使用不一样的值。
二、设置混合方程式
在指定了混合方程式之后,还需要确定,源混合因子和目标混合因子如何结合(这就是混合方程式)。使用函数 void glBlendEquation( GLenum mode ) 或 void glBlendEquationSeparate( GLenum modeRGB, GLenum modeAlpha) 进行设定,与混合因子的设置类似,两个*Separate()函数允许Alpha采取与RGB不同的混合方程式。
混合模式参数
|
数学操作
|
|
GL_FUNC_ADD(默认)
|
Cs*S+Cd*D
|
GL_FUNC_SUBTRACT
|
Cs*S-Cd*D
|
GL_FUNC_REVERSE_SUBTRACT
|
Cd*D-Cs*S
|
GL_MIN
|
min(Cs*S,Cd*D)
|
GL_MAX
|
max(Cs*S,Cd*D)
|
GL_LOGIC_OP
|
Cs op Cd
|
最后当然还有的是要启用混合:glEnable(GL_BLEND)
三、例子分析
例子1:绘制了练个不同颜色的三角形,通过输入个改变练个三角形的绘图顺序来了解图元的绘制顺序对于混合的最终效果的影响。
#include "alpha.h" #include "CLApp.h" static int leftFirst = GL_TRUE; void alphaInit() { glEnable( GL_BLEND ); glDisable( GL_DEPTH ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glShadeModel( GL_FLAT ); glClearColor( 0.0, 0.0, 0.0, 0.0 ); } void alphaDrawLeftTriangle() { //黄色三角形 glBegin( GL_TRIANGLES ); glColor4f(1.0, 1.0, 0.0, 0.75); glVertex3f( 1, 9, 0.0 ); glVertex3f( 1, 1, 0.0 ); glVertex3f( 7, 5, 0.0 ); glEnd(); } void alphaDrawRightTriangle() { // glBegin( GL_TRIANGLES ); glColor4f( 0.0, 1.0, 1.0, 0.25 ); glVertex3f(9, 9,0.0); glVertex3f(3, 5, 0.0); glVertex3f(9, 1,0.0); glEnd(); } void alphaDisplay() { glClear( GL_COLOR_BUFFER_BIT ); if (leftFirst) { alphaDrawLeftTriangle(); alphaDrawRightTriangle(); } else { alphaDrawRightTriangle(); alphaDrawLeftTriangle(); } } void alphaUpdate(float dt) { CLInput* pInput = theAppInstance->getInputInstance(); if (pInput->IsKeyUp(DIK_T)) { leftFirst = !leftFirst; } }
首先绘制的是黄色的三角形,然后绘制通明度比较高的蓝色三角形,可以看出即使蓝色三角形通明度低,但在最终的颜色影响上还是比目标颜色黄色来的大。
在切换了绘制顺序之后,最终颜色基本是黄色了。这个例子只是说明了绘制顺序会极大影响最终的混合效果。
其实不难理解的是,在本例子中因为我们的目标和源混合因子在切换了绘制顺序时并没有随之调换。根据回合因子的设置,可以得到源混合因子总是0.75,目标混合因子总是0.25.所以后绘制的图元总是对最终的颜色产生较大的影响。
例子2:如何在解决3d场景了的混合问题:深度缓冲区。
当在3维场景中存在半透明和不透明物体时,问题就来了。如果只有不透明物体,假如一个物体被另外一个物体遮挡,我们不需要考虑的把被遮挡的物体剔除。但如果挡在前面的是个半透明物体时,事实上我们不应该去剔除这个物体,而更为复杂的是,这些物体有可能是运动的,这样我们就无法单一的指定绘制顺序去解决这样的问题。在此的解决方案是深度缓冲区:先去绘制不透明物体,这是深度缓冲区可读写;然后再绘制半透明物体时把深度缓冲区设为只读,这是已经在深度缓冲区的片元将不会被剔除,而是与新的片元进行混合。
#include "alpha3D.h" #define MAXZ 8.0 #define MINZ -8.0 #define ZINC 0.4 static float solidz = MAXZ; static float transparentz = MINZ; static GLuint sphereList, cubeList; void alpha3DInit() { GLfloat mat_specular[] = {1.0, 1.0, 1.0, 0.15 }; GLfloat mat_shininess[] = {100.0}; GLfloat position[] = {5, 5, 10.0, 0.0 }; glLoadIdentity(); gluLookAt(0.0, 0.0, -30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glMaterialfv( GL_FRONT, GL_SPECULAR, mat_specular ); glMaterialfv( GL_FRONT, GL_SHININESS, mat_shininess ); glMaterialfv( GL_LIGHT0, GL_POSITION, position ); glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 ); glEnable( GL_DEPTH_TEST ); sphereList = glGenLists(1); glNewList( sphereList, GL_COMPILE ); GLUquadricObj* qobj = gluNewQuadric(); //二次曲面对象 gluSphere(qobj, 3.0, 16, 16); gluDeleteQuadric(qobj); glEndList(); cubeList = glGenLists(1); glNewList( cubeList, GL_COMPILE ); GLUquadricObj* qobj1 = gluNewQuadric(); //二次曲面对象 //gluDisk(qobj1, 2.0, 5.0, 15.0, 10.0); gluQuadricDrawStyle(qobj1, GLU_FILL); gluCylinder(qobj1, 1, 4, 3, 15, 10); gluDeleteQuadric(qobj1); glEndList(); } void alpha3DDisplay() { GLfloat mat_solid[] = {0.75, 0.75, 0.0, 1.0 }; GLfloat mat_zero[] = {0.0, 0.0, 0.0, 1.0}; GLfloat mat_transparent[] = {0.0, 0.8, 0.8, 0.6 }; GLfloat mat_emission[] = {0.0, 0.3, 0.3, 0.6 }; glClear( GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT ); glPushMatrix(); glTranslatef( 5, 0, solidz ); glMaterialfv( GL_FRONT, GL_EMISSION, mat_zero ); glCallList( sphereList ); glPopMatrix(); glPushMatrix(); glTranslatef( 5, 0, transparentz ); glRotatef( 15.0, 1.0, 1.0, 0.0 ); glRotatef( 30.0, 0.0, 1.0, 0.0 ); glMaterialfv( GL_FRONT, GL_EMISSION, mat_emission ); glMaterialfv( GL_FRONT, GL_DIFFUSE, mat_transparent ); glEnable( GL_BLEND ); glDepthMask( GL_FALSE ); glBlendFunc( GL_SRC_ALPHA, GL_ONE ); glCallList( cubeList ); glDepthMask(GL_TRUE ); glDisable( GL_BLEND ); glPopMatrix(); } void alpha3DUpdate(float delta) { static bool ani = false; static int tTime = 0; if (solidz <= MINZ || transparentz >= MAXZ) { ani = false; } if ( ani ) { tTime += delta; if (tTime > 100) { solidz -= ZINC; transparentz += ZINC; tTime = 0; } } CLInput* pInput = theAppInstance->getInputInstance(); if (pInput->IsKeyUp(DIK_A)) { ani = true; } else if (pInput->IsKeyUp(DIK_R)) { solidz = MAXZ; transparentz = MINZ; } }