OpenGL在处理光照时把光照系统分为三部分,分别是光源、材质和光照模型。
光源、材质和光照模式都有各自的属性,尽管属性种类繁多,但这些属性都只用很少的几个函数来设置。
使用glLight*函数可设置光源的属性,
使用glMaterial*函数可设置材质的属性,
使用glLightModel*函数可设置光照模式。
GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR这三种属性是光源和材质所共有的,如果某光源发出的光线照射到某材质的表面,则最终的漫反射强度由两个GL_DIFFUSE属性共同决定,最终的镜面反射强度由两个GL_SPECULAR属性共同决定。
在OpenGL中,仅仅支持有限数量的光源。使用GL_LIGHT0表示第0号光源,GL_LIGHT1表示第1号光源,依次类推,OpenGL至少会支持8个光源,即GL_LIGHT0到GL_LIGHT7。使用glEnable函数可以开启它们。例如,glEnable(GL_LIGHT0);可以开启第0号光源。使用glDisable函数则可以关闭光源。一些OpenGL实现可能支持更多数量的光源,但总的来说,开启过多的光源将会导致程序运行速度的严重下降,
光源GL_LIGHT0与其他几个光源不同,
GL_DIFFUSE,GL_SPECULAR的默认值是(1.0,1.0,1.0,1.0)
而其他光源的默认值是 (0.0,0.0,0.0,1.0)。
使用OpenGL的光照模型包括以下几个步骤:
1 设置光源的种类、位置和方向(对于平行光源)
2 为每个图元的每个顶点指定它的法线向量
3 为各个图元指定它的材质
4 启用光照模型
1 光源设置
设置环境光
glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);
设置漫射光成分
glLightfv(GL_LIGHT0,GL_DIFFUSE,DiffuseLight)
设置镜面光成分
glLightfv(GL_LIGHT0,GL_SPECULAR,SpecularLight);
光源的属性GL_SPECULAR影响镜面反射区域的颜色,一般物体的镜面反射区域的颜色为入射光线的颜色,要实现真实感,应该将它的值设置成与GL_DIFFUSE相同。
设置光源的位置
glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position);
GL_POSITION属性。表示光源所在的位置。由四个值(X, Y, Z, W)表示。
方向性光源 第四个值W为零,则表示该光源位于无限远处,前三个值表示了它所在的方向。通常,太阳可以近似的被认为是方向性光源。
位置性光源 第四个值W不为零,则X/W, Y/W, Z/W表示了光源的位置。这种光源称为位置性光源。
定位光源需要对其发射的光进行衰减,可以设置各种衰减因子。环境光,散射光和镜面反射光的贡献都是衰减的,只有发射光和全局环境光不会衰减。
对于,设置其位置与设置多边形顶点的方式相似,各种矩阵变换函数例如:glTranslate*、glRotate*等在这里也同样有效。方向性光源在计算时比位置性光源快了不少,
因此,在视觉效果允许的情况下,应该尽可能的使用 方向性 光源。
下面定义了一个位置在(1,1,1),没有环境光,镜面反射光和漫反射光都为白光的光源
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
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 };
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);
2 创建聚光灯(这些属性只对位置性光源有效)
glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,LightCutOff);
GL_SPOT_DIRECTION、GL_SPOT_EXPONENT、GL_SPOT_CUTOFF属性。
表示将光源作为聚光灯使用。很多光源都是向四面八方发射光线,但有时候一些光源则是只向某个方向发射,比如手电筒,只向一个较小的角度发射光线。
GL_SPOT_DIRECTION 属性有三个值,表示一个向量,即光源发射的方向。光源的默认方向是(0.0,0.0,-1.0),即指向z轴负方向
GL_SPOT_EXPONENT 属性只有一个值,表示聚光的程度,为零时表示光照范围内向各方向发射的光线强度相同,为正数时表示光照向中央集中,正对发射方向的位置受到更多光照,其它位置受到较少光照。数值越大,聚光效果就越明显。
GL_SPOT_CUTOFF 属性也只有一个值,表示一个角度,它是光源发射光线所覆盖角度的一半,其取值范围在0到90之间,也可以取180这个特殊值。取值为180时表示光源发射光线覆盖360度,即不使用聚光灯,向全周围发射。即一个点光源。
3 设置光线衰减系数(这些属性只对位置性光源有效)
glLightf(GL_LIGHT0,AttenuationWay,SpotAttenuation);
AttenuationWay可以取以下几个值:
GL_CONSTANT_ATTENUATION -- 表示光线按常熟衰减(与距离无关)
GL_LINEAR_ATTENUATION -- 表示光线按距离线性衰减
GL_QUADRATIC_ATTENUATION -- 表示光线按距离以二次函数衰减。
参数 SpotAttenuation为光线的衰减系数。
GL_CONSTANT_ATTENUATION、GL_LINEAR_ATTENUATION、GL_QUADRATIC_ATTENUATION属性。这三个属性表示了光源所发出的光线的直线传播特性。现实生活中,光线的强度随着距离的增加而减弱,OpenGL把这个减弱的趋势抽象成函数:
衰减因子 = 1 / (k1 + k2 * d + k3 * k3 * d)
其中d表示距离,光线的初始强度乘以衰减因子,就得到对应距离的光线强度。k1, k2, k3分别是GL_CONSTANT_ATTENUATION,GL_LINEAR_ATTENUATION,GL_QUADRATIC_ATTENUATION。通过设置这三个常数,就可以控制光线在传播过程中的减弱趋势。
4 为图元指定法向量
OpenGL必须通过图元的法线向量来确定图元的明暗程度
通过计算得到法线向量后,我们需要在绘制顶点前调用glNormal函数为顶点或图元指定法
使用glTranslate*函数或者glRotate*函数可以改变物体的外观,但法线向量并不会随之改变。然而,使用glScale*函数,对每一坐标轴进行不同程度的缩放,很有可能导致法线向量的不正确,虽然OpenGL提供了一些措施来修正这一问题,但由此也带来了各种开销。因此,在使用了法线向量的场合,应尽量避免使用glScale*函数。即使使用,也最好保证各坐标轴进行等比例缩放
对光源进行平移或旋转,使之相对于静止的物体移动,这可以在指定模型变换后设置光源位置,然后通过修改模型变换来改变光源的位置。
5 设置材质
OpenGL 用材料对光的红、绿、蓝三原色的反射率来近似定义材料的颜色。象光源一
样,材料颜色也分成环境、漫反射和镜面反射成分,它们决定了材料对环境光、漫反射光和
镜面反射光的反射程度。在进行光照计算时,材料对环境光的反射率与每个进入光源的环境
光结合,对漫反射光的反射率与每个进入光源的漫反射光结合,对镜面光的反射率与每个进
入光源的镜面反射光结合。对环境光与漫反射光的反射程度决定了材料的颜色,并且它们很
相似。对镜面反射光的反射率通常是白色或灰色(即对镜面反射光中红、绿、蓝的反射率相
同)。镜面反射高光最亮的地方将变成具有光源镜面光强度的颜色。例如一个光亮的红色塑
料球,球的大部分表现为红色,光亮的高光将是白色的。
材质的颜色与光源的颜色有些不同。对于光源,R、G、B 值等于R、G、B 对其最大
强度的百分比。若光源颜色的R、G、B 值都是1.0,则是最强的白光;若值变为0.5,颜色
仍为白色,但强度为原来的一半,于是表现为灰色;若R=G=1.0,B=0.0,则光源为黄色。
对于材质,R、G、B 值为材质对光的R、G、B 成分的反射率。比如,一种材质的R=1.0、
G=0.5、B=0.0,则材质反射全部的红色成分,一半的绿色成分,不反射蓝色成分。也就是
说,若OpenGL 的光源颜色为(LR、LG、LB),材质颜色为(MR、MG、MB),那么,在
忽略所有其他反射效果的情况下,最终到达眼睛的光的颜色为(LR*MR、LG*MG、LB*MB)
指定了图元的法线之后,我们还需要为其指定相应的材质以决定物体对各种颜色的光的反射程度,这将影响物体表现为何种颜色
指定材质
glMaterialfv(GL_FRONT,GL_DIFFUSE,@Diffuse);
1 GL_FRONT(正面),GL_BACK(反面),GL_FRONT_AND_BACK(正反两面)。
2 GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR属性。这三个属性与光源的三个对应属性类似,每一属性都由四个值组成。
GL_AMBIENT表示各种光线照射到该材质上,经过很多次反射后最终遗留在环境中的光线强度(颜色)。
GL_DIFFUSE 表示光线照射到该材质上,经过漫反射后形成的光线强度(颜色)。
GL_SPECULAR表示光线照射到该材质上,经过镜面反射后形成的光线强度(颜色)。
通常,GL_AMBIENT和GL_DIFFUSE都取相同的值,可以达到比较真实的效果。使用GL_AMBIENT_AND_DIFFUSE可以同时设置GL_AMBIENT和GL_DIFFUSE属性。
3 GL_SHININESS属性。该属性只有一个值,称为“镜面指数”,取值范围是0到128。该值越小,表示材质越粗糙,点光源发射的光线照射到上面,也可以产生较大的亮点。该值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。
4 GL_EMISSION属性。该属性由四个值组成,表示一种颜色。OpenGL认为该材质本身就微微的向外发射光线,以至于眼睛感觉到它有这样的颜色,但这光线又比较微弱,以至于不会影响到其它物体的颜色。
5 GL_COLOR_INDEXES属性。该属性仅在颜色索引模式下使用,由于颜色索引模式下的光照比RGBA模式要复杂,并且使用范围较小,这里不做讨论。
它们的数值情况如下:
GLfloat earth_mat_ambient[] = { 0.0f, 0.0f, 0.5f, 1.0f};
GLfloat earth_mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat earth_mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
GLfloat earth_mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat earth_mat_shininess = 30.0f;(0--128)
6 使用颜色跟踪 (这将导致正面的DIFFUSE总是设置为当前颜色)?
在启用光照系统之后,为图元指定颜色变得不太方便。首先我们需要创建一个数组,然后调用glMaterial函数将数组传给材质,以此决定物体的颜色。为了简便,我们可以开启颜色跟踪来简化代码。调用
glEnable(GL_CORLOR_MATERIAL);
启动颜色跟踪,再调用
glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
来决定对物体的正面还是反面,对环境光、镜面光还是漫射光进行颜色跟踪。
第一个参数可以取
GL_FRONT、GL_BACK、GL_FRONT_AND_BACK中的任意一种,
第二个参数可以取
GL_AMBIENT、GL_DIFFUSE、GL_AMBIENT_AND_DIFFUSE、GL_SPECULAR中的任意一种。
启动颜色跟踪之后,我们就可以像以前一样,使用glColor函数来指定图元的颜色了。这时,OpenGL将自动根据从glColor函数传递的颜色来决定物体材质,
画了一个红色的有立体感的球
glPushMatrix();
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
glColor3f(1.0,0.0,0.0);
glutSolidSphere(1.0, 16, 16);
glDisable(GL_COLOR_MATERIAL);
glPopMatrix();
7 光照模型
光照模型有4部分:
全局环境光 近视点或远视点
双面光照 镜面反射颜色是否和环境颜色,散射颜色分开。
指定全局环境光
GLfloat ambient[]={0.3,0.3,0.3,1.0};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);
顶点的镜面反射亮度取决于该点的法线,顶点相对于光源的方向以及顶点相对于视点的方向。
使用近视点,
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);
这就将视点放置在(0,0,0)处.
启用双面光照
glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE)。
镜面反射颜色和环境颜色,散射颜色分开
典型的光照计算中,分别计算环境光,散射光,镜面反射光和发射光的贡献,然后进行叠加,而在这之后进行纹理映射的话,镜面反射区可能被覆盖,为了解决这个问题,
可以glLightModelfv(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR); 这样,每个顶点光照计算将产生两种颜色,主颜色和辅助颜色,前者包含所有非镜面反射光照的贡献,后者是所有镜面反射光照的总贡献。纹理映射的时候只将主颜色和纹理颜色混合起来,执行完纹理映射后,再将主颜色和纹理颜色的混合结果与辅助颜色混合起来。