Jeff Molofee(NeHe) 的OpenGL 教程
- Alpha 混合
原 文 : Lesson 8: Blending
译 者 : CKER
OpenGL 中 的绝大多数特效都与某些类型的(色彩)混合有关。混色的定义为,将某个象素的颜色和已绘制在屏幕上与其对应的象素颜色相互结合。至于如何结合这两个颜色则 依赖于颜色的alpha 通道的分量值,以及/ 或 者所使用的混色函数。Alpha 通常是位于颜色值末尾的第4 个 颜色组成分量。前面这些课我们都是用GL_RGB 来指定颜色的三个分量。相应的GL_RGBA 可以指定alpha 分量的值。更进一步, 我们可以使用glColor4f() 来代替glColor3f() 。
绝大多数人都认为Alpha 分 量代表材料的透明度。这就是说,alpha 值为0.0 时 所代表的材料是完全透明的。alpha 值为1.0 时 所代表的材料则是完全不透明的。
8.1 、混色的公式
若您对数学不感兴趣,而只想看看如何实现透明,请跳过这一节。若您想深入理解(色彩)混合的工作原理,这一节应该适合您吧。(译 者 :其实混合的基本原理是就将要分色的图像各象素的颜色以及背景颜色均按照RGB 规 则各自分离之后,根据 — 图像的RGB 颜 色分量*alpha 值+ 背景的RGB 颜色分量*(1-alpha 值) — 这样一个简单公式来混合之后,最后将混合得到的RGB 分 量重新合并。)公式如下:
(Rs Sr + Rd Dr, Gs Sg + Gd Dg, Bs Sb + Bd Db, As Sa + Ad Da) |
OpenGL 按照上面的公式计算这两个象素的混色结果。小写的s 和r 分别代表源象素和目标象素。大写的S 和D 则是相应的混色因子。这些决定了您如何对这些象素混色。绝大多数情况下,各颜色通道的alpha 混色值大小相同,这样对源象素就有(As, As, As, As) ,目标象素则有(1, 1, 1, 1) - (As, As, As, As) 。 上面的公式就成了下面的模样:
(Rs As + Rd (1 - As), Gs As + Gd (1 - As), Bs As + Bs (1 - As), As As + Ad (1 - As)) |
这个公式会生成透明/ 半透明的效果。
8.2 、OpenGL 中的混色
在OpenGL 中实现混色的步骤类似于我们以前提到的OpenGL 过程。接着设置公式,并在绘制透明对象时关闭写深度缓存。因为我们想在半透明的图形背后绘制对象。 这不是正确的混色方法,但绝大多数时候这种做法在简单的项目中都工作的很好。
Rui Martins 的补充 :正确 的混色过程应该是先绘制全部的场景之后再绘制透明的图形。并且要按照与深度缓存相反的次序来绘制(先画最远的物体)。考虑对两个多边形(1 和2 )进行alpha 混 合,不同的绘制次序会得到不同的结果。(这里假定多边形1 离观察者最近,那么正确的过程应该先画多 边形2 ,再画多边形1 。正如您再现实中所见 到的那样,从这两个“ 透明的” 多边形背后照 射来的光线总是先穿过多边形2 ,再穿过多边形1 , 最后才到达观察者的眼睛)。 在深度缓存启用时,您应该将透明图形按照深度进行排序,并在全部场景绘制完毕之后再绘制这些透明物体。否则您将得到不正确的结果。我知道某些时候这样做是 很令人痛苦的,但这是正确的方法。
我们 将使用第七课的代码。一开始先在代码开始处增加两个新的变量。出于清晰起见,我重写了整段代码。
#include <windows.h> // Windows 的头文件
#include <stdio.h> // 标准输入/ 输 出库的头文件
#include <gl\gl.h> // OpenGL32 库的头文件
#include <gl\glu.h> // GLu32 库的头文件
#include <gl\glaux.h> // GLaux 库的头文件
HGLRC hRC=NULL; // 永久着色描述表
HDC hDC=NULL; // 私有GDI 设 备描述表
HWND hWnd=NULL; // 保存我们的窗口句柄
HINSTANCE hInstance; // 保存程序的实例
bool keys[256]; // 用于键盘例程的数组
bool active=TRUE; // 窗口的活动标志,缺省为TRUE
bool fullscreen=TRUE; // 全屏标志缺省设定成全屏模式
BOOL light; // 光源的开/ 关
bool blend; // Blending 开/ 关 ( 新增 )
BOOL lp; // L 键按下了么?
BOOL fp; // F 键按下了么?
GLfloat xrot; // X 旋转
GLfloat yrot; // Y 旋转
GLfloat xspeed; // X 旋转速度
GLfloat yspeed; // Y 旋转速度
GLfloat z=-5.0f; // 深入屏幕的距离
GLfloat LightAmbient[]= { 0.5f }; // 环境光参数
GLfloat LightDiffuse[]= { 1.0f }; // 漫射光参数
GLfloat LightPosition[]= { 0.0f }; // 光源位置
GLuint filter; // 滤波类型
GLuint texture[3]; // 3 种纹理的储存空间
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // WndProc 定义
然后往下移动到 LoadGLTextures() 这 里。找到“ if (TextureImage[0]=LoadBMP("Data/Crate.bmp")) ” 这一行。我们现在使用有色玻璃纹理来代替上一课中的木箱纹理。
if (TextureImage[0]=LoadBMP("Data/glass.bmp")) // 载入玻璃位图 ( 已修改)
在 InitGL() 代码段加入以下两行。第一行以全亮度绘制此物体,并对其进行50% 的alpha 混合(半透明)。当混合选项打开时,此物体将会产生50% 的 透明效果。第二行设置所采用的混合类型。Rui Martins 的补充 :alpha 通道的值为0.0 意味着物体材质是完全透明 的。1.0 则意味着完全不透明。
glColor4f(1.0f,1.0f,1.0f,0.5f); // 全亮度, 50% Alpha 混 合( 新增)
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // 基于源象素alpha 通道值的半透明混合函 数 ( 新增)
在接近第七课结尾处的地方找到下面的代码段。
if (keys[VK_LEFT]) // Left 方向键按下了么?
{
yspeed-=0.01f; // 若是, 减少yspeed
}
接着上面的代码,我们增加如下的代码。这几行监视B 键是否按下。如果是的话,计算机检查混合选项是否已经打开。然后将其置为相反的状态。
if (keys[VK_LEFT]) // Left 方向键按下了么?
if (keys['B'] && !bp) // B 健按下且bp 为 FALSE 么?
{
bp=TRUE; // 若是, bp 设为 TRUE
blend = !blend; // 切换混合选项的 TRUE / FALSE
if(blend) // 混合打开了么?
{
glEnable(GL_BLEND); // 打开混合
glDisable(GL_DEPTH_TEST); // 关闭深度测试
}
else // 否则
{
glDisable(GL_BLEND); // 关闭混合
glEnable(GL_DEPTH_TEST); // 打开深度测试
}
}
if (!keys['B']) // B 键松开了么?
{
bp=FALSE; // 若是, bp 设为 FALSE
}
但是怎样才能在使用纹理贴图的时候指定混合时的颜色呢?很简单, 在调整贴图模式时,文理贴图的每个象素点的颜色都是由alpha 通道参数与当前地象素颜色相乘所得 到的。比如,绘制的颜色是(0.5, 0.6, 0.4) ,我们会把颜色相乘得到(0.5, 0.6, 0.4, 0.2) (alpha 参 数在没有指定时,缺省为1.0 )。
就是如此。OpenGL 实现Alpha 混合的确很简单。
原文注 ( 11/13/1999 )
我(NeHe )混色代码进行了修改,以使 显示的物体看起来更逼真。同时对源象素和目的象素使用alpha 参数来混合,会导致物体的人造痕迹 看起来很明显。会使得物体的背面沿着侧面的地方显得更暗。基本上物体会看起来很怪异。我所用的混色方法也许不是最好的,但的确能够工作。启用光源之后,物 体看起来很逼真。感谢Tom 提供的原始代码,他采用的混色方法是正确的,但物体看起来并不象所期望 的那样吸引人。
代码所作的再次修改是因 为在某些显卡上 glDepthMask() 函数存在寻址问题。这条命令在某些卡上启用或关闭深度缓冲测试时似乎不是很有效,所以我已经将启用或 关闭深度缓冲测试的代码转成老式的 glEnable 和 glDisable 。
8.3 、纹理贴图的Alpha 混合
用于纹理贴图的alpha 参数可以象颜色一样从问题贴图中读取。方法如 下,您需要在载入所需的材质同时取得其的alpha 参数。然后在调用 glTexImage2D() 时使用 GL_RGBA 的颜色格式。