第6章 光照
学习目的: 了解OpenGL如何模拟现实的光照效果 定义期望的光源和光照模型 定义被照物体的材质 利用矩阵堆栈控制光源位置
我们看到的任何物体的样子是由其自身特性和光照共同决定的。想想在风和日丽的晴天和在乌云密布的阴天看到的大海是多么的不同,一个宛如明丽的蓝宝石,另一个却只是晦暗的灰色。实际上如果没有光照,大部分物体看起来根本不是三维的。入射光和物体对光的吸收和反射决定了你所看到的景色,这也就是OpenGL对光照计算的部分依据。图6-1是一个球在有光照和无光照情况下的样子。
图6-1 有光照和无光照球面的比较
看我又说对了吧,没有光照的球面看起来和一个二维的圆盘没有区别,从而可见光照和物体的相互作用在三维影像中是多么重要。
利用OpenGL,你可以通过调整光照和物体在场景中的关系获得各种各样的效果。这是本章将着重讨论的内容,包括OpenGL的光照模型以及众多的参数。本章末尾将从数学上介绍光照对颜色的影响问题。
本章可分为如下几部分: “现实世界与OpenGL光照” “简例:渲染一个球体” “创建光源” “选择光照模型” “定义材质特性” “光照中的数学” “颜色索引模式下的光照”
§6.1 现实世界与OpenGL光照
正如前面所讲的,最终的光照效果是光线与表面共同作用的结果。
在OpenGL中,光线被假定成红绿蓝三种原色光的组合,因此,光源的颜色就由它所发出的这三种原色光的含量决定,而物理表面的颜色则由其反射的这三种光的含量决定。OpenGL的光照模型尽管只是一个近似,但它很管用且计算速度很快。如果你需要一个更精确的模型,那么只好自己编程计算了,不过模型越精确则计算速度越慢。
在场景中,OpenGL可以放置若干个光源,每个光源都可以被单独地打开或关闭。照在物体上的光线有些是直接来自光源,而另一些是光源的光经过若干次反射而来。这些多次反射的光被称为环境光(Ambient Light),它们已经变得很“散”,以致于无法确定其初始方向,不过当某个光源(产生它们的光源)被关闭后,它们也就消失了。
不同的物理表面对光线有不同的反射特性,那些通常看起来很光泽的表面能够将入射光很好地向某一特定方向反射,而另外一些则将入射光均衡地散射向各个方向。大部分表面的特性是介于这两者之间的。当然有些表面也会自己发光,比如汽车前灯。
§6.1.1 光照分量
OpenGL对光照效果的计算是由四个独立的部分叠加而成的,它们是:出射光[自发光](emitted)、环境光(ambient)、漫反射光(diffuse)、镜面光(specular)。
出射光是最简单的,它从物体发出且不受任何其它光源的影响。
环境光从光源发出并经过多次反射形成的,它均匀从周围环境入射至物体表面并朝各个方向等量反射。
漫反射光是直接从特定光源入射并朝各个方向等量反射的光,入射角越小它看起来越亮,同时从各个角度来看它的亮度是一样的。
镜面光也是直接从特定光源入射的,但在反射时只朝特定的方向反射(遵从镜面反射定理)。一束平行激光在高质量的镜面上可以几乎被100%地反射。表面光泽的金属或塑料都有很高的镜面反射成份,而象粉笔或地毯之类的光泽度差的物品则几乎没有。
尽管同一光源出射的光的频率分布是一定的,但照在不同的表面上产生的环境光、漫反射光、镜面光分量却可能不同。比如白光照在房间的红色墙壁上,被散射的光就会趋于红色。OpenGL允许对各个反射光分量的RGB值分别作独立的调整。
§6.1.2 材质颜色
OpenGL中近似认为材质的颜色由它的反射光中的红绿蓝三色所占的百分比决定。比如:一个理想的红色球将反射入射的所有红光并吸收所有绿色和蓝色光。这样的球在白光(含红绿蓝三色光)和纯红光的照射下的表现是一致的--都是红色球,然而在不含红光成份的光(如纯绿光)的照射下则呈现为黑色(因为没有光被反射)。
与光线对应,材质有独立的环境反射、漫反射和镜面反射颜色成份,分别决定了材质对环境光、漫反射光和镜面光的反射能力。环境反射和漫反射决定了物体的颜色,这两种成份很相象,甚至可以当作一种来处理。镜面反射色通常为白色或灰色,因此镜面反射的高光(highlights)通常是光源的镜面光成份的颜色和强度决定。想象一束白光照射在一个有光泽的红色金属球上,球体的大部分将表现为红色,而高光处则为白色。
§6.1.3 光线与材质的RGB值
光线颜色的RGB值定义于材质的RGB值定义有所不同。
对光线来说,RGB值的大小对应于该色光强度与其最高强度的比例百分数。比如一束光的RGB都是1.0,那么它就是最强的白光;如果这些值都为0.5,则整束光的颜色还是白色,只不过强度减半,看起来是灰色。如果R=G=1.0而B=0.0,那么就是最强的黄光。
对材质来说,RGB值的大小对应于材质对该色光的反射比例百分数。比如某材质的R=1.0,G=0.5,B=0.0,则它反射入射的所有红光,一半的绿光,不反射蓝光。换句话来说,如果一束光的RGB值为(LR, LG, LB),被照材质的RGB值为(MR, MG, MB),则在不考虑其它反射效果的情况下,眼睛所看到的颜色由(LR*MR, LG*MG, LB*MB)决定。
类似地,如果到达眼睛的两束光的RGB成份分别为(R1, G1, B1)和(R2, B2, G2),则OpenGL将相应的成份相加,得到(R1+R2, G1+G2, B1+B2)。如果任何一个相加结果大于1(表明这样的光强是显示设备不能实现的),则被认为是1。
§6.2 简例:渲染一个球体
向场景中加入光照的步骤如下:
1. 定义所有物体每个顶点的法向量方向。这些法向量决定了物体表面与光源的相对取向。
2. 建立、选择并放置一个或多个光源。
3. 建立并选择一个光照模型,它决定了全局环境光照的程度以及视点的有效位置(用于光照计算)。
4. 定义场景中物体的材质。
例6-1将显示一个在独立光源照射下的球体,如前面的图6-1所示。
例6-1 :光照下的球体:
#include <GL/gl.h>
#include <GL/glu.h>
#include "aux.h"
void myinit(void)
{
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auxSolidSphere(1.0);
glFlush();
}
void myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
int main(int argc, char** argv)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH);
auxInitPosition (0, 0, 500, 500);
auxInitWindow (argv[0]);
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
与光照有关的调用写在函数myinit()中,下面我们将会简单地讨论它们的用法,在后面的章节中还会仔细研究。在例6-1中需要注意的一点是,它使用了RGBA颜色模式,而不是颜色索引模式(color-index)。OpenGL对这两种模型的光照计算是不同的,而且实际上颜色索引模式的光照表现力是很有限的,因此RGBA是比较受推崇的光照模式,本章中的所有例子都使用该模式。需要对颜色索引模式作进一步了解的请参阅“颜色索引模式中的光照”一节。
场景中物体的材质特性决定了光线是如何被反射的从而表现出它是用什么材料做的。因为入射光线与物体材质表面的作用相当复杂,所以通过设置物体的材质特性而使它“看起来象那么会事”就是一件很技术的活了。你需要制定一种材质的环境光反射色、漫射光反射色、镜面光反射色(这里说XX反射色的意思是指对某种光的某个颜色分量的反射能力)以及它的光泽度。在这个例子中,只有最后两个特性--镜面光反射颜色和光泽度被明确地指定(通过使用glMaterialfv()函数)。在“定义材质特性”一节中会描述所有的材质特性参数并给出例子。
重点注意:
在写你自己的光照程序时,记住可以修改一些参数值,而另一些参数使用其缺省值。当然,不要忘记使你定义的光源生效(enable之),同时允许光照计算。最后,记住你可以使用显示列表以提高在改变光源位置时的计算效率(参阅“显示列表设计”一节)。
§6.3 创建光源
光源有如下属性:颜色、位置和方向。下面将讨论如何控制这些属性一达到期望的效果。我们用来设置光源的所有属性的函数是glLight*(),它有三个参数,分别用来代表光源标识、需设置的属性及期望的属性值。函数形式为:
void glLight{if}[v](GLenum light, GLenum pname, TYPE param);
light是光源的标识,例如:GL_LIGHT0,GL_LIGHT1...GL_LIGHT7。一般地,GL_LIGHTi代表第i号光源。pname代表需设置的属性,其枚举值和对应的属性意义见表6-1。param代表需要给pname代表的属性设置的值。注意,TYPE是param的类型,它由glLight函数名后面跟的字母决定,i就是int,f就是float,v(就是vectro)可选,选了说明param代表指向一组参数的指针,不选则说明是单一参数。
表6-1 glLight*()的pname参数及其缺省值
(本表系从yxy (田丝丝#冬眠的小肥鹰)的教程中拷贝而来,改了一点点,呵呵)
pname 缺省值 说明 GL_AMBIENT 0,0,0,1 RGBA模式的环境光 GL_DIFFUSE 1,1,1,1 RGBA模式的漫反射光 GL_SPECULAR 1,1,1,1 RGBA模式的镜面光 GL_POSTION 1,0,1,0 光源位置齐次坐标(x,y,z,w) GL_SPOT_DIRECTION 0,0,-1 聚光灯聚光方向矢量 GL_SPOT_EXPONENT 0 聚光灯聚光指数 GL_SPOT_CUTOFF 180 聚光灯聚光发散半角 GL_CONSTANT_ATTENUATION 1 常数衰减因子 GL_LINER_ATTENUATION 0 线性衰减因子 GL_QUADRATIC_ATTENUATION 0 平方衰减因子表6-1中相对GL_DIFFUSE和GL_SPECULAR的缺省值只对GL_LIGHT0有效,对于其它的光源,它们的缺省值都是 (0.0, 0.0, 0.0, 1.0)。
下面是使用glLight*()的一个例子:
GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
数组被用来给参数赋值,通过反复调用glLightfv()给不同的参数赋值。在这个例子里,前三条语句是多余的,因为它们设置的分别是那三个参数的缺省值。
记住要用glEnable打开每个光源。要了解更多的相关操作,请参阅“使光源生效”一节。
在下面的部分中将解释glLight*()的所有参数和它们可能的取值。这些参数和定义全局光照模型以及物体材质特性的参数息息相关。请参阅“选择光照模型”和“定义材质特性”以获取更多的知识。在“光照的数学”中,将从数学的角度解释这些参数是如何相互作用的。
§6.3.1 颜色
OpenGL允许对光源的三个不同的与颜色相关的参数:GL_AMBIENT, GL_DIFFUSE和GL_SPECULAR进行调整。
GL_AMBIENT参数代表场景中某光源的环境光的RGBA各个分量强度。象表6-1中列的那样,缺省情况下是没有环境光的,因为GL_AMBIENT是(0.0, 0.0, 0.0, 1.0)。例6-1中使用的正是这样的值。如果程序将环境光设为蓝光,应该这样
GLfloat light_ambient[] = { 0.0, 0.0, 1.0, 1.0};
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
那么产生的结果如附录J中图J-14所示。
GL_DIFFUSE可能是与“光的颜色”联系最大的参数了,它定义了特定光源提供的漫反射光的RGBA颜色值。缺省情况下,光源GL_LIGHT0的GL_DIFFUSE值是 (1.0,1.0, 1.0, 1.0),即产生明亮的白光,如附录J中图J-14。而其它光源(GL_LIGHT1到GL_LIGHT7)的缺省值都是 (0.0, 0.0, 0.0, 0.0)。
GL_SPECULAR参数影响物体上的高光点颜色。现实世界中的典型例子如玻璃瓶子上的高光点显示的就是照在上面的光的颜色。因此,如果你想获得逼真的效果,应该将GL_SPECULAR的值设得和GL_DIFFUSE一样(为什么? 我想是获得逼真的金属光泽效果吧。) 所有的光源的GL_SPECULAR参数缺省值和GL_DIFFUSE缺省值也是一样的。
§6.3.2 位置与衰减
光源的位置有两种。一种是离场景无限远,另一种是在附近。前者被称为方向光源,它射到物体上的光可以认为是平行的。现实生活中,太阳就是这样的光源。后者被称为位置光源,因为它的确切位置决定了它对场景的作用效果,尤其是决定了光线的投射方向。台灯就是位置光源。从附录J图J-16中你可以看到这两种光源的不同作用效果。
在例6-1中使用的是方向光源:
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
如上所示,你给GL_POSITION参数提供了一个四个值的向量(x, y, z, w)。如果最后一个值w是0,则相应的光源为方向光源,且(x, y, z)决定其方向。这个方向将被透视模型(modelview,这个词俺不知该怎么翻,根据后面的描述,模型变换是指旋转变换,透视变换是只位移变换)矩阵转化到视点坐标系中,就好象它是一个被标准化的向量一样。GL_POSITION的缺省值为(0, 0, 1, 0),指向Z轴的负方向。(注:你当然可以定义诸如(0,0,0)的方向,但这对处理光照是没有意义的)
如果w是非零值,光源就是位置型的。而(x,y,z)代表光源位置的齐次坐标(参见附录G)。该位置将被透视模型矩阵转换到视点坐标系中(参见“控制光源的位置和方向”一节以了解更多的有关位置转换的知识)。在缺省意义下,光源的光是向各个方向发出的,不过你也可以定义一个聚光灯使其发射锥光,这将在下一节“聚光灯”中解释。
请注意,具有光滑阴影效果的多边形表面各部分的颜色是由其各顶点的颜色计算决定的。正因为如此,你需要避免在局部光源附近使用大多边形--如果光源位置在多边形的中部,各顶点会因为距离光源太远而接收不到足够的光照,从而整个多边形看起来要比你相象的要暗。要解决这个问题,就得把大多边形分解成许多小块。
在现实世界中,光线的强度会随着传播距离的增加而减弱。方向光源是从无限远处射来的,对它谈距离衰减意义不大,所以衰减计算对方向光源是无效的。然而,对位置光源则必须考虑这个问题。OpenGL通过给光源的光乘上一个因子来达到衰减的目的。
衰减因子 =
其中,
d为光源到顶点的距离
kc为GL_CONSTANT_ATTENUATION
kl为GL_LINEAR_ATTENUATION
kq为GL_QUADRATIC_ATTENUATION
缺省情况下,kc为1.0,kl和kq都为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);
注意,环境光、漫反射光和镜面光都会被衰减,只有出射光和全局环境光不被衰减。
§6.3.3 聚光灯
前面曾提到,你可以将一个位置光源定义成一个聚光灯,也就是说可以将光的发射形状调整为圆锥形。创建聚光灯,你需要定义需要的锥光的跨度。(记住,因为聚光灯是位置光源,你还是需要为它定义一个位置。你当然也可以方向光源定义为聚光灯,从语法上没有错,但是无意义)。定义锥光的散射角(即圆锥中轴和边的夹角),需要使用GL_SPOT_CUTOFF参数,圆锥的顶角是该值的两倍,见图6-2。
图6-2 GL_SPOT_CUTOFF参数示意
注意光线不能超越圆锥的边界。缺省时没有聚光灯效果,因为GL_SPOT_CUTOFF参数被默认为180度,也就意味着光线是超各个方向发射的(此时圆锥的顶角为360,已经不成“锥”了)。GL_SPOT_CUTOFF一般的取值范围是[0.0, 90.0](或者就是取那个180)。下面的语句将散射角置为45度。
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);
你还需要给出聚光灯的方向,即圆锥中轴的方向。
GLfloat spot_direction[] = { -1.0, -1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
方向是用齐次坐标表示的,缺省情况下为(0, 0, 1),所以如果你不显式设置GL_SPOT_DIRECTION的值,聚光灯光线将射向z轴负向。同时,这个方向将被透视模型矩阵按其标准化后的值转换到视点坐标系中(参见“控制光源的位置和方向”一节以了解更多的有关位置转换的知识)。
除了聚光灯的散射角和方向外,你还可以控制光线在圆锥内的强度分布。有两个办法,首先,你可以先设置光线衰减因子,我们前面提到过,它将与光强相乘;你也可以设置聚光指数,即GL_SPOT_EXPONENT参数,它决定了光线的集中性,缺省值为0。光线在圆锥的中心最强,越靠边越弱,其衰减与该光线与圆锥中轴的夹角的COS值的GL_SPOT_EXPONENT参数次方成正比。因此聚光指数越大,锥光的集中性越好。请参阅“光照的数学”一节以了解光强计算的细节。
§6.3.4 多光源
如前所述,你可以在你的场景中至少使用8个光源(当然可以更多,这取决于你的需要)。因为OpenGL需要对每个顶点从所有光源接收的光进行计算,所以增加光源的数目无疑会使系统处理效率下降。用来代表个光源的常量为GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, GL_LIGHT3等等。在前面的讨论中,已经对与GL_LIGHT0相关的参数进行了设置。如果要增加光源,则必须对其参数进行设置。不过要记住,其他光源的缺省参数与GL_LIGHT0的是不同的,如表6-2所示。下面的代码定义了一个衰减的白色聚光灯。
GLfloat light1_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
GLfloat light1_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light1_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light1_position[] = { -2.0, 2.0, 1.0, 1.0 };
GLfloat spot_direction[] = { -1.0, -1.0, 0.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_CONSTANT_ATTENUATION, 1.5);
glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.5);
glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.2);
glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 45.0);
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, 2.0);
glEnable(GL_LIGHT1);
如果将这些代码加入例6-1中,那么其中的球体将被两个光源照射,一个是方向型的,另一个是位置型的。
可以尝试一下,修改例6-1, 将原来的白色方向光源修改为一个位置型有色光源 加入一个有色聚光灯。提示:可以参考上面的代码 感觉一下这两种改动对整体效果的影响
§6.3.5 控制光源位置和方向
OpenGL对光源的位置和方向的处理与它对基本的几何形体的处理是类似的。换句话说,光源与基本形体有着相同的矩阵变换。进一步讲,当使用glLight*()来指定光源的位置和方向时,这些位置和方向就通过当前的透视模型矩阵转换到视点坐标系中。这意味着你可以通过修改当前透视模型矩阵栈中的内容来控制光源的位置和方向(注:投影矩阵不会对光源的位置和方向产生影响)。本节将介绍如何通过修改程序中对光源位置的设置来获得三种不同的效果,这是与模型和透视变换相关的。这些效果是: 固定位置光源 环绕固定物体运动的光源 和视点一起运动的光源
例6-1作为最简单的例子,演示了固定位置光源。要达到这个效果,你只需要在使用了透视或(和)模型变换后设置光源位置。下面是myinit()和myReshape()函数中的相关语句:
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-1.5, 1.5, -1.5*h/w, 1.5*h/w, -10.0, 10.0);
else
glOrtho (-1.5*w/h, 1.5*w/h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();
/* later in myInit() */
GLfloat light_position[] = { 1.0, 1.0, 1.0, 1.0 };
glLightfv(GL_LIGHT0, GL_POSITION, position);
视窗和投影矩阵被首先确定。然后,单位阵被调入作为透视模型矩阵,接下来设置光源位置。因为使用了单位阵,因此光源的初始位置(1.0, 1.0, 1.0)和它相乘并没有变化。在这之后,光源位置和透视模型矩阵都没有改变,所以光源还放在(1.0, 1.0, 1.0)的位置。
现在假设你要旋转或移动光源的位置使其相对一个静态的物体运动。一个办法是在模型变换后设置光源位置,光源位置改变的效果是通过模型变换阵的改变来实现的。你仍可以在程序开头使用上面的init()函数。然后,一般是在程序消息循环里加入你想要的模型变换(利用透视模型栈)从而确定光源的新位置。下面是一段可能的代码:
void display(GLint spin)
{
GLfloat light_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) spin, 1.0, 0.0, 0.0);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glPopMatrix();
auxSolidTorus(0.275, 0.85);
glPopMatrix();
glFlush();
}
display()函数重画场景,重画时光源好象是绕静态圆环面转过了spin代表的角度。请注意两对glPushMatrix()和glPopMatrix()函数,它们是用来隔离透视和模型变换的,之所以要隔离是因为需要画的圆环面是不旋转的,即画它时只受到glTranslatef(0.0, 0.0, -5.0); 这条语句的影响。在本例中视点保持不变,所以当前的变换阵被压栈,然后由glTranslatef()完成透视变换(实际就是位移),将现在的变换阵压栈,再由glRotated()完成模型变换(其实就是旋转变换)。此时设置新的光源位置,由于此位置是在旋转后的坐标系中定义的,所以变换回原坐标系中后就好象光源被旋转了一样(注:原也就是视点坐标系,与之对应的变换阵一直被压在栈里)。在光源位置定义完毕后,与透视变换对应的矩阵被弹出,此时再画圆环面,就象前面所说的那样,转换到原坐标系中,圆环面只有位置变换而无旋转变换,所以看起来就有了光源环绕圆环面旋转的效果。
为了创建和视点一起移动的光源,你需要在透视模型变换之前设置光源位置,这样,变换就会按相同的方式一起变换视点和光源。让我们在myinit()函数中,作一些小改动来达到这个效果:
GLfloat light_position[] = { 0.0, 0.0, 1.0, 1.0 }; // 这样光源好象是放在视点的位置上
glViewport(0, 0, w-1, h-1);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(40.0, (GLfloat) w/(GLfloat) h, 1.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
然后对display()函数修改如下:
void display(GLint spin)
{
glClear(GL_COLOR_BUFFER_MASK | GL_DEPTH_BUFFER_MASK);
glPushMatrix();
glTranslatef (0.0, 0.0, -5.0);
glRotatef ((GLfloat) spin, 1.0, 0.0, 0.0);
auxSolidTorus(0.275, 0.85);
glPopMatrix();
glFlush();
}
当被照的圆环面重画时,光源和视点都被移动了spin角度。因为我们是在视点的坐标系中,所以看到的效果是一个旋转的圆环面被固定的视点的光源照射的效果。
可以尝试一下,修改下面的例子6-1: 让光源只是相对物体移动而不是环绕其转动。提示:在display()中用glTranslated()代替glRotated(),同时找一个合适的参数代替spin 改变光源衰减模式,使光源远离物体时物体上的光照会减弱。提示:加入glLight*()设置相应参数
例6-1: 通过模型变换移动光源: movelight.c
#include <GL/gl.h>
#include <GL/glu.h>
#include "aux.h"
static int spin = 0;
void movelight (AUX_EVENTREC *event)
{
spin = (spin + 30) % 360;
}
void myinit (void)
{
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
}
void 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) spin, 1.0, 0.0, 0.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 (0.0, 1.0, 1.0);
auxWireCube (0.1);
glEnable (GL_LIGHTING);
glPopMatrix ();
auxSolidTorus (0.275, 0.85);
glPopMatrix ();
glFlush ();
}
void 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);
}
int main(int argc, char** argv)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH);
auxInitPosition (0, 0, 500, 500);
auxInitWindow (argv[0]);
myinit();
auxMouseFunc (AUX_LEFTBUTTON, AUX_MOUSEDOWN, movelight);
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
§6.4 选择光照模型
OpenGL的光照模型概念有如下三个方面: 全局环境光强度 视点位置在场景内还是可以被认为是无限远处 对物体表面的正反面光照计算是否不同
本节介绍如何设置光源,同时介绍如何起用之--即如何告诉OpenGL你需要对相应光照进行计算。
§6.4.1 全局环境光
如前所述,每个光源都对场景的环境光有贡献。同时,还有一种环境光不是由光源提供的,这就是全局环境光。 设置全局环境光的RGBA强度,需要按下面的方式使用GL_LIGHT_MODEL_AMBIENT参数:
GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
本例中lmodel_ambient的参数值都是GL_LIGHT_MODEL_AMBIENT的缺省值。因为这些数值会产生少量的环境光,所以即使不定义其它光源你仍可以看到场景中的物体。附录J中图J-18演示了设置不同强度全局环境光的效果。
§6.4.2 局部和无穷远视点
视点位置影响到由于镜面反射导致的高光点计算。更具体地讲,高光点的强度由三个因素决定:被照顶点的法向量、从光源到顶点的光线方向、视点和顶点的连线方向。有一点要明白,使用各种光照命令后,实际上移动的并不是视点,你所做的是改变了投影方式,使视点看起来被移动了(请参阅“投影变换”一节)。不过,在光照计算中,很多地方是假设光源位置改变的。
对于无穷远视点来说,它和某顶点的连线方向是个常数。局部视点的效果更趋于真实,但由于它要对每个顶点的方向分别计算,就大大增加了程序的负担。缺省状态下,视点都是在无穷远处的。下面的语句可以把它改成局部的:
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
这条语句把视点放在视点坐标系的(0,0,0)位置。想变回无穷远视点,只要把最后一个参数改成GL_FALSE就行了。
§6.4.3 双侧光照
光照计算是对所有多边形表面进行的,不管是正面还是反面。由于通常设置的都是正面的光照性质,背面的光照效果往往都是不正确的。在例6-1中,被照物体是球面,可以看到的永远是其正面,也就是球的外表面,所以背面的光照效果根本看不到,也就无所谓了。如果球面被切开,其内侧就会被看到,那么你一定希望里面的光照效果和外面定义的一致,你也许还需要给内表面贴上一种不同的材质。起用双侧光照的语句如下:
glLightModeli(LIGHT_MODEL_TWO_SIDE, GL_TRUE);
OpenGL会把正面顶点的法向量反过来作为背面的法向量。这样,所有多边形的光照效果就正确了。
关闭双侧光照,只需把前面语句中的GL_TRUE换成GL_FALSE即可。参阅“定义材质特性”一节以了解如何给双侧设置材质特性。你还可以通过glFrontFace()命令设置哪个表面被OpegGL认为是正面,参阅“Reversing and Culling Polygon Faces”一节,那里对该命令会详细解释。
§6.4.4 起用光照
在OpenGL中你需要显式地起用(或取消)光照计算。如果光照没有被起用,当前的颜色只是简单地被映射到当前顶点上,不会进行任何关于法向量、光源、光照模型,以及材质的计算。下面是起用光照的命令:
glEnable(GL_LIGHTING);
取消光照的命令为glDisable(GL_LIGHTING); 。
当你设置了你定义的每个光源后,你必须显式地打开它们。在例6-1中只使用了一个光源,GL_LIGHT0。打开它的语句为:
glEnable(GL_LIGHT0);
§6.5 定义材质特性
本节将介绍如何定义物体的材质:环境反射色、漫反射色和镜面反射色,光泽度,以及出射光色。在光照和材质计算中用到的公式会在“光照中的数学”一节中介绍。大部分的材质特性参数与在建立光源时用过的参数相似,设置它们的机制也差不多,只不过这回使用的命令是glMaterial*()罢了。其完整形式为:
void glMaterial{if}[v](Glenum face, GLenum pname, TYPE param);
face参数可以是GL_FRONT, GL_BACK, 或 GL_FRONT_AND_BACK,分别代表材质应该贴到物体的哪个表面。材质特性的名称通过pname设置,特性值由param参数确定。param可以是单值或是指向一组参数的指针(如果函数是向量表示形式的话,此时函数名后面一定有“v”字符)。非向量表示形式(函数名后面没有“v”)只用来设置GL_SHININESS参数。pname可以表示的参数意义和对应缺省值见表6-2:
表6-2 glMaterial*()的pname参数及其缺省值
pname 缺省值 说明 GL_AMBIENT (0.2, 0.2, 0.2, 1.0) 材料环境光反射色 GL_DIFFUSE (0.8, 0.8, 0.8, 1.0) 材料漫反射光反射色 GL_AMBIENT_AND_DIFFUSE 设置上两项为相同值 GL_SPECULAR (0.0, 0.0, 0.0, 1.0) 材料镜面光反射色 GL_SHININESS 0.0 材料反射指数 GL_EMISSION (0.0, 0.0, 0.0, 1.0) 材料发射光色 GL_COLOR_INDEXES (0,1,1) 环境、漫射、镜面光反射颜色索引在“选择光照模型”一节中讨论过,你可以为物体的正反表面设置不同的光照效果。如果反面的确可以被看到的话,你可以通过glMaterial*()的face参数来使正反表面具有不同的材质特性。附录J的图J-19展示了一个内外材质不同的物体的光照效果。
想知道通过材质特性的设置会引起什么样的效果,请见附录图J-21,图中给出了同一个物体设置了不同材质特性的效果。整幅图使用的是相同的光照模型。下面的部分将详细讨论这些球体的效果是如何画出来的。
请注意,大部分材质特性的设置值是(R,G,B,A)颜色。不管在其它参数中alpha(也就是A)的含义,相对某个特定的顶点,它表示该顶点的漫反射光反射色的alpha,也就是给GL_DIFFUSE参数设置值的alpha值。(参阅“混合”一节关于alpha值的讨论)。同时,任何RGBA模式的材质特性使用的都不是颜色索引模式。参阅“颜色索引模式下的光照”一节以了解更多关于颜色索引模式的知识。
§6.5.1 漫射和环境反射
通过glMaterial*()设置GL_DIFFUSE和GL_AMBIENT参数会影响到被物体反射的漫射光和环境光的颜色。漫射光是影响你所看到的物体颜色的最重要的因素,它取决与入射光的颜色以及入射光和顶点法向量的夹角。(当入射光垂直于表面是漫射光最强)。视点位置对漫射光无影响。
环境光影响物体的总体色彩。因为当物体被光源直接照射时眼睛看到的主要的漫射光,所以只有当没有直接照明时环境光在物体上的效果才会被明显的看到。物体的环境反射光由全局环境光和各光源的环境光成分决定。与漫射光类似,环境光也不受视点位置的影响。
对于现实的物体来说,环境光和漫射光的颜色基本是一致的。因此,OpenGL提供了一个方法可以方便地使用glMaterial*()一次将漫射光和环境光参数设置为相同的值:
GLfloat mat_amb_diff[] = { 0.1, 0.5, 0.8, 1.0 };
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat_amb_diff);
在本例中,RGBA色为(0.1, 0.5, 0.8, 1.0)--一种深蓝色,这句话将物体正反表面的环境和漫射光色都设置为这种颜色。
在附录图J-21第一行的球体没有环境光反射--(0,0,0,0),第二行的环境光反射就比较大--(0.7, 0.7, 0.7, 1.0)。
§6.5.2 镜面反射
物体上的镜面反射导致高光点。与环境光和漫射光不同,镜面反射的光强与视点的位置关系密切--从反射角的方向看它最亮。想理解这一点,可以想象在阳光下看一个金属球的情形。当你移动脑袋的时候,高光点也会跟着移动一些。而移动超过一定限度时,你就完全看不到高光点了。
OpenGL允许你设置镜面反射高光点的RGBA颜色(通过GL_SPECULAR参数),并控制高光点的大小和明亮程度(通过GL_SHININESS参数)。GL_SHININESS的取值范围是[0.0, 128.0]--值越高,高光点越小且越亮(聚焦越好)。想了解高光点的计算请参阅“光照中的数学”一节。
在附录图J-21中,第一列的球体没有镜面反射,第二列的GL_SPECULAR 的GL_SHININESS值设置如下:
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat low_shininess[] = { 5.0 };
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
在第三列中,GL_SHININESS值都被增加到了100.0。
§6.5.3 出射光
通过给GL_EMISSION参数设置一个RGBA值,你可以使物体看起来好象在发射那种颜色的光。因为大部分现实世界的物体(除了光源)都不发射光,所以你你最可能使用这个功能来模拟一盏灯以及场景中的其它光源。在图J-21中,第四列的球体都有绿色的GL_EMISSION:
GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
你会注意到这些球体微微有些荧光灯泡的感觉,不过它们在场景中不会起到光源的作用。要想真正有灯泡照亮别人的效果,你必须在球体的位置再定义一个位置型光源才行。
§6.5.4 改变材质特性
例6-1给场景中物体(球体)的所有顶点都赋与了相同的材质。在其它情况下,你也许需要对同一个物体不同顶点赋与不同的材质。更可能的情况是,你的场景中有不止一个物体,而每个物体都有不同的材质特性。例6-3给出了这样一个例子(这些语句写在display()函数中):
例6-3:使用不同的材质:material.c
GLfloat no_mat[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat mat_ambient[] = { 0.7, 0.7, 0.7, 1.0 };
GLfloat mat_ambient_color[] = { 0.8, 0.8, 0.2, 1.0 };
GLfloat mat_diffuse[] = { 0.1, 0.5, 0.8, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat no_shininess[] = { 0.0 };
GLfloat low_shininess[] = { 5.0 };
GLfloat high_shininess[] = { 100.0 };
GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 第一行第一列的球体,只有漫反射,没有环境光或镜面反射
glPushMatrix();
glTranslatef (-3.75, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere();
glPopMatrix();
// 第一行第二列的球体,有漫反射和镜面反射,低光泽度,无环境光
glPushMatrix();
glTranslatef (-1.25, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere();
glPopMatrix();
// 第一行第三列的球体,有漫反射和镜面反射,高光泽度,无环境光
glPushMatrix();
glTranslatef (1.25, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
auxSolidSphere();
glPopMatrix();
// 第一行第四列的球体,有漫反射和出射光,无环境光和镜面反射
glPushMatrix();
glTranslatef (3.75, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
auxSolidSphere();
glPopMatrix();
如上所示,glMaterial*()被反复调用以设置各球期望的材质特性。注意,只有一个特性需要被修改时才应该调用一个glMaterialf*()。举个例子,第二和第三球的环境光和漫射特性与第一个球的一致,所以这些特性不用被重新设置一次。因为glMaterialf*()的调用次数影响到系统消耗,所以对材质特性的修改越少越好。
另一种减少修改材质特性的消耗的办法是使用glColorMaterial(),其形式为
void glColorMaterial(Glenum face, GLenum mode);
该命令使材质的特性色和当前颜色(由glColor*()设置)一致。face参数可以是GL_FRONT, GL_BACK, 或GL_FRONT_AND_BACK(缺省值),mode参数可以是GL_AMBIENT, GL_DIFFUSE, GL_AMBIENT_AND_DIFFUSE (缺省值), GL_SPECULAR, 或 GL_EMISSION。face代表哪个或哪些表面的材质特性色需要改变为当前颜色,mode代表哪些材质特性色需要改变。注意,OpenGL不允许给同一面设置不同的mode。
使用了glColorMaterial()命令后,你需要调用glEnable(GL_COLOR_MATERIAL)命令使之有效。下面,就可以使用glColor*()来改变当前颜色并影响后面所画的物体的颜色了(使用glMaterial*()可以改变其它材质特性)。下面是一段例子:
glColorMaterial(GL_FRONT, GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
glColor3f(0.2, 0.5, 0.8);
/* 画一些东西 */
glColor3f(0.9, 0.0, 0.2);
/* 再画一些东西 */
glDisable(GL_COLOR_MATERIAL);
当你需要改变场景中的大部分顶点的某个单独材质特性时,就应该使用glColorMaterial(),这样做速度最快。如果改变的是不止一个特性,则需要使用glMaterial*()。当不再需要glColorMaterial()功能时,别忘了用glDisable(GL_COLOR_MATERIAL)使之无效,以减少系统消耗。
例6-4给出了一个利用glColorMaterial()改变材质参数的互动式程序。按动三个鼠标键可以将物体的散射光色改变为不同颜色。
例6-4:使用glColorMaterial():colormat.c
#include <GL/gl.h>
#include <GL/glu.h>
#include "aux.h"
GLfloat diffuseMaterial[4] = { 0.5, 0.5, 0.5, 1.0 };
void myinit(void)
{
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuseMaterial);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialf(GL_FRONT, GL_SHININESS, 25.0);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glDepthFunc(GL_LEQUAL);
glEnable(GL_DEPTH_TEST);
glColorMaterial(GL_FRONT, GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
}
void changeRedDiffuse (AUX_EVENTREC *event)
{
diffuseMaterial[0] += 0.1;
if (diffuseMaterial[0] > 1.0)
diffuseMaterial[0] = 0.0;
glColor4fv(diffuseMaterial);
}
void changeGreenDiffuse (AUX_EVENTREC *event)
{
diffuseMaterial[1] += 0.1;
if (diffuseMaterial[1] > 1.0)
diffuseMaterial[1] = 0.0;
glColor4fv(diffuseMaterial);
}
void changeBlueDiffuse (AUX_EVENTREC *event)
{
diffuseMaterial[2] += 0.1;
if (diffuseMaterial[2] > 1.0)
diffuseMaterial[2] = 0.0;
glColor4fv(diffuseMaterial);
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
auxSolidSphere(1.0);
glFlush();
}
void myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,
1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0);
else
glOrtho (-1.5*(GLfloat)w/(GLfloat)h,
1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
int main(int argc, char** argv)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH);
auxInitPosition (0, 0, 500, 500);
auxInitWindow (argv[0]);
myinit();
auxMouseFunc(AUX_LEFTBUTTON, AUX_MOUSEDOWN,
changeRedDiffuse);
auxMouseFunc(AUX_MIDDLEBUTTON, AUX_MOUSEDOWN,
changeGreenDiffuse);
auxMouseFunc(AUX_RIGHTBUTTON, AUX_MOUSEDOWN,
changeBlueDiffuse);
auxReshapeFunc(myReshape);
auxMainLoop(display);
}
可以尝试一下,修改例6-3, 修改全局环境光。提示:改变GL_LIGHT_MODEL_AMBIENT参数值。 改变散射光、环境光和镜面反射光,光泽度指数,以及出射光。提示:使用glMaterial*()命令,但注意不要调用次数过多。 使用双侧光照,并加入一个任意的裁剪面(参阅“加入裁剪面”一节),这样你可以看到一行或一列的球体的内部光照情况。提示:打开GL_LIGHT_MODEL_TWO_SIDE开关,设置期望的材质特性,并加入裁剪面。 去掉所有的glMaterial*()语句,多使用一些高效率的glColorMaterial()语句达到同样的光照效果。
§6.6 光照中的数学
提高:
本节将给出一些公式,它们是OpenGL用来计算光照下的RGBA模式颜色的。(在“索引颜色模式光照中的数学”一节中,你可以找到关于索引颜色的相应计算)。如果你想通过尝试的办法获得想要的光照效果,那么你没有必要阅读本节。而且即使在你读完本节后,你还是需要通过尝试才能真正掌握光照的应用。当然,在读完本节后你会对各个参数对顶点的颜色是如何影响的有一个更好的理解。记住,如果不允许光照,那么顶点的颜色只是当前颜色,当允许光照后,下面将要介绍的光照计算将在视点坐标系中被使用。
在下面的公式中,数学运算是对RGB分量分别进行的。举个例子,当三颜色条目相加时,RGB值是分别与相应的分量相加,再合成为最终结果RGB颜色的(R1+R2+R2, G1+G2+G3, B1+B2+B3)。当三个颜色条目相乘时,计算结果形式为(R1R2R3, G1G2G3, B1B2B3)。(记住,某顶点颜色的A或alpha分量最终等于该点的材质散射特性值的alpha分量)。
顶点在光照下的颜色由下式给出:
顶点颜色 = 顶点出射光 + 全局环境光与该点材质的环境光反射因子之积 + 经过一定衰减的,各光源对该点贡献的环境反射光、漫射光、以及镜面反射光
光照计算完毕后,个点的RGBA颜色值会被限制到[0,1]范围内。注意OpenGL不会考虑物体都对光线的阻挡,所以不会自动生成阴影效果。(想知道如何生成阴影,参阅“阴影”一节)。还要注意,OpenGL中的物体的出射光不会照到其它物体上产生光照效果。要想产生那样的效果必须再多加光源才行。
§6.6.1 材质出射光
这个最简单,就是给GL_EMISSION参数设置的RGB值。
§6.6.2 放缩的全局环境光
这个由全局环境光(GL_LIGHT_MODEL_AMBIENT参数值)和材质的环境光反射特性(GL_AMBIENT参数值)相乘得到。即,
ambientlight model * ambientmaterial
这两个参数的RGB值会分别相乘,最后再合成为最终值:(R1R2, G1G2, B1B2)。
§6.6.3 光源的贡献
每个光源都会对顶点的颜色有所贡献,而且这些贡献是可叠加的。计算每个光源贡献的公式如下:
contribution = attenuation factor * spotlight effect * (ambient term + diffuse term + specular term)
§6.6.3.1 衰减因子
衰减因子在“位置与衰减”一节中已经讨论过:
衰减因子 =
其中,
d = 光源到顶点的距离
kc = GL_CONSTANT_ATTENUATION
kl = GL_LINEAR_ATTENUATION
kq = GL_QUADRATIC_ATTENUATION
如果光源是方向型的,衰减因子是1。
§6.6.3.2 聚光灯效果
聚光灯效果可以有三种评价值,这取决于光源是否是聚光灯以及顶点在光锥以内还是以外: 1,如果光源不是聚光灯(GL_SPOT_CUTOFF等于180) 0,如果光源是聚光灯但是顶点位于光锥以外 (max {v · d, 0 } )*GL_SPOT_EXPONENT,其中
v = (vx, vy, vz)是从聚光灯位置(GL_POSITION)指向顶点的单位向量
d = (dx, dy, dz)是聚光灯方向,并假定是聚光灯光源且顶点位于光锥以内
v和d的点积代表这两个向量夹角的cos值,所以正对着聚光灯的点的亮度最大,越靠边的点,随着cos值的减小,亮度就越小。
为了判断一个点是否在锥光的内部,OpenGL计算(max {v · d, 0 } )的值,如果该值比聚光灯的发散角的cos值小,那么该点就在锥光以外,反之在以内。
§6.6.3.3 环境光分量
环境光分量只是光源的环境光分量和材质的环境光反射特性的乘积:
ambientlight *ambientmaterial
§6.6.3.4 漫射光分量
漫射光分量的计算要考虑光线是否垂直入射、入射光的漫射分量、以及材质的漫射特性:
(max { l · n , 0 } ) * diffuselight * diffusematerial,其中
l = (lx, ly, lz)是从顶点指向光源位置(GL_POSITION)的单位向量,
n = (nx, ny, nz)是该顶点的单位法向量。
§6.6.3.5 镜面反射分量
镜面反射光分量的计算也要考虑光线是否垂直入射。如果l · n不大于0,那么该点就没有镜面反射分量(如果是小于0,说明光源的光无法照到该计算的这一面来)。如果存在镜面反射分量的话,它由下面的因素决定: 该点的单位法向量 (nx, ny, nz) 两个单位向量之和,它们是:(1)由顶点指向光源位置的向量;(2)由顶点指向视点的向量。(假定GL_LIGHT_MODEL_LOCAL_VIEWER模式为真。如果为假,则第二个向量由(0, 0, 1)代替)。最后的和向量要经过标准化,即除以自己的模变成单位向量,为s = (sx, sy, sz)。 镜面反射指数 (GL_SHININESS) 光源的镜面光分量(GL_SPECULAR) 材质的镜面反射分量(GL_SPECULAR)
在上面的定义下,OpenGL是这样计算镜面反射分量的:
(max { s · n , 0} ) * shininess * specularlight * specularmaterial
如果l · n = 0,上式为0。
§6.6.4 合成
由上面所定义的各个分量, 整体的RGBA模式的光照计算为:
顶点颜色 =
emissionmaterial + ambientlight model * ambientmaterial +
(spotlight effect)i
[ ambientlight *ambientmaterial +
(max { 1 · n , 0 } ) * diffuselight * diffusematerial +
(max {s · n ,0} )shininess * specularlight * specularmaterial ] I
§6.7 颜色索引模式中的光照
在颜色索引模式下,RGBA形式的参数值或者没有意义或有其它的含义。因为在颜色索引模式下获得某种效果比较困难,所以你应该进可能使用RGBA模式。实际上,在颜色索引模式下使用的RGBA形式的参数只是光源参数GL_DIFFUSE和GL_SPECULAR,和材质参数GL_SHININESS。这些参数(dl, sl)用来计算颜色索引模式下的漫射光和镜面反射光强(dci, sci):
dci = 0.30 R(dl) + 0.59 G(dl) + 0.11 B(dl)
sci = 0.30 R(sl) + 0.59 G(sl) + 0.11 B(sl)
其中R(x), G(x), 和 B(x)代表颜色x的红、绿、蓝分量。权重系数0.30, 0.59, 和0.11代表了视觉效果对各个颜色分量的权重--眼睛对绿色最敏感,而对蓝色最不敏感。
在颜色索引模式下定义材质颜色,调用glMaterial*()时要加入GL_COLOR_INDEXES参数,如下:
GLfloat mat_colormap[] = { 16.0, 47.0, 3682.0 };
glMaterialfv(GL_FRONT, GL_COLOR_INDEXES, mat_colormap);
mat_colormap的三个分量分别代表材质的环境光反射、漫射光反射、镜面光反射索引颜色值。换句话说,OpenGL认为第一个索引值(本例中是16.0)是纯的环境光反射色,第二个索引值(本例中是47.0)是纯的漫射光反射色,第三个索引值(本例中是3682.0)是纯的镜面反射光色。(缺省情况下,上面三个值是0.0, 1.0, 1.0。并注意glColorMaterial()函数在颜色索引模式下无效)。
在画一幅场景时,OpenGL利用这几个“纯”色之间的过渡值绘制出物体的明暗效果。因此你必须建立这些纯索引色之间的过渡映射函数(原文叫color ramp,直译是颜色斜坡),在本例中是16和47之间,以及47和3682之间。经常使用的是光滑过渡(原文称之为smoothly),也就是线性函数,但你也可以利用其它形式的函数达到不同的效果。下面的例子给出了从黑色的环境光反射色到紫红色的漫射光反射色,再到白色的镜面反射色的光滑过渡色的计算。
for (i = 0; i < 32; i++) {
auxSetOneColor (16 + i, 1.0 * (i/32.0), 0.0, 1.0 * (i/32.0));
auxSetOneColor (48 + i, 1.0, 1.0 * (i/32.0), 1.0);
}
其中辅助函数auxSetOneColor()有四个参数。它使第一个参数代表的索引值和后三个参数代表的RGB颜色对相关联。当i=0时,颜色索引16被赋与RGB值(0.0, 0.0, 0.0),或黑色。然后随着i的增加,颜色过渡到索引值47(i=31),对应RGB值(1.0, 0.0, 1.0),是纯紫红色。循环的另一部分在紫红和白色(索引值3682)之间进行过渡。附录图J-20给出了在此模式下的单光源照射的球体的样子。
提高:
你也许会想到,如果在颜色索引模式中使用的参数形式和在RGBA模式中的有所不同,那么它们对光照的计算方法也是不一样的。
本文地址:http://com.8s8s.com/it/it24720.htm