OpenGL中的混色问题(Alpha通道)
1、在图像处理中alpha到底是什么?
Alpha通道是计算机图形学中的术语,指的是特别的通道,意思是“非彩色”通道,主要是用来保存选区和编辑选区。
2、为什么用‘Alpha’代表透明度?
Alpha 没有透明度的意思,不代表透明度。opacity 和 transparency 才和透明度有关,前者是不透明度,后者是透明度。比如 css 中的「opacity: 0.5」就是设定元素有 50% 的不透明度。后来 Alvy Ray Smith 提出每个像素再增加一个 Alpha 通道,取值为0到1,用来储存这个像素是否对图片有「贡献」,0代表透明、1代表不透明。也就是说,「Alpha 通道」储存一个值,其外在表现是「透明度」,Alpha 和透明度没啥关系。
为什么取名为 Alpha 通道,我觉得是因为这是除RGB以外「第一个通道」的意思,没有别的更深刻的含义。
「Alpha 通道」是图片内在的一个属性,用 css 或者其他外部方法设定透明度,并没有改变图片的 Alpha 通道的值。
阿尔法通道(α Channel或Alpha Channel)是指一张图片的透明和半透明度。例如:一个使用每个像素16比特存储的位图,对于图形中的每一个像素而言,可能以5个比特表示红色,5个比特表示绿色,5个比特表示蓝色,最后一个比特是阿尔法。在这种情况下,它要么表示透明要么不是,因为阿尔法比特只有0或1两种不同表示的可能性。又如一个使用32个比特存储的位图,每8个比特表示红绿蓝,和阿尔法通道。在这种情况下,就不光可以表示透明还是不透明,阿尔法通道还可以表示256级的半透明度,因为阿尔法通道有8个比特可以有256种不同的数据表示可能性。
RGBA是代表Red(红色) Green(绿色) Blue(蓝色)和 Alpha的色彩空间。虽然它有的时候被描述为一个颜色空间,但是它其实仅仅是RGB模型的附加了额外的信息。采用的颜色是RGB,可以属于任何一种RGB颜色空间,但是Catmull和Smith在1971至1972年间提出了这个不可或缺的alpha数值,使得alpha渲染和alpha合成变得可能。提出者以alpha来命名是源于经典的线性插值方程αA + (1-α)B所用的就是这个希腊字母。(线性插值是数学、计算机图形学等领域广泛使用的一种简单插值方法)
真正让图片变透明的不是Alpha 实际是Alpha所代表的数值和其他数值做了一次运算 。比如你有一张图片你想抠出图片中间的一部分 在PS里你会建立一个蒙板 然后在蒙板里把不需要的地方填充成黑色 需要的留成白色 这个时候实际上是是做了一次乘法。用黑色所代表的数值0去乘以你所填充的地方 那么这个地方就变透明了
3、Alpha通道详解
通道的概念
通道作为图像的组成部分,是与图像的格式密不可分的,图像颜色,格式的不同决定了通道的数量和模式,在通道面板中可以直观的看到。通道的不同,自然我们给它们的命名就不同。
Alpha 通道
Alpha 通道是为保存选择区域而专门设计的通道。在计算机图形学中,一个RGB颜色模型的真彩图形,用由红、绿、蓝三个色彩信息通道合成的,每个通道用了8位色彩深度,共计24位,包含了所有彩色信息。为实现图形的透明效果,采取在图形文件的处理与存储中附加上另一个8位信息的方法,这个附加的代表图形中各个素点透明度的通道信息就被叫做Alpha通道。
Alpha通道使用8位二进制数,就可以表示256级灰度,即256级的透明度。白色(值为255)的Alpha像素用以定义不透明的彩色像素,而黑色(值为0)的Alpha通道像素用以定义透明像素,介于黑白之间的灰度(值为30-255)的Alpha像素用以定义不同程度的半透明像素。因而通过一个32位总线的图形卡来显示带Alpha通道的图形,就可能呈现出透明或半透明的视觉效果。
在生成一个图像文件时,并不必须产生 Alpha 通道。通常它是由人们在图像处理过程中人为生成,并从中读取选择区域信息的。因此在输出制版时, Alpha 通道会因为与最终生成的图像无关而被删除。但也有时,比如在三维软件最终渲染输出的时候,会附带生成一张 Alpha 通道,用以在平面处理软件中作后期合成。
除了 photoshop 的文件格式 PSD 外, GIF 与 TIFF 格式的文件都可以保存 Alpha 通道。而 GIF 文件还可以用 Alpha 通道作图像的去背景处理。因此,我们可以利用 GIF 文件的这一特性制作任意形状的图形。
一个栗子:
一个透明或半透明图形的数学模型应当如下:
为了便于下面的分析,设Alpha值[0,255]区间映射为[0,1]区间相对应的值表示,即Alpha值为0—1之间的数值。则图形文件中各个像素点可表示为:
Graphx(Redx,Greenx,Bulex,Alphax)
屏幕上相应像素点的显示值就转换为:
Dispx(Redx*Alphax,Greenx*Alphax,Bluex*Alphax)
Alpha通道不仅用于单个图形的透明或半透明显示,更重要的是在图像合成中被广泛运用。
下面是如何根据Alpha通道数据进行图像混合的算法:
事实上,我们把需要组合的颜色计算出不含Alpha分量的原始RGB分量然后相加便可。如:两幅图像分别为A和B,由这两幅图像组合而成的图像称为C,则可用如下四元组表示图A和B,三元组表示图像C:
A:(Ra,Ga,Ba,Alphaa)
B:(Rb,Gb,Bb,Alphab)
C:(Rc,Gc,Bc)
根据上述算法,则:
Rc=Ra*Alphaa+Rb*Alphab
Gc=Ga*Alphaa+Gb*Alphab
Bc=Ba*Alphaa+Bb*Alphab
这就是两图像混合后的三原色分量。如果有多幅图像需要混合,则按照以上方法两幅两幅地进行混合。
Alpha通道在多媒体课件作品开发中的应用
Alpha通道技术是非曲直图像合成的最基本技术,目前其应用多局限于多媒体课件作品开发中的个别环节,未能从整体上给予Alpha通道功能以足够的重视。如:对通用图像处理软件PHOTOSHOP中的Alpha通道及其运用有较深刻的理解,而疏忽了Alpha通道在动画、视频和多媒体集成工具中的应用。本文通过对Alpha通道的综合分析,以期使Alpha通道技术在多媒体课件作品开发中的应用形成一个相对完整的理论体系,并在多媒体课件作品开发实践中起到具体的指导作用。
4、Alpha通道在多媒体素材准备中的应用
( 1.图像处理软件Photoshop中的Alpha通道
在图像处理软件Photoshop中,通道是一个最为基本的概念,颜色通道代表了该图像的主要色彩信息,附加通道有用于印刷的专色通道和存储、修改选取区域的Alpha通道。
通常情况下,单独创建的新通道就是Alpha通道,这个通道并不存储图像的色彩,而是将选择域作为8位灰度图像存放并被加入到图像的颜色通道中。因而Alpha通道的内容代表的不是图像的颜色,而是选择区域,其中的白色表示完全选取区域,黑色为非选取区域,不同层次的灰度代表不同的选取百分率,最多可有256级灰阶。
对Alpha通道内容的操作,即是创建、存储、修改我们所需要的选取区域。如在目标图层上载入该选区(即运用该Alpha通道)便可实现任意层次的选取。通过多个Alpha通道之间的计算或Alpha通道与图层的合成便能够产生许多特殊的效果。因此,Alpha通道功能可以说是Photoshop编辑合成多媒体作品中所使用的背景图片、主题图片、按钮、导航标志等素材的一大法宝。
( 2.Alpha通道在动画创作软件中的应用
Alpha通道无论是在二维动画软件还是三维动画软件中都有广泛的应用。
大家熟知的3DMAX动画软件中提供了动画作品后期合成的工作环境(即VIDEO POST),利用VIDEO POST的图像合成功能可以实现一次处理许多不同层次的图像文件与动画场景合成的设定。其中使用最频繁的合成方式Alpha composing就是靠Alpha通道来实现的。
3DMAX能根据文件的输出格式产生包含Alpha通道的32位文件格式(特别是32位的Targa图像文件)。当进行渲染时,任何不含物体的区域的Alpha值为0,实色的物体的Alpha值为255,而透明材质的Alpha值介于0~255间。利用3DMAX的这一特性可在该软件或其它软件中实现各种透明重叠的图像或动画效果。
Flash矢量动画中同样具有Alpha通道功能的运用。Alpha是Flash动画场景中图形符号(graphic)的一个主要属性,改变其值(0~100之间)便可改变对象符号的透明程度。如在各关键帧设定某图形符号以不同的Alpha值,则该图形符号就呈现出动态变化的透明效果。通过编写动作脚本(Action scripting),用户交互式地改Alpha值,更能使动画作品生动有趣。
( 3.利用Alpha通道在视频编辑软件中实现高叠轨道的透明合成
在视频处理软件中,通常要将多个视频片段按照要求重叠在一起形成透明或半透明效果。Premiere电影编辑软件里提供了多达97个视频高叠轨道用以实现影像片段的合成。在高叠片段的video Option命令中有一个专门用于合成的透明设置选项Transparency,它提供了许多key Type基本(透明)类型。其中Alpha channel key就是利用影像或图片的Alpha通道在片段上选定区域形成透明效果。
一些影视创作人员在节目编辑中,为了把广告的图标、报道题花等图案作为标记或栏目标题叠加在画面上,使用Photoshop、3DMAX或Cool3d等软件生成带Alpha通道的32位TGA图片或图片序列,在非编系统软件中运用Alpha channel key功能实现标题字幕或图标的高叠。
Alpha通道技术本身并不复杂,但其运用上的创意是无限的。不断总结在多媒体课件作品开发中运用Alpha通道技术的途径以提高作品技术质量、增强作品艺术感染力,是一个永无止境的课题。
opengl 里面,alpha值通常是在激活了混合操作之后起作用的。用来将已经处理过的片元颜色值与存储在帧缓存中的像素颜色值结合起来。如果不进行混合操作,每个新的片元都将被opengl当成是不透明的,覆盖掉已经存在的所有颜色值。利用混合操作可以实现半透明等状态。可以这样看alpha值,将片元的RGB分量考虑为对颜色的表示,而将alpha值分量考虑为颜色的不透明度。
你确定是先绘制的背景,后绘制的发光吗?另外你要把发光区域的alpha设置为0.5,你也可以试试其他参数如0.75,0.25
OpenGL混色2009-10-14 10:05今天介绍关于OpenGL混合的基本知识。混合是一种常用的技巧,通常可以用来实现半透明。但其实它也是十分灵活的,你可以通过不同的设置得到不同的混合结果,产生一些有趣或者奇怪的图象。混合是什么呢?混合就是把两种颜色混在一起。具体一点,就是把某一像素位置原来的颜色和将要画上去的颜色,通过某种方式混在一起,从而实现特殊的效果。
假设我们需要绘制这样一个场景:透过红色的玻璃去看绿色的物体,那么可以先绘制绿色的物体,再绘制红色玻璃。在绘制红色玻璃的时候,利用“混合”功能,把将要绘制上去的红色和原来的绿色进行混合,于是得到一种新的颜色,看上去就好像玻璃是半透明的。
要使用OpenGL的混合功能,只需要调用:glEnable(GL_BLEND);即可。
要关闭OpenGL的混合功能,只需要调用:glDisable(GL_BLEND);即可。
注意:只有在RGBA模式下,才可以使用混合功能,颜色索引模式下是无法使用混合功能的。
一、源因子和目标因子
前面我们已经提到,混合需要把原来的颜色和将要画上去的颜色找出来,经过某种方式处理后得到一种新的颜色。这里把将要画上去的颜色称为“源颜色”,把原来的颜色称为“目标颜色”。
OpenGL 会把源颜色和目标颜色各自取出,并乘以一个系数(源颜色乘以的系数称为“源因子”,目标颜色乘以的系数称为“目标因子”),然后相加,这样就得到了新的颜 色。(也可以不是相加,新版本的OpenGL可以设置运算方式,包括加、减、取两者中较大的、取两者中较小的、逻辑运算等,但我们这里为了简单起见,不讨 论这个了)
下面用数学公式来表达一下这个运算方式。假设源颜色的四个分量(指红色,绿色,蓝色,alpha值)是 (Rs, Gs, Bs, As),目标颜色的四个分量是(Rd, Gd, Bd, Ad),又设源因子为(Sr, Sg, Sb, Sa),目标因子为 (Dr, Dg, Db, Da)。则混合产生的新颜色可以表示为:
(Rs*Sr+Rd*Dr, Gs*Sg+Gd*Dg, Bs*Sb+Bd*Db, As*Sa+Ad*Da)
当然了,如果颜色的某一分量超过了1.0,则它会被自动截取为1.0,不需要考虑越界的问题。
源因子和目标因子是可以通过glBlendFunc函数来进行设置的。glBlendFunc有两个参数,前者表示源因子,后者表示目标因子。这两个参数可以是多种值,下面介绍比较常用的几种。
GL_ZERO: 表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。
GL_ONE: 表示使用1.0作为因子,实际上相当于完全的使用了这种颜色参与混合运算。
GL_SRC_ALPHA:表示使用源颜色的alpha值来作为因子。
GL_DST_ALPHA:表示使用目标颜色的alpha值来作为因子。
GL_ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
GL_ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。
除 此以外,还有GL_SRC_COLOR(把源颜色的四个分量分别作为因子的四个分量)、GL_ONE_MINUS_SRC_COLOR、 GL_DST_COLOR、GL_ONE_MINUS_DST_COLOR等,前两个在OpenGL旧版本中只能用于设置目标因子,后两个在OpenGL 旧版本中只能用于设置源因子。新版本的OpenGL则没有这个限制,并且支持新的GL_CONST_COLOR(设定一种常数颜色,将其四个分量分别作为 因子的四个分量)、GL_ONE_MINUS_CONST_COLOR、GL_CONST_ALPHA、 GL_ONE_MINUS_CONST_ALPHA。另外还有GL_SRC_ALPHA_SATURATE。新版本的OpenGL还允许颜色的alpha 值和RGB值采用不同的混合因子。但这些都不是我们现在所需要了解的。毕竟这还是入门教材,不需要整得太复杂~
举例来说:
如果设置了glBlendFunc(GL_ONE, GL_ZERO);,则表示完全使用源颜色,完全不使用目标颜色,因此画面效果和不使用混合的时候一致(当然效率可能会低一点点)。如果没有设置源因子和目标因子,则默认情况就是这样的设置。
如果设置了glBlendFunc(GL_ZERO, GL_ONE);,则表示完全不使用源颜色,因此无论你想画什么,最后都不会被画上去了。(但这并不是说这样设置就没有用,有些时候可能有特殊用途)
如 果设置了glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);,则表示源颜色乘以自身的alpha 值,目标颜色乘以1.0减去源颜色的alpha值,这样一来,源颜色的alpha值越大,则产生的新颜色中源颜色所占比例就越大,而目标颜色所占比例则减 小。这种情况下,我们可以简单的将源颜色的alpha值理解为“不透明度”。这也是混合时最常用的方式。
如果设置了glBlendFunc(GL_ONE, GL_ONE);,则表示完全使用源颜色和目标颜色,最终的颜色实际上就是两种颜色的简单相加。例如红色(1, 0, 0)和绿色(0, 1, 0)相加得到(1, 1, 0),结果为黄色。
注意:
所 谓源颜色和目标颜色,是跟绘制的顺序有关的。假如先绘制了一个红色的物体,再在其上绘制绿色的物体。则绿色是源颜色,红色是目标颜色。如果顺序反过来,则 红色就是源颜色,绿色才是目标颜色。在绘制时,应该注意顺序,使得绘制的源颜色与设置的源因子对应,目标颜色与设置的目标因子对应。不要被混乱的顺序搞晕 了。
二、二维图形混合举例
下面看一个简单的例子,实现将两种不同的颜色混合在一起。为了便于观察,我们绘制两个矩形:glRectf(-1, -1, 0.5, 0.5);glRectf(-0.5, -0.5, 1, 1);,这两个矩形有一个重叠的区域,便于我们观察混合的效果。
先来看看使用glBlendFunc(GL_ONE, GL_ZERO);的,它的结果与不使用混合时相同。
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ZERO);
glColor4f(1, 0, 0, 0.5);
glRectf(-1, -1, 0.5, 0.5);
glColor4f(0, 1, 0, 0.5);
glRectf(-0.5, -0.5, 1, 1);
glutSwapBuffers();
}
尝 试把glBlendFunc的参数修改为glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);以及 glBlendFunc(GL_ONE, GL_ONE);,观察效果。第一种情况下,效果与没有使用混合时相同,后绘制的图形会覆盖先绘制的图形。第二 种情况下,alpha被当作“不透明度”,由于被设置为0.5,所以两个矩形看上去都是半透明的,乃至于看到黑色背景。第三种是将颜色相加,红色和绿色相 加得到黄色。
三、实现三维混合
也许你迫不及待的想要绘制一个三维的带有半透明物体的场景了。但是现在恐怕还不行,还有一点是在进行三维场景的混合时必须注意的,那就是深度缓冲。
深 度缓冲是这样一段数据,它记录了每一个像素距离观察者有多近。在启用深度缓冲测试的情况下,如果将要绘制的像素比原来的像素更近,则像素将被绘制。否则, 像素就会被忽略掉,不进行绘制。这在绘制不透明的物体时非常有用——不管是先绘制近的物体再绘制远的物体,还是先绘制远的物体再绘制近的物体,或者干脆以 混乱的顺序进行绘制,最后的显示结果总是近的物体遮住远的物体。
然而在你需要实现半透明效果时,发现一切都不是那么美好了。如果你绘制了一个近距离的半透明物体,则它在深度缓冲区内保留了一些信息,使得远处的物体将无法再被绘制出来。虽然半透明的物体仍然半透明,但透过它看到的却不是正确的内容了。
要 解决以上问题,需要在绘制半透明物体时将深度缓冲区设置为只读,这样一来,虽然半透明物体被绘制上去了,深度缓冲区还保持在原来的状态。如果再有一个物体 出现在半透明物体之后,在不透明物体之前,则它也可以被绘制(因为此时深度缓冲区中记录的是那个不透明物体的深度)。以后再要绘制不透明物体时,只需要再 将深度缓冲区设置为可读可写的形式即可。嗯?你问我怎么绘制一个一部分半透明一部分不透明的物体?这个好办,只需要把物体分为两个部分,一部分全是半透明 的,一部分全是不透明的,分别绘制就可以了。
即使使用了以上技巧,我们仍然不能随心所欲的按照混乱顺序来进行绘制。必须是先绘制不透明的物体,然 后绘制透明的物体。否则,假设背景为蓝色,近处一块红色玻璃,中间一个绿色物体。如果先绘制红色半透明玻璃的话,它先和蓝色背景进行混合,则以后绘制中间 的绿色物体时,想单独与红色玻璃混合已经不能实现了。
总结起来,绘制顺序就是:首先绘制所有不透明的物体。如果两个物体都是不透明的,则谁先谁后 都没有关系。然后,将深度缓冲区设置为只读。接下来,绘制所有半透明的物体。如果两个物体都是半透明的,则谁先谁后只需要根据自己的意愿(注意了,先绘制 的将成为“目标颜色”,后绘制的将成为“源颜色”,所以绘制的顺序将会对结果造成一些影响)。最后,将深度缓冲区设置为可读可写形式。
调用glDepthMask(GL_FALSE);可将深度缓冲区设置为只读形式。调用glDepthMask(GL_TRUE);可将深度缓冲区设置为可读可写形式。
一 些网上的教程,包括大名鼎鼎的NeHe教程,都在使用三维混合时直接将深度缓冲区禁用,即调用glDisable(GL_DEPTH_TEST);。这样 做并不正确。如果先绘制一个不透明的物体,再在其背后绘制半透明物体,本来后面的半透明物体将不会被显示(被不透明的物体遮住了),但如果禁用深度缓冲, 则它仍然将会显示,并进行混合。NeHe提到某些显卡在使用glDepthMask函数时可能存在一些问题,但可能是由于我的阅历有限,并没有发现这样的 情况。
那么,实际的演示一下吧。我们来绘制一些半透明和不透明的球体。假设有三个球体,一个红色不透明的,一个绿色半透明的,一个蓝色半透明的。红色最远,绿色 在中间,蓝色最近。根据前面所讲述的内容,红色不透明球体必须首先绘制,而绿色和蓝色则可以随意修改顺序。这里为了演示不注意设置深度缓冲的危害,我们故 意先绘制最近的蓝色球体,再绘制绿色球体。
为了让这些球体有一点立体感,我们使用光照。在(1, 1, -1)处设置一个白色的光源。代码如下:
void setLight(void)
{
static const GLfloat light_position[] = {1.0f, 1.0f, -1.0f, 1.0f};
static const GLfloat light_ambient[] = {0.2f, 0.2f, 0.2f, 1.0f};
static const GLfloat light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
static const GLfloat light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
}
每一个球体颜色不同。所以它们的材质也都不同。这里用一个函数来设置材质。
void setMatirial(const GLfloat mat_diffuse[4], GLfloat mat_shininess)
{
static const GLfloat mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f};
static const GLfloat mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, mat_shininess);
}
有了这两个函数,我们就可以根据前面的知识写出整个程序代码了。
坐标是可以设置的。OpenGL默认坐标系的确是楼上两位说的那样,但是我本人更习惯Z轴垂直显示器平面向内,所以把它修改掉了。
glOrtho(-1, 1, -1, 1, 1, -1); // 默认情形
glOrtho(-1, 1, -1, 1, -1, 1); // 我设置的情形
这里只给出了绘制的部分,其它部分大家可以自行完成。
void myDisplay(void)
{
// 定义一些材质颜色
const static GLfloat red_color[] = {1.0f, 0.0f, 0.0f, 1.0f};
const static GLfloat green_color[] = {0.0f, 1.0f, 0.0f, 0.3333f};
const static GLfloat blue_color[] = {0.0f, 0.0f, 1.0f, 0.5f};
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 启动混合并设置混合因子
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 设置光源
setLight();
// 以(0, 0, 0.5)为中心,绘制一个半径为.3的不透明红色球体(离观察者最远)
setMatirial(red_color, 30.0);
glPushMatrix();
glTranslatef(0.0f, 0.0f, 0.5f);
glutSolidSphere(0.3, 30, 30);
glPopMatrix();
// 下面将绘制半透明物体了,因此将深度缓冲设置为只读
glDepthMask(GL_FALSE);
// 以(0.2, 0, -0.5)为中心,绘制一个半径为.2的半透明蓝色球体(离观察者最近)
setMatirial(blue_color, 30.0);
glPushMatrix();
glTranslatef(0.2f, 0.0f, -0.5f);
glutSolidSphere(0.2, 30, 30);
glPopMatrix();
// 以(0.1, 0, 0)为中心,绘制一个半径为.15的半透明绿色球体(在前两个球体之间)
setMatirial(green_color, 30.0);
glPushMatrix();
glTranslatef(0.1, 0, 0);
glutSolidSphere(0.15, 30, 30);
glPopMatrix();
// 完成半透明物体的绘制,将深度缓冲区恢复为可读可写的形式
glDepthMask(GL_TRUE);
glutSwapBuffers();
}
大家也可以将上面两处glDepthMask删去,结果会看到最近的蓝色球虽然是半透明的,但它的背后直接就是红色球了,中间的绿色球没有被正确绘制。
小结:
本课介绍了OpenGL混合功能的相关知识。
混合就是在绘制时,不是直接把新的颜色覆盖在原来旧的颜色上,而是将新的颜色与旧的颜色经过一定的运算,从而产生新的颜色。新的颜色称为源颜色,原来旧的颜色称为目标颜色。传统意义上的混合,是将源颜色乘以源因子,目标颜色乘以目标因子,然后相加。
源 因子和目标因子是可以设置的。源因子和目标因子设置的不同直接导致混合结果的不同。将源颜色的alpha值作为源因子,用1.0减去源颜色alpha值作 为目标因子,是一种常用的方式。这时候,源颜色的alpha值相当于“不透明度”的作用。利用这一特点可以绘制出一些半透明的物体。
在进行混合时,绘制的顺序十分重要。因为在绘制时,正要绘制上去的是源颜色,原来存在的是目标颜色,因此先绘制的物体就成为目标颜色,后来绘制的则成为源颜色。绘制的顺序要考虑清楚,将目标颜色和设置的目标因子相对应,源颜色和设置的源因子相对应。
在进行三维混合时,不仅要考虑源因子和目标因子,还应该考虑深度缓冲区。必须先绘制所有不透明的物体,再绘制半透明的物体。在绘制半透明物体时前,还需要将深度缓冲区设置为只读形式,否则可能出现画面错误。
简单的透明
OpenGL中的绝大多数特效都和某些类型的(色彩)混合有关。
混色的定义为,将某个象素的颜色和已绘制在屏幕上和其对应的象素颜色相互结合。
至于怎么结合这两个颜色则依赖于颜色的alpha通道的分量值,及/或所使用的混色函数。
Alpha通常是位于颜色值末尾的第4个颜色组成分量。
前面这些课我们都是用GL_RGB来指定颜色的三个分量。
相应的GL_RGBA能指定alpha分量的值。
更进一步,我们能使用glColor4f()来代替glColor3f()。
绝大多数人都认为Alpha分量代表材料的透明度。
这就是说,alpha值为0.0时所代表的材料是完全透明的。
alpha值为1.0时所代表的材料则是完全不透明的。
混色的公式
若你对数学不感冒,而只想看看怎么实现透明,请跳过这一节。
若你想深入理解(色彩)混合的工作原理,这一节应该适合你吧。
『CKER的补充:其实并不难^-^。原文中的公式如下,CKER再唠叨一下吧。
其实混合的基本原理是就将要分色的图像各象素的颜色及背景颜色均按照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))
这个公式会生成透明/半透明的效果。
OpenGL中的混色
在OpenGL中实现混色的步骤类似于我们以前提到的OpenGL过程。
接着设置公式,并在绘制透明对象时关闭写深度缓存。
因为我们想在半透明的图像背后绘制 对象。
这不是正确的混色方法,但绝大多数时候这种做法在简单的项目中都工作的非常好。
Rui Martins 的补充: 正确的混色过程应该是先绘制全部的场景之后再绘制透明的图像。
并且要按照和深度缓存相反的次序来绘制(先画最远的物体)。
考虑对两个多边形(1和2)进行alpha混合,不同的绘制次序会得到不同的结果。
(这里假定多边形1离观察者最近,那么正确的过程应该先画多边形2,再画多边形1。
正如你再现实中所见到的那样,
从这两个<透明的>多边形背后照射来的光线总是先穿过多边形2,
再穿过多边形1,最后才到达观察者的眼睛。)
在深度缓存启用时,你应该将透明图像按照深度进行排序,
并在全部场景绘制完毕之后再绘制这些透明物体。否则你将得到不正确的结果。
我知道某些时候这样做是非常令人痛苦的,但这是正确的方法。
我们将使用第七课的代码。
一开始先在代码开始处增加两个新的变量。出于清晰起见,我重写了整段代码。
}
Var
h_RC : HGLRC; // Rendering Context(着色描述表)。
h_DC : HDC; // Device Context(设备描述表)
h_Wnd : HWND; // 窗口句柄
h_Instance : HINST; // 程式Instance(实例)。
keys : Array[0..255] Of Boolean; // 用于键盘例程的数组
light : Boolean; // 光源的开/关
blend : Boolean; // Blending OFF/ON? ( 新增 )
lp : Boolean; // L键按下了么?
fp : Boolean; // F键按下了么?
bp : Boolean; // B 键按下了么? ( 新增 )
xrot : GLfloat; // X 旋转
yrot : GLfloat; // Y 旋转
xspeed : GLfloat; // X 旋转速度
yspeed : GLfloat; // Y 旋转速度
z : GLfloat = -5.0 f; // 深入屏幕的距离
LightAmbient : Array[0..3] Of GLfloat = (0.5, 0.5, 0.5, 1.0); //环境光参数 ( 新增 )
LightDiffuse : Array[0..3] Of GLfloat = (1.0, 1.0, 1.0, 1.0); // 漫射光参数 ( 新增 )
LightPosition : Array[0..3] Of GLfloat = (0.0, 0.0, 2.0, 1.0); // 光源位置 ( 新增 )
filter : GLuint; // 滤波类型
texture : Array[0..2] Of GLuint; // 3种纹理的储存空间
Procedure glGenTextures(n: GLsizei; Var textures: GLuint); stdcall; external
opengl32;
Procedure glBindTexture(target: GLenum; texture: GLuint); stdcall; external
opengl32;
Function gluBuild2DMipmaps(target: GLenum; components, width, height: GLint;
format, atype: GLenum; data: Pointer): Integer; stdcall; external glu32 name
gluBuild2DMipmaps;
{
然后往下移动到 LoadGLTextures() 这里。
找到 if (TextureImage[0]=LoadBMP(Data/Crate.bmp))
这一行。我们目前使用有色玻璃纹理来代替上一课中的木箱纹理。
if (TextureImage[0]=LoadBMP("Data/glass.bmp")); // 载入玻璃位图 ( 已修改 )
}
Function LoadTexture: boolean; // 载入位图并转换成纹理
Var
Status : boolean; // Status 指示器
TextureImage : Array[0..1] Of PTAUX_RGBImageRec; // 创建纹理的存储空间
Begin
Status := false;
ZeroMemory(@TextureImage, sizeof(TextureImage)); // 将指针设为 NULL
TextureImage[0] := LoadBMP(Walls.bmp);
If TextureImage[0] <> Nil Then
Begin
Status := TRUE; // 将 Status 设为 TRUE
glGenTextures(1, texture[0]); // 创建纹理
// 创建 Nearest 滤波贴图
glBindTexture(GL_TEXTURE_2D, texture[0]);
// 生成纹理
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // ( 新增 )
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // ( 新增 )
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0].sizeX,
TextureImage[0].sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
TextureImage[0].data);
glBindTexture(GL_TEXTURE_2D, texture[1]); // 使用来自位图数据生成 的典型纹理
// 生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0].sizeX,
TextureImage[0].sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,
TextureImage[0].data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 线形滤波
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 线形滤波
// 创建 MipMapped 纹理
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_NEAREST); // ( 新增 )
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0].sizeX,
TextureImage[0].sizey, GL_RGB, GL_UNSIGNED_BYTE,
TextureImage[0].data); //(新增) }
End;
If assigned(TextureImage[0]) Then // 纹理是否存在
If assigned(TextureImage[0].data) Then // 纹理图像是否存在
TextureImage[0].data := Nil; // 释放纹理图像占用的内存
TextureImage[0] := Nil; // 释放图像结构
result := Status; // 返回 Status
End;
{
在glInit()代码段加入以下两行。
第一行以全亮度绘制此物体,并对其进行50%的alpha混合(半透明)。
当混合选项打开时,此物体将会产生50%的透明效果。
第二行设置所采用的混合类型。
Rui Martins 的补充:
alpha通道的值为 0.0意味着物体材质是完全透明的。
1.0 则意味着完全不透明。
}
End;
不过怎样才能在使用纹理贴图的时候指定混合时的颜色呢?非常简单,
在调整贴图模式时,文理贴图的每个象素点的颜色都是由alpha通道参数
和当前地象素颜色相乘所得到的。
比如,绘制的颜色是 (0.5, 0.6, 0.4),
我们会把颜色相乘得到(0.5, 0.6, 0.4, 0.2)
(alpha参数在没有指定时,缺省为零)。
就是如此!OpenGL实现Alpha混合的确非常简单!
}
{
原文注 (11/13/99)
我(NeHe)混色代码进行了修改,以使显示的物体看起来更逼真。
同时对源象素和目的象素使用alpha参数来混合,会导致物体的人造痕迹看起来非常明显。
会使得物体的背面沿着侧面的地方显得更暗。
基本上物体会看起来非常怪异。
我所用的混色方法也许不是最佳的,但的确能够工作。
启用光源之后,物体看起来非常逼真。
感谢Tom提供的原始代码,他采用的混色方法是正确的,
但物体看起来并不象所期望的那样吸引人:)
代码所作的再次修改是因为在某些显卡上glDepthMask()函数存在寻址问题。
这条命令在某些卡上启用或关闭深度缓冲测试时似乎不是非常有效,
所以我已将启用或关闭深度缓冲测试的代码转成老式的glEnable和glDisable。
纹理贴图的Alpha混合
用于纹理贴图的alpha参数能象颜色相同从问题贴图中读取。
方法如下,你需要在载入所需的材质同时取得其的alpha参数。
然后在调用glTexImage2D()时使用GL_RGBA的颜色格式。
}
直接重新弄
真专业啊
有骅冻干: AE的OpenGL问题一直都是一个很普遍的问题!这问题出现的原因一般有两种可能:第一,显卡驱动问题!第二:软件出错!(归根结底还是显卡问题,如果是专业显卡是绝对不会有问题的)因为我们普通的显卡有的也支持OpenGL加速,但不...
汉沽区15582478798: 网上下的3ds模型 添加OPENGL里面 没有材质 不显示颜色 怎么办 3dmax有了 不知道该怎么弄了 - ?
有骅冻干: 你的那个openGL问题解决了吗,我也遇到了差不多的问题,用3Dmax导出的3DS你应该在你的3dmax里给你的模型加上纹理:选中模型,“渲染”-
汉沽区15582478798: Opengl颜色问题 - ?
有骅冻干: void CCubeDlg::DrawCodn() { GLfloat fCurrentColor[4];// Get the current color glGetFloatv(GL_CURRENT_COLOR, fCurrentColor); glColor3f(255,0,0); glLineWidth(3.0); glBegin(GL_LINES); glVertex3i(290,0,0); glVertex3i(-290,0,0); glVertex3i(0,290...
汉沽区15582478798: 显示器玩低级游戏显示一般 - ?
有骅冻干: 放屁.DX对下兼容,根本不会出现因为DX的问题造成游戏不能玩.
汉沽区15582478798: OpenGL超级难的问题,新手也可以进来 - ?
有骅冻干: 是可以画出来,但是的视图矩阵是怎么设置的?可能是你的图形画在了屏幕之外.首先你要设置好模型视图矩阵,把矩阵移动到可视范围内,然后再绘制图形....
汉沽区15582478798: opengl问题 - ?
有骅冻干: glCreateShader(GL_FRAGMENT_SHADER) Access violation 之类的内存出错 是因为你没有在OpenGL初始化完全就是用了 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); GLuint fragmentShader = glCreateShader(GL_...
汉沽区15582478798: 关于OpenGL的问题,请教高手 - ?
有骅冻干: 我觉得是显卡设置的问题,重装驱动就可以解决,我相信楼主早就试过了.我觉得应该...
汉沽区15582478798: opengl 更改背景色问题 - ?
有骅冻干: 你的代码中对glBegin..glEnd的使用存在错误之处: glBegin..glEnd这段代码中,仅允许执行部分命令,他们是 glVertex,glColor,glSecondaryColor, glIndex,glNormal, glFogCoord,等等(完整列表参看: https://www.opengl.org/sdk/docs/man2/...
汉沽区15582478798: 关于opengl的问题. - ?
有骅冻干: 框架请重写Draw函数. 自己绘制框架... 回答你几个问题.. 框架可以不一样. 不一样的,地方是你的绘制方法不同,效果也不同.
汉沽区15582478798: 在VC++6.0中运行OPENGL出现这个问题:LINK : fatal error LNK1104: cannot open file "Glaux32.lib" - ?
有骅冻干: 或者你没有连接这个库到你的程序中,解决方法--在CPP中写#pragma comment(lib, "Glaux32.lib".或者你有这行代码,但没有被找到,这时将这个lib放到工程目录下.