混合
在正常情况下,OpenGL 渲染时会把颜色值输入到颜色缓冲区中,深度值输入到深度缓冲区中。如果我们关闭深度测试,那么新的颜色值会简单地覆盖已经存在于颜色缓冲区中的值。当开启深度测试时,颜色段只有在通过深度测试时,才会覆盖已经存在于颜色缓冲区中的值。在这两种情况下,在渲染时颜色值要么完全被废弃,要么就直接覆盖旧的颜色值。现在介绍一种新的方式,混合。
glEnable(GL_BLEND);
当开启混合时,输入的颜色值将会和已经存在于颜色缓冲区中的颜色进行组合。至于如何进行组合,取决于你为混合设置的参数。
组合颜色
首先需要了解两个概念:
- 目标颜色:已经存在于颜色缓冲区中的颜色值。这个颜色包含红、绿、蓝三种成分,和可选的 alpha 值。
- 源颜色:来自于渲染命令的颜色。包含红、绿、蓝和可选的 alpha 值成分。
当开启 OpenGL 的混合时,源颜色是如何与目标颜色进行结合的,取决于你设置的混合方程式。默认情况下的,混合方程如下:
Ci = (Cs * S) + (Cd * D)
在这里,Ci 是最终被计算出来的颜色,Cs 是源颜色,Cd 是目标颜色。而 S 和 D 分别是源混合因子和目标混合因子。这些混合因子通过:glBlendFunc (GLenum S, GLenum D); 设置。S 和 D 是枚举值,其枚举列表如下:(R、G、B、A 分别代表红色成分,绿色成分,蓝色成分,Alpha 成分)
Function | RGB Blend Factors | Alpha Blend Factor |
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_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_CONST_COLOR | (1,1,1)-(Rc,Gc,Bc) | 1-Ac |
GL_CONST_ALPHA | (Ac,Ac,Ac) | Ac |
GL_ONE_MINUS_CONSTANT_ALPHA | (1,1,1)-(Ac,Ac,Ac) | 1-Ac |
GL_SRC_ALPHA_SATURATE | (f,f,f)* | 1 |
* f = min(A s , 1 – A d)
PS:上面表格的颜色值都是用浮点数来表示的。举例说明:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
通过上面的调用来告诉 OpenGL 我们使用的混合因子。假设现在渲染命令传进来的源 RGBA 颜色值是 (1.0f,0.0f,1.0f,1.0f). 而在颜色缓冲区相同的位置已经有目标颜色 RGBA 值是 (0.0f, 1.0f, 0.0f, 1.0f).
代入公式:Ci = (Cs * S) + (Cd * D)
Cs=(1.0f,0.0f,1.0f,0.6f), GL_SRC_ALPHA 参数说明源混合因子取源颜色的 alpha 值,所以 S = 0.6f。Cd=(0.0f, 1.0f, 0.0f, 1.0f). GL_ONE_MINUS_SRC_ALPHA 说明了目标混合因子取 1-As,D = 1-0.6 = 0.4f;
所以代入计算得 (Cs * S) = (0.6f, 0.0f, 0.6f, 0.36f) , (Cd * D) = (0.0f, 0.4f, 0.0f, 0.4f)
Ci = (Cs * S) + (Cd * D) = (0.6f, 0.4f, 0.6f, 0.76f)
得到的颜色是近似于洋紫色。
上面表格的 GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR,GL_CONSTANT_ALPHA 和 GL_ONE_MINUS_CONSTANT_ALPHA 的值在默认情况下是 (0.0f, 0.0f, 0.0f, 0.0f) 但我们可以通过下面的函数修改:
void glBlendColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
颜色混合经常用于在不透明的前面画一个透明的物体。这样我们就可以先画好背景,然后再在上面画一个半透明的物体。可以制造一种,透过彩色玻璃看外面的风景的效果。下面的例子:(物体在光滑的彩色地面上的倒影)。
#include "gltools.h"
#include "math3d.h"
//光源位置
GLfloat lightPos[4] = { -100.0f, 100.0f, 50.0f, 1.0f };
GLfloat lightPosMirror[4] = {-100.0f, -100.0f, 50.0f, 1.0f};
GLfloat fNoLight[] = { 0.0f, 0.0f, 0.0f, 0.0f };
GLfloat fLowLight[] = { 0.25f, 0.25f, 0.25f, 1.0f };
GLfloat fBrightLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
void DrawGround()
{
//画地面
GLfloat fExtent = 20.0f;
GLfloat fStep = 0.5f;
GLfloat y = 0.0f;
GLfloat fColor;
GLfloat iStrip, iRun;
GLint iBounce = 0;
glShadeModel(GL_FLAT);
for(iStrip = -fExtent; iStrip <= fExtent; iStrip += fStep)
{
glBegin(GL_TRIANGLE_STRIP);
for(iRun = fExtent; iRun >= -fExtent; iRun -= fStep)
{
if((iBounce %2) == 0)
fColor = 1.0f;
else
fColor = 0.0f;
glColor4f(fColor, fColor, fColor, 0.5f);
glVertex3f(iStrip, y, iRun);
glVertex3f(iStrip + fStep, y, iRun);
iBounce++;
}
glEnd();
}
glShadeModel(GL_SMOOTH);
}
void DrawWorld()
{
glColor3f(1.0f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(0.0f, 0.5f, -3.5f);
gltDrawTorus(0.25, 0.08, 68, 37);
glPopMatrix();
}
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glPushMatrix();
//设置光源的倒影
glLightfv(GL_LIGHT0, GL_POSITION, lightPosMirror);
glPushMatrix();
//因为是镜像,所以要反过来绘制
glFrontFace(GL_CW);
glScalef(1.0f, -1.0f, 1.0f);
DrawWorld();
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);
glPopMatrix();
//设置光源在左上角
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
DrawWorld();
glutSwapBuffers();
}
void ChangeSize(GLsizei w, GLsizei h)
{
if (h == 0)
h = 1;
glViewport(0, 0, w, h);
GLfloat faspect = (GLfloat)w/(GLfloat)h;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(35.0, faspect, 1.0f, 50.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//往Y轴负方向平移一点
glTranslatef(0.0f, -0.4f, 0.0f);
glutPostRedisplay();
}
void SetupRC()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//开启深度测试,剔除物体背面
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
//逆时针方向绘制的面为正面
glFrontFace(GL_CCW);
//开启光照,设置光源参数
glEnable(GL_LIGHTING);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fNoLight);
glLightfv(GL_LIGHT0, GL_AMBIENT, fLowLight);
glLightfv(GL_LIGHT0, GL_DIFFUSE, fBrightLight);
glLightfv(GL_LIGHT0, GL_SPECULAR, fBrightLight);
glEnable(GL_LIGHT0);
//开启颜色追踪,设置镜面加亮效果
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glMateriali(GL_FRONT, GL_SHININESS, 128);
}
int main(int args, char **argv)
{
glutInit(&args, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(800, 600);
glutCreateWindow("Reflection");
glutDisplayFunc(RenderScene);
glutReshapeFunc(ChangeSize);
SetupRC();
glutMainLoop();
return 0;
}
改变混合方程
OpenGL 的默认混合方程式:Ci = (Cs * S) + (Cd * D)
但我们可以通过函数:
void glBlendEquation(GLenum mode);
来改变混合方程。可选择的混合方程模式如下表:
还有一个更灵活的函数:
void glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
这个函数允许你分别指定 RGB 的混合方程和 alpha 成分的混合方程。