完全的透明(simple transparency)
在OpenGL中很多特定的效果依赖于一些类型的混合。混合用来把将要绘制到屏幕上的像素的颜色和已经绘制到屏幕上的像素的颜色结合起来。颜色如何混合依赖于颜色的alpha值 和/或使用的混合函数。alpha通常是指定的颜色组成的第四个参数。过去,你使用GL_RGB 来说明颜色是由3种成分组成的。GL_RGBA 能够用来指定alpha值。另外,我们可以使用glColor4f() 函数代替glColor3f() 函数。
大部分人认为alpha是材料的透明度。alpha值等于0.0f意味着这种材料是完全透明的。而1.0f则意味着完全不透明。
混合方程式(The Blending Equation )
如果你不太喜欢研究数学,只是想知道如何实现透明,可以跳过这部分。如果你想理解混合是如何实现的,仔细阅读下面的内容。
(Rs Sr + Rd Dr, Gs Sg + Gd Dg, Bs Sb + Bd Db, As Sa + Ad Da)
OpenGL将使用上面的方程式计算两种像素的混合结果。下标s和d说明了源像素和目标像素。S和D是混合元素。这些值指示了你想如何混合这些像素。最常用的S和D的值是(As, As, As, As) (AKA source alpha) 对于 S,和(1, 1, 1, 1) - (As, As, As, As) (AKA one minus src alpha) 对于 D。它将产生一个向下面这样的混合方程式:
(Rs As + Rd (1 - As), Gs As + Gd (1 - As), Bs As + Bd (1 - As), As As + Ad (1 - As))
这个方程式将产生透明的/半透明的风格的效果。
OpenGL中的混合
我们像启用其他功能一样启用混合。然后我们设置混合方程式,当绘制透明物体的时候关闭深度缓存,因为我们仍然想在半透明的图形后面绘制物体。这并不是最合理的使用混合的方式,但是大部分时候应用于一些简单的物体,它的效果很好。Rui Martins Adds: 在你绘制完整个场景之后,正确绘制所有透明(alpha < 1.0f)的多边形的方法是,以逆向深度的顺序(从最远处开始)绘制它们。这是因为混合俩个多边形(1和2)的效果因为绘制的顺序不同,结果也不同。例如:假设多边形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;
// 永久的渲染上下文( Rendering Context)
HDC
hDC=NULL;
// 私有的GDI设备上下文( GDI Device Context)
HWND
hWnd=NULL;
// 获得我们窗口的句柄
HINSTANCE
hInstance;
// 获得应用程序的实例
bool
keys[256];
// 用于键盘行为的数组
bool
active=TRUE;
// 窗口活动标记,默认设置为TRUE
bool
fullscreen=TRUE;
// 全屏标记,默认设置为全屏
BOOL light; // 光照打开/关闭
BOOL blend; //混合 关闭/打开(NEW)
BOOL lp; // L键是否按下
BOOL fp; // F键是否按下
BOOL bp; //B键是否按下(NEW)
GLfloat xrot; // x轴上的转动角度
GLfloat yrot; // y轴上的转动角度
GLfloat xspeed; // x轴上的转动速度
GLfloat yspeed; // y轴上的转动速度
GLfloat z=-5.0f; //在屏幕里的深度
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; // 环绕光
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; // 散射光
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; //光源位置
GLuint filter; // 使用哪种纹理
GLuint texture[3]; // 存储三种纹理
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // 声明WndProc函数
然后到LoadGLTextures()函数中。找到if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))这行代码。把这行代码改为下面的代码。我们在这节课中使用玻璃属性的纹理代替条板箱纹理。
if (TextureImage[0]=LoadBMP("Data/glass.bmp")) // 加载Glass Bitmap ( MODIFIED )
在InitGL() 函数中加入下面两行代码。第一行代码把物体的绘制明亮度设置为50%alpha(不透明)最强光。这意味着当启用混合时,物体将为50%透明。第二行代码使用我们使用的混合类型。
Rui Martins Adds:alpha值等于0.0f意味着这种材料是完全透明的。而1.0f则意味着完全不透明。
glColor4f(1.0f,1.0f,1.0f,0.5f); // 最强亮度, 50% Alpha ( NEW )
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // 基于源alpha值用于半透明的混合函数 ( NEW )
观察下面这段代码,你可以在第七课最后面找到。
if (keys[VK_LEFT]) // Left Arrow 键是否被按下?
{
yspeed-=0.01f; // 减小yspeed值
}
在这段代码的正下方,我们将加入下面的代码。下面的代码用来监视'B'键是否被按下。如果它被按下,检查混合是关闭还是打开的。如果混合是打开的,我们就关掉它。如果混合是关闭的,我们就打开它。
if (keys['B'] && !bp) // B 键是否被按下,bp是不是等于 FALSE?
{
bp=TRUE; // 如果是, bp 设置为TRUE
blend = !blend; // 切换 blend 为TRUE / FALSE
if(blend) // blend 是否为TRUE?
{
glEnable(GL_BLEND); // 打开混合
glDisable(GL_DEPTH_TEST); // 关闭深度测试、
}
else // 否则
{
glDisable(GL_BLEND); // 关闭混合
glEnable(GL_DEPTH_TEST); // 打开深度测试
}
}
if (!keys['B']) // B 键是否被松开?
{
bp=FALSE; // 如果是, bp 设置为FALSE
}
如果我们想使用纹理映射,如何指明使用的颜色呢?很简单,在处理纹理模式时,纹理映射的每一个像素都乘以当前的颜色。所以,如果我们要绘制的颜色是(0.5, 0.6, 0.4),我们乘以它,得到(0.5, 0.6, 0.4, 0.2) (如果没有指明,alpha假设为1.0f)。(原文:But how can we specify the color if we are using a texture map? Simple, in modulated texture mode, each pixel that is texture mapped is multiplied by the current color. So, if the color to be drawn is (0.5, 0.6, 0.4), we multiply it times the color and we get (0.5, 0.6, 0.4, 0.2) (alpha is assumed to be 1.0 if not specified).
)
就是这样。在OpenGL中混合实际上非常简单。
Note (11/13/99)
我(NeHe)修改了混合部分的代码,这样使用这个物体看起来更加真实。源使用alpha值然后去实现混合将会产生人工制作的效果。会导致背面更黑,以及侧面。物体可能开起来有点奇怪。我使用的混合方式不是最好的,但它可以实现混合的效果,当启用光照的时候,物体看起来很真实。谢谢Tom提供的原始代码,他使用的混合alpha值的方式是很合理的,但是不像人们想象的那样吸引人。
这段代码又做了一些修改,来处理有些显卡对于glDepthMask()函数支持不够理想的问题。看起来好像这条命令在某些显卡上不能启用或禁止深度测试,所以我又改回了过去常用的glEnable 和glDisable函数来启用和禁用深度测试。
(原文:
I ( NeHe ) have modified the blending code so the output of the object looks more like it should. Using Alpha values for the source and destination to do the blending will cause artifacting. Causing back faces to appear darker, along with side faces. Basically the object will look very screwy. The way I do blending may not be the best way, but it works, and the object appears to look like it should when lighting is enabled. Thanks to Tom for the initial code, the way he was blending was the proper way to blend with alpha values, but didn't look as attractive as people expected :)
The code was modified once again to address problems that some video cards had with glDepthMask(). It seems this command would not effectively enable and disable depth buffer testing on some cards, so I've changed back to the old fashioned glEnable and Disable of Depth Testing.
)
纹理映射的alpha(Alpha from Texture Map)
用于透明的alpha值可以像颜色值一样从纹理映射中读取出来,想要这样做,你将需要把alpha值添加到你加载的图像中,然后在调用glTexImage2D()函数时使用GL_RGBA 颜色格式。
(原文:
The alpha value that is used for transparency can be read from a texture map just like color, to do this, you will need to get alpha into the image you want to load, and then use GL_RGBA for the color format in calls to glTexImage2D().
)