目 录 14.1
光照模型 14.2
光源位置与衰减 14.3
聚光和多光源 14.4
光源位置与方向的控制 14.5
辐射光
本章内容是基础篇第七章的补充和提高。这一章主要讲述一些特殊光效果处理,如全局环境光、双面光照、光的衰减、聚光、多光源、光源位置的改变等等。读者若有兴趣,可以按照本章例程的方法作出许多变换,则会出现意想不到的效果,充分发挥你的艺术才能。
14.1、光照模型 OpenGL光照模型的概念由一下三部分组成:1)全局泛光强度、2)视点位置在景物附近还是在无穷远处、3)物体的正面和背面是否分别进行光照计算。
14.1.1 全局环境光 正如前面基础篇中所提到的一样,每个光源都能对一个场景提供环境光。此外,还有一个环境光,它不来自任何特定的光源,即称为全局环境光。下面用参数GL_LIGHT_MODEL_AMBIENT来说明全局环境光的RGBA强度:
GLfloat lmodel_ambient[]={0.2,0.2,0.2,1.0};
glLightModelfv(GLLIGHT_MODEL_AMBIENT,lmodel_ambient);
在这个例子中,lmodel_ambient所用的值为GL_LIGHT_MODEL_AMBIENT的缺省值。这些数值产生小量的白色环境光。
14.1.2 近视点与无穷远视点
视点位置能影响镜面高光的计算,也就是说,顶点的高光强度依赖于顶点法向量,从顶点到光源的方向和从顶点到视点的方向。实际上,调用光照函数并不能移动视点。但是可以对光照计算作出不同的假定,这样视点似乎移动了。对于一个无穷远视点,视点到任何顶点的方向保持固定,缺省时为无穷远视点。对于一个近视点,物体每个顶点到视点的方向是不同的,需要逐个计算,从而整体性能降低,但效果更真实。下面一句函数代码是假定为近视 点:
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);
这个调用把视点放在视点坐标系的原点处。若要切换到无穷远视点,只需将参数GL_TRUE 改为GL_FALSE即可。
14.1.3 双面光照
光照计算通常是对所有多边形进行的,无论其是正面或反面。一般情况下,设置光照条件时总是正面多边形,因此不能对背面多边形进行正确地光照。在基础篇的第七章中的例子里,物体是一个球,只有正面多边形能看到,即球的外部可见,这种情况下不必考虑背面光照。若球被砍开,其内部的曲面是可见的,那么对内部多边形需进行光照计算,这时应该调用如下函数:
glLightModeli(LIGHT_MODEL_TWO_SIDE,GL_TRUE);
启动双面光照。实际上,这就是OpenGL给背面多边形定义一个相反的法向量(相对于正面多边形而言)。一般来说,这意味着可见正面多边形和可见反面多边形的法向量都面朝观察者,而不是向里,这样所有多边形都能进行正确的光照。关闭双面光照,只需将参数GL_TRUE改为GL_FALSE即可。
14.2、光源位置与衰减
在基础篇中已经提到过,光源有无穷远光源和近光源两种形式。无穷远光源又称作定向光源,即这种光到达物体时是平行光,例如现实生活中的太阳光。近光源又称作定位光源,光源在场景中的位置影响场景的光照效果,尤其影响光到达物体的方向。台灯是定位光源的范例。在以前所有与光照有关的例子里都采用的是定向光源,如:
GLfloat light_position[]={1.0,1.0,1.0,0.0};
glLightfv(GL_LIGHT0,GL_POSITION,light_position);
光源位置坐标采用的齐次坐标(x, y, z, w),这里的w为0,所以相应的光源是定向光,(x, y, z)描述光源的方向,这个方向也要进行模型变换。GL_POSITION的缺省值是(0.0, 0.0, 1.0, 0.0),它定义了一个方向指向Z负轴的平行光源。若w非零,光源为定位光源。(x, y, z, w)指定光源在齐次坐标系下的具体位置,这个位置经过模型变换等在视点坐标系下保存下来。
真实的光,离光源越远则光强越小。因为定向光源是无穷远光源,因此距离的变换对光强的影响几乎没有,所以定向光没有衰减,而定位光有衰减。OpenGL的光衰减是通过光源的发光量乘以衰减因子计算出来的。其中衰减因子在第十章表10-2中说明过。缺省状态下,常数衰减因子是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);
注意:环境光、漫反射光和镜面光的强度都衰减,只有辐射光和全局环境光的强度不衰减。
14.3、聚光和多光源
14.3.1 聚光
定位光源可以定义成聚光灯形式,即将光的形状限制在一个圆锥内。OpenGL中聚光的定义有以下几步:
1)定义聚光源位置。因为聚光源也是定向光源,所以他的位置同一般定向光一样。如:
GLfloat light_position[]={1.0,1.0,1.0,1.0};
glLightfv(GL_LIGHT0,LIGHT_POSITION,light_position);
2)定义聚光截止角。参数GL_SPOT_CUTOFF给定光锥的轴与中心线的夹角,也可说成是光锥顶角的一半,如图14-1所示。缺省时,这个参数为180.0,即顶角为360度,光向所有的方向发射,因此聚光关闭。一般在聚光启动情况下,聚光截止角限制在[0.0, 90.0] 之间,如下面一行代码设置截止角为45度:
glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,45.0);
3)定义聚光方向。聚光方向决定光锥的轴,它齐次坐标定义,其缺省值为(0.0, 0.0, -1.0),即指向Z负轴。聚光方向也要进行几何变换,其结果保存在视点坐标系中。它的定义如下:
GLfloat spot_direction[]={-1.0,-1.0,0.0};
glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,spot_direction);
4)定义聚光指数。参数GL_SPOT_EXPONENT控制光的集中程度,光锥中心的光强最大,越靠边的光强越小,缺省时为0。如:
glLightf(GL_LIGHT0,GL_SPOT_EXPONENT,2.0);
此外,除了定义聚光指数控制光锥内光强的分布,还可利用上一节所讲的衰减因子的设置来实现,因为衰减因子与光强相乘得最终光强值。
14.3.2 多光源及例程
在前面已提到过场景中最多可以设置八个光源(根据OpenGL的具体实现也许会更多一些)。在多光源情况下,OpenGL需计算每个顶点从每个光源接受的光强,这样会增加计算量,降低性能,因此在实时仿真中最好尽可能地减少光源数目。在前面都已讨论过八个光源的常数:GL_LIGHT0、GL_LIGHT1、…、LIGHT7,注意:GL_LIGHT0的参数缺省值与其它光源的参数缺省值不同。下面这段代码定义了红色的聚光:
GLfloat light1_ambient[]= { 0.2, 0.2, 0.2, 1.0 };
GLfloat light1_diffuse[]= { 1.0, 0.0, 0.0, 1.0 };
GLfloat light1_specular[] = { 1.0, 0.6, 0.6, 1.0 };
GLfloat light1_position[] = { -3.0, -3.0, 3.0, 1.0 };
GLfloat spot_direction[]={ 1.0,1.0,-1.0};
glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
glLightfv(GL_LIGHT1, GL_SPECULAR,light1_specular);
glLightfv(GL_LIGHT1, GL_POSITION,light1_position);
glLightf (GL_LIGHT1, GL_SPOT_CUTOFF, 30.0);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION,spot_direction);
glEnable(GL_LIGHT1);
// 这段代码描述的是一个红色的立方体,用它来代表所定义的聚光光源 //
// 可加到程序的函数display()中去。 //
glPushMatrix();
glTranslated (-3.0, -3.0, 3.0);
glDisable (GL_LIGHTING);
glColor3f (1.0, 0.0, 0.0);
auxWireCube (0.1);
glEnable (GL_LIGHTING);
glPopMatrix ();
//////////////////////////////////////////////////////////////////
将这些代码加到基础篇第十章光照的例程(light2.c)中去:
例14-1 聚光和多光源运用例程(spmulght.c)
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
void CALLBACK display(void);
/* 初始化光源、材质等 */
void myinit(void)
{
GLfloat mat_ambient[]= { 0.2, 0.2, 0.2, 1.0 };
GLfloat mat_diffuse[]= { 0.8, 0.8, 0.8, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat light0_diffuse[]= { 0.0, 0.0, 1.0, 1.0};
GLfloat light0_position[] = { 1.0, 1.0, 1.0, 0.0 };
GLfloat light1_ambient[]= { 0.2, 0.2, 0.2, 1.0 };
GLfloat light1_diffuse[]= { 1.0, 0.0, 0.0, 1.0 };
GLfloat light1_specular[] = { 1.0, 0.6, 0.6, 1.0 };
GLfloat light1_position[] = { -3.0, -3.0, 3.0, 1.0 };
GLfloat spot_direction[]={ 1.0,1.0,-1.0};
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS,mat_shininess);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION,light0_position);
glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);
glLightfv(GL_LIGHT1, GL_SPECULAR,light1_specular);
glLightfv(GL_LIGHT1, GL_POSITION,light1_position);
glLightf (GL_LIGHT1, GL_SPOT_CUTOFF, 30.0);
glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION,spot_direction);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslated (-3.0, -3.0, 3.0);
glDisable (GL_LIGHTING);
glColor3f (1.0, 0.0, 0.0);
auxWireCube (0.1);
glEnable (GL_LIGHTING);
glPopMatrix ();
auxSolidSphere(2.0);
glFlush();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-5.5, 5.5, -5.5*(GLfloat)h/(GLfloat)w, 5.5*(GLfloat)h/(GLfloat)w,
-10.0, 10.0);
else
glOrtho (-5.5*(GLfloat)w/(GLfloat)h, 5.5*(GLfloat)w/(GLfloat)h,
-5.5, 5.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Spotlight and Multi_lights ");
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是球被两个光源照射,一个是蓝色的定向光,另一个红色的是聚光。
|
图14-1 聚光与多光源 | 14.4、光源位置与方向的控制 OpenGL光源的位置和方向与其它几何图元的位置和方向一样都必须经过变换矩阵的作用。尤其是当用glLight*()说明光源的位置和方向时,位置和方向都要经过当前变换矩阵的作用并保存在视点坐标系中,注意投影矩阵变换对它们不起作用。OpenGL可通过调整光源函数和视点变换函数在程序中的相对位置来获得三种不同的效果:1)光源位置保持固定、2)光源绕静止物体移动、3)光源随视点移动。 在第一种情况下,为获得光源位置固定的效果,必须在所有视点和模型变换之后设置光源位置。第二种情况下,有两种方法可以达到这种效果,一种是在模型变换后设置光源位置,变换的改变将修改光源位置;另一种是使物体和视点绕光源移动,相对地光源就能绕物体移动了。第三种情况下,要建立一个沿视线移动的光源,必须在视点变换之前设置光源位置,则视点变换按同样的方式影响光源和视点。下面有一个光源移动的例子:
例14-1 光源移动例程(mvlight.c) | | |
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
void myinit(void);
void CALLBACK movelight (AUX_EVENTREC *event);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
static int step = 0;
void CALLBACK movelight (AUX_EVENTREC *event)
{
step = (step + 15) % 360;
}
void myinit (void)
{
GLfloat mat_diffuse[]={0.0,0.5,1.0,1.0};
GLfloat mat_ambient[]={0.0,0.2,1.0,1.0};
GLfloat light_diffuse[]={1.0,1.0,1.0,1.0};
GLfloat light_ambient[]={0.0,0.5,0.5,1.0};
glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,mat_diffuse);
glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
}
void CALLBACK display(void)
{
GLfloat position[] = { 0.0, 0.0, 1.5, 1.0 };
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix ();
glTranslatef (0.0, 0.0, -5.0);
glPushMatrix ();
glRotated ((GLdouble) step, -1.0, 1.0, 1.0);
glRotated (0.0, 1.0, 0.0, 0.0);
glLightfv (GL_LIGHT0, GL_POSITION, position);
glTranslated (0.0, 0.0, 1.5);
glDisable (GL_LIGHTING);
glColor3f (1.0, 1.0, 0.0);
auxWireSphere (0.1);
glEnable (GL_LIGHTING);
glPopMatrix ();
auxSolidTorus (0.275, 0.85);
glPopMatrix ();
glFlush ();
}
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(40.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
}
void main(void)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow ("Moving Light");
myinit();
auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, movelight);
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
以上程序运行结果是在屏幕中央显示一个蓝色的环形圈,按下鼠标左键则可移动光源,其中一个黄色的小球代表光源。
|
图14-2 光源移动 |
14.5、辐射光
在前面的章节中已经应用了辐射光,可以参见10.4.4材质改变的例程(chgmat1.c)的运行效果。这里再一次强调提出,通过给GL_EMISSION定义一个RGBA值,可以使物体看起来象发出这种 颜色的光一样,以达到某种特殊效果。实际上,现实生活中的物体除光源外都不发光,但我们可以利用这一特性来模拟灯和其他光源。代码举例如下:
GLfloat mat_emission[]={0.3,0.3,0.5,0.0};
glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission);
这样,物体看起来稍微有点发光。比如绘制一个打开的台灯,就可以将一个小球的材质定义成上述形式,并且在小球内部建立一个聚光源,这样台灯的灯泡效果就出来了。