OpenGL 混合(Blending)
在OpenGL中,物体透明技术通常被叫做混合(Blending)。透明的物体的颜色来自于不同浓度的自身颜色和它后面的物体颜色的混合。
- 可以开启GL_BLEND来启用混合功能:
glEnable(GL_BLEND);
- OpenGL以下面的方程进行混合:
- Csource:源颜色向量。这是来自纹理的本来的颜色向量。
- Cdestination:目标颜色向量。这是储存在颜色缓冲中当前位置的颜色向量。
- Fsource:源因子。设置了对源颜色的alpha值影响。
- Fdestination:目标因子。设置了对目标颜色的alpha影响。
Cresult = Csource ∗ Fsource + Cdestination ∗ Fdestination
-
片段着色器运行完成并且所有的测试都通过以后,混合方程才能自由执行片段的颜色输出,当前它在颜色缓冲中(前面片段的颜色在当前片段之前储存)。源和目标颜色会自动被OpenGL设置,而源和目标因子可以让我们自由设置。
glBlendFunc
函数就是这个作用void glBlendFunc(GLenum sfactor, GLenum dfactor)
- 接收两个参数,来设置源(source)和目标(destination)因子。
为从两个方块获得混合结果,可以把源颜色的alpha给源因子,1-alpha给目标因子。调整到glBlendFunc之后就像这样:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
; - 接收两个参数,来设置源(source)和目标(destination)因子。
-
在半透明的处理之后会引入新问题,前面的透明部分会阻塞后面的,原因是深度测试与混合一同工作时出现了问题。当写入深度缓冲的时候,深度测试并不关心片段是否有透明度,所以透明部分就和其他值一样被写入深度缓冲。结果是透明的物体在深度检测时都被忽视了透明度,本应该显示在透明物体后面的物体被深度缓存抛弃了,只留下了在前面的透明物体。
所以正确的做法是:- 开启深度写入, glDepthMask( GL_TRUE )
- 渲染所有的不透明物体,以任何顺序
- 关闭深度写入, glDepthMask( GL_FALSE )
- 开启混合 glEnable( GL_BLEND )
- 从远到近渲染透明物体
为什么要这么做?
我们关注两个缓存: 深度缓存和颜色缓存。 这两个缓存其实是两个很大的2维数组, 大小为窗口的高度 X 宽度。 颜色缓存不停地更新,最终会得到屏幕上看到的颜色。每一个屏幕像素的值都会有颜色缓存跟它对应。 和颜色缓存类似,每一个像素也都一个深度缓存的值跟它对应。 深度缓存描述片元离相机的远近,然后取最近的深度。
比如你要渲染一个三角形,它远离相机,它会为将要覆盖的屏幕像素产生一组颜色缓存和深度缓存。当渲染另一个离相机近一点的多边形,它也会为它覆盖的像素产生一组颜色缓存和深度缓存。如果这两个多边形重叠,颜色缓存就会产生“竞争”,离得远的片元(深度值大)将被丢弃。最后, 只有离相机最近的片元才能更新颜色缓存和深度缓存, 渲染到屏幕上。
对于渲染不透明物体,打开深度检测,只有物体离相机更近时,才会有机会更新深度缓冲区和颜色缓冲区,前面的物体就能遮住后面的物体,而不用管物体渲染的前后关系。对于透明的物体,需要关闭深度检测,再开启混合,从远到近渲染透明物体。关闭深度检测后,渲染透明物体, OpenGL将读取深度缓存,然后决定什么时候丢弃该片元(例如, 如果你的透明物体在已经绘制的不透明物体后面, 则透明物体的片元将丢弃)。由于没有开启深度检测, 当透明物体离相机更近时,此透明的片元不会阻挡后面的片元,而是会跟他们混合(更新颜色缓存, 但不更新深度缓存)。
关闭深度测试是“欺骗”OpenGL, 让它不知道哪一个透明片元离相机最近(因为透明片元不会因之前绘制的透明片元而深度测试失败),常常会用来渲染透明片元。
-
渲染透明物体时,未关闭深度检测,在前面的壶即使是透明的,也会挡住后面的物体
-
关闭深度检测,前面的透明壶就能正常透出后面的壶
关键部分代码如下:
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0f, 0.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0, 1, 0);
glPushMatrix();
glTranslatef(LightPosition[0], LightPosition[1], LightPosition[2]);
glDisable(GL_LIGHTING);
glColor3f(1.0f, 1.0f, 1.0f);
glutSolidSphere(0.1, 10.0, 10.0);
glEnable(GL_LIGHTING);
glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);
glPopMatrix();
/****************************************不透明************************************************/
glPushMatrix();
glTranslatef(0.0f, 0.0f, 0.0f);
GLfloat mat_ambient1[] = { 0.0, 0.8, 0.8, 1 };
GLfloat mat_diffuse1[] = { 0.0, 0.8, 0.8, 1 };
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient1);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse1);
glutSolidTeapot(1);
glPopMatrix();
/****************************************透明************************************************/
glDepthMask(GL_FALSE);//控制深度缓冲区是否可写
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//选择源混合因子与目标混合因子
glEnable(GL_BLEND);//开启混合
//从远及近画茶壶,试点在(0,0,20)
glPushMatrix();
glTranslatef(0.0f, 0.0f, 5.0f);
GLfloat mat_ambient2[] = { 0.2, 0.0, 0.3, 0.4 };//第四个数设置透明度
GLfloat mat_diffuse2[] = { 0.2, 0.0, 0.3, 0.4 };
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient2);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse2);
glutSolidTeapot(0.3);
glPopMatrix();
glPushMatrix();
glTranslatef(0.0f, 0.0f, 10.0f);
GLfloat mat_ambient3[] = { 0.21, 0.35, 0.3, 0.5 };
GLfloat mat_diffuse3[] = {0.21, 0.35, 0.3, 0.5};
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient3);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse3);
glutSolidTeapot(3);
glPopMatrix();
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glutSwapBuffers();
}