绘制一个场景,设置场景中物体的颜色、材质和光照效果(双光源、镜面光斑)。
相关算法及原理描述:
1. 颜色生成原理
计算机颜色不同于绘画或印刷中的颜色,显示于计算机屏幕上每一个点的颜色都是由监视器内部的电子枪激发的三束不同颜色的光(红、绿、蓝)混合而成,因此,计算机颜色通 常用R(Red)、G(Green)、B(Blue)三个值来表示,这三个值又称为颜色分量。
2 .RGB颜色立方体
所有监视器屏幕的颜色都属于RGB颜色空间,如果用一个立方体形象地表示RGB颜色组成关系,那么就称这个立方体为RGB色立体。
3.颜色模式
OpenGL颜色模式一共有两个:RGB(RGBA)模式和颜色表模式。在RGB模式下,所有的颜色定义全用R、G、B三个值来表示,有时也加上Alpha值(与透明度有关),即RGBA模式。而在颜色表模式下,每一个象素的颜色是用颜色表中的某个颜色索引值表示,而这个索引值指向了相应的R、G、B值。这样的一个表成为颜色映射(Color Map)。
3.1RGB模式
在RGBA模式下,可以用glColor*()来定义当前颜色。其函数形式为:
void glColor
voidglColor
第一种函数表示形式中的x表示参数的数目,当它等于3的时候,三个参数分别代表R、G、B值,alpha值缺省为1.0;当它等于4的时候,还包括了Alpha值,其范围从0.0到1.0。函数名中的t指定参数数据的类型,可以取b、d、f、i、s、ub、ui或us,它们分别代表字节型、双精度型、浮点型、整型、短整型、无符号字节型和无符号短整型。
第二种函数表示形式中的后缀v,表示用一个数组来放置这些参数。
常用的指定颜色的函数是glColor3f,其中每个颜色分量的值在[0.0,1.0]范围内。
还有一个函数glColor3ub,这个版本使用的颜色分量的取值范围是0到255之间的无符号字节,与Windows的RGB宏指定颜色的方法类似:
glColor3ub( 128,128,128) =RGB( 128,128,128)
3.2颜色表模式(Color_Index Mode)
采用颜色表模式,可以同时显示的颜色由颜色映射的尺寸和位平面数目确定。若颜色映射的尺寸为2n,位平面数为m,则可用颜色为min(2n,2m)
在颜色表模式下,可以调用glIndex*()函数从颜色表中选取当前颜色。其函数形式为:
void glIndex{sifd}(TYPE c);
void glIndex{sifd}v(TYPE *c);
其中,参数值c用于设置当前颜色索引值,即调色板号,若值大于颜色位面数时则取模。
4.光照模型
4.1简单光照模型
当光照射到一个物体表面上时,会出现三种情形。首先,光可以通过物体表面向空间反射,产生反射光;其次,对于透明物体,光可以穿透该物体并从另一端射出,产生透射光;最后,部分光被物体表面吸收而转换成热。在上述三部分光中,仅仅是透射光和反射光能够进入人眼产生视觉效果。此外,物体本身还有可能发光,比如发光的灯泡。这里暂时不考虑透明物体,这样场景中可能存在以下几种类型的光,即环境光、漫射光、镜面光和辐射光。
Phong光照明模型的综合表述:由物体表面上一点P反射到视点的光强I为环境光的反射光强Ie、理想漫反射光强Id和镜面反射光Is的总和。
4.2创建光源
OpenGL可提供8个独立的光源,它们可以放在场景中的任何地方,也可放在视见空间之外。当将光源放在无穷远处就可以得到平行的光线。
光源有许多特性,如颜色、位置、方向等。选择不同的特性值,则对应的光源作用在物体上的效果也不一样。
下面详细讲述定义光源特性的函数glLight*():
void glLight{if}[v](GLenum light , GLenum pname, TYPE param)
创建具有某种特性的光源。其中第一个参数light指定所创建的光源号,如GL_LIGHT0、GL_LIGHT1、...、GL_LIGHT7。第二个参数pname指定光源特性,这个参数的辅助信息见后表所示。最后一个参数设置相应的光源特性值。
漫反射
GLfloat light0_diffuse[]= { 0.0f, 0.0f, 1.0f, 1.0f};
glLightfv(GL_LIGHT0,GL_DIFFUSE, light0_diffuse);
函数glLightfv函数的第一个参数是一个符号常数,指定设置的是哪个光源的参数。第二个参数可以选择GL_DIFFUSE、GL_AMBIENT、GL_SPECULAR,分别用于指定漫射光、环境光和镜面光成分。第三个参数是一个包含四个浮点数的数组,用于指定相应的参数。这段代码指定的光源LIGHT0只包含了漫射光成分,是一个漫反射光源。
设置光源位置
指定了光源成分之后,需要指定光源的位置,如下面一段代码:
GLfloat light0_position[] = {1.0f, 1.0f, 1.0f, 0.0f };
glLightfv(GL_LIGHT0,GL_POSITION,light0_position);
这段代码指定了光源LIGHT0所在的位置,指定光源位置参数的数组light0_position包含四个值,其中最后一个值为1.0时,则表明指定的坐标就是光源的位置。如果最后一个值是0.0,则表明沿数组指定的矢量方向的光源位于无限远的地方,即光源发出的光是平行光。
聚光源
光源可以定义成聚光灯形式,即将光的形状限制在一个圆锥内。OpenGL中聚光的定义包括以下几步:
(1) 定义聚光源位置
(2) 定义聚光截止角
(3) 定义聚光方向
(4) 定义聚光指数
衰减因子
OpenGL的光衰减是通过光源的发光量乘以衰减因子计算出来的。常用的衰减因子有常值因子(GL_CONSTANT_ATTENUATION)、线形因子(GL_LINEAR_ATTENUATION)和二次因子(GL_QUADRATIC_ATTENUATION),其中缺省的常数衰减因子是1.0,其余两个因子都是0.0。
用户也可以自己定义这些值,如:
glLightf(GL_LIGHT0,GL_CONSTANT_ATTENUATION, 2.0);
glLightf(GL_LIGHT0,GL_LINEAR_ATTENUATION, 1.0);
glLightf(GL_LIGHT0,GL_QUADRATIC_ATTENUATION, 0.5);
启动光照
让OpenGL进行光照计算,必须明确指出光照是否有效或无效。如果光照无效,OpenGL只是简单地将当前颜色映射到当前顶点上去,而不进行法向、光源、材质等复杂计算。要使光照有效,首先得启动光照,即:
glEnable(GL_LIGHTING);
若使光照无效,则调用
gDisable(GL_LIGHTING)
可关闭当前光照。
明暗处理
在计算机图形学中,光滑的曲面表面常用多边形予以逼近和表示,而每个小多边形轮廓(或内部)就用单一的颜色或许多不同的颜色来勾画(或填充),这种处理方式就称为明暗处理。在OpenGL中,用单一颜色处理的称为平面明暗处理(Flat Shading),用许多不同颜色处理的称为光滑明暗处理(Smooth Shading),也称为Gourand明暗处理(Gourand Shading)。设置明暗处理模式的函数为:
void glShadeModel(GLenum mode);
函数参数为GL_FLAT或GL_SMOOTH,分别表示平面明暗处理和光滑明暗处理。
用平面明暗处理模式时,多边形内每个点的法向一致,且颜色也一致;应用光滑明暗处理模式时,多边形所有点的法向是由内插生成的,具有一定的连续性,因此每个点的颜色也相应内插,故呈现不同色。这种模式下,插值方法采用的是双线性插值法,如图所示。
4.3材质属性
设置材质属性
材质的定义与光源的定义类似。其函数为:
void glMaterial{if}[v](GLenum face,GLenum pname,TYPE param);
定义光照计算中用到的当前材质。face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明当前材质应该应用到物体的哪一个面上;pname说明一个特定的材质;param是材质的具体数值,若函数为向量形式,则param是一组值的指针。
下面的代码指定了后面绘制的多边形都具有相同的材质属性:
GLfloatmaterial_ambiemt[]={0.75f, 0.75f, 0.75f, 1.0f};
glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE, material_ambiemt);
glMaterialfv函数的第一个参数指定多边形的前面(GL_FRONT),背面(GL_BACK)或者两面(GL_FRONT_AND_BACK)具有指定的材质属性。第二个参数则指定当前设置的是哪一种材质属性,这里使用GL_AMBIENT_AND_DIFFUSE,即设置环境光和漫反射光的值。最后一个参数是一个数组,它包含了构成属性的RGBA值。
镜面光斑
在定义了光源中的镜面光成分之后(函数glLightfv函数的第二个参数设置成GL_SPECULAR ),物体上并不会产生镜面光斑的效果,还需要定义材质的镜面反射率和镜面指数。
(1) 镜面反射率
镜面反射率决定了材质表面对于镜面光反射的成分,如下面的代码:
GLfloat mat_specular[] = {1.0f, 1.0f, 1.0f, 1.0f };
glMaterialfv(GL_FRONT,GL_SPECULAR, mat_specular);
指定了其后绘制的表面几乎反射了所有的入射镜面光。
(2)镜面指数
镜面指数说明如何确定镜面光亮斑的大小和聚光程度。值为0时指定一个没有焦点的镜面光亮斑,它能够对整个多边形的颜色均匀加亮。如果增大这个值,可以减少镜面光亮斑的尺寸、增加聚焦度,就出现了闪亮的光斑,该参数值的范围在[1,128]之间。
下面的代码指定材质的镜面指数。
glMaterialfv(GL_FRONT,GL_SHININESS, 50);
辐射光
辐射光可以使物体看起来发出某种颜色的光(自发光),通过给GL_EMISSION定义一个RGBA值,可以达到这种特殊效果。可以利用这一特性来模拟灯和其他光源。代码如下:
GLfloat mat_emission[]={0.3f, 0.3f, 0.5f, 0.0f};
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
这样,物体看起来稍微有点发光。比如绘制一个打开的台灯,就可以将一个小球的材质定义成上述形式,并且在小球内部建立一个聚光源,这样台灯的灯泡效果就出来了。
程序运用矩阵堆栈多次调用glMaterialfv()来设置每个球的材质,建议最好尽可能少的改变材质,以减少改变材质时所带来的性能开销。更常用的设置材质属性的方法是使用颜色跟踪法。相应函数为glColorMaterial(),说明如下:
void glColorMaterial(GLenumface,GLenum mode);
参数face指定面,值有GL_FRONT、GL_BACK或GL_FRONT_AND_BACK(缺省值)。
mode指定材质成分,值有GL_AMBIENT、GL_DIFFUSE、GL_AMBIENT_AND_DIFFUSE(缺省值)、GL_SPECULAR或GLEMISSION。
在调用glColorMterial()以后,必须调用带有参数GL_COLOR_MATERIAL的glEnable函数来启动颜色材质,然后通过调用glColor就可以设置材质颜色属性,或用glMaterial()来改变材质成分。当不用这种方式来改变材质时,可调用glDisable(GL_COLOR_MATERIAL)来关闭取消。
示例:颜色定义改变材质
材质RGB值和光源RGB值的关系
在进行光照计算时,物体的最终颜色是由其材质属性的RGB值和光照属性的RGB值共同决定的。也就是说,若OpenGL的光源颜色为(LR、LG、LB),材质颜色为(MR、MG、MB),那么,在忽略所有其他反射效果的情况下,最终到达眼睛的光的颜色为(LR*MR、LG*MG、LB*MB), 即将每个环境光源的成分与材质的环境反射率相乘。
例如,如果当前的环境光源的RGB值为(0.5,1.0,0.5),而物体的材质的环境反射成分的RGB值为(0.5,0.5,0.5),那么物体最终的颜色为:
(0.5×0.5,1.0×0.5,0.5×0.5)=(0.25,0.5,0.25)
同样,如果有两束光,相应的值分别为(R1、G1、B1)和(R2、G2、B2),则OpenGL将各个颜色成分相加,得到(R1+R2、G1+G2、B1+B2),即当叠加的RGB中任何一个颜色分量的值大于1.0,那么就用1.0计算。
设置光照模型
允许进行光照计算后,需要设置光照模型,影响光照模型的三个参数是通过glLightModel函数中设置的。
//设置光照
glEnable(GL_LIGHTING);
GLfloat ambient[]={0.8f, 0.8f, 0.8f, 1.0f};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
在上面的示例中,使用了参数GL_LIGHT_MODEL_AMBIENT,这个参数允许指定当前场景的全局环境光,它可以从各边均匀照射所有的物体。第二个参数为一个储存RGBA值的数组。注意,默认的全局环境光的RGBA值为(0.2,0.2,0.2,1.0),光线相当黯淡的。
双面光照
光照计算通常是对所有多边形进行的,无论其是正面或反面。一般情况下,设置光照条件时总是正面多边形,因此不能对背面多边形进行正确地光照。对于一个封闭的物体,只有正面多边形能看到,这种情况下不必考虑背面光照。而如果物体不封闭,其内部的曲面是可见的,那么对内部多边形需进行光照计算,这时应该调用如下函数启动双面光照:
glLightModeli(LIGHT_MODEL_TWO_SIDE, GL_TRUE);
要关闭双面光照,只需将参数GL_TRUE改为GL_FALSE即可。