OpenGL光照

  目 录
  10.1 概念
  10.2 光照模型
  10.3 明暗处理
  10.4 材质

本章是比较重要的一部分,前几章只是基础,在这章里将着重讲述光照模型、明暗处理、光源设置、材质定义以及有关的计算机图形学概念等内容,介绍如何运用OpenGL函数来绘制真实感图形,充分展示三维图形世界的魅力。如果读者以前从未接触过这方面的概念,要直接应用OpenGL函数库编写三维图形程序,是很困难的。因此,我们将结合OpenGL的特殊性简洁明了地介绍一些有关真实感图形绘制的概念,帮助读者理解基本的OpenGL光照、材质等函数的应用方式,顺利掌握最基本的真实感图形编程方法,引导读者快速进入OpenGL三维图形世界。

10.1、真实感图形基本概念
  真实感图形绘制是计算机图形学的一个重要组成部分,它综合利用数学、物理学、计算机科学和其它科学知识在计算机图形设备上生成象彩色照片那样的具有真实感的图形。一般说来,用计算机在图形设备上生成真实感图形必须完成以下四个步骤:一是用建模,即用一定的数学方法建立所需三维场景的几何描述,场景的几何描述直接影响图形的复杂性和图形绘制的计算耗费;二是将三维几何模型经过一定变换转为二维平面透视投影图;三是确定场景中所有可见面,运用隐藏面消隐算法将视域外或被遮挡住的不可见面消去;四是计算场景中可见面的颜色,即根据基于光学物理的光照模型计算可见面投射到观察者眼中的光亮度大小和颜色分量,并将它转换成适合图形设备的颜色值,从而确定投影画面上每一象素的颜色,最终生成图形。
  由于真实感图形是通过景物表面的颜色和明暗色调来表现景物的几何形状、空间位置以及表面材料的,而一个物体表面所呈现的颜色是由表面向视线方向辐射的光能决定的。在计算机图形学中,常采用一个既能表示光能大小又能表示其颜色组成的物理量即光亮度(luminance)或光强(intensity of light)来描述物体表面朝某方向辐射光能的颜色。采用这个物理量可以正确描述光在物体表面的反射、透射和吸收现象,因而可以正确计算处物体表面在空间给定方向上的光能颜色。
  物体表面向空间给定方向辐射的光强可应用光照模型进行计算。简单的光照模型通常假定物体表面是光滑的且由理想材料构成,因此只考虑光源照射在物体表面产生的反射光,所生成的图形可以模拟处不透明物体表面的明暗过渡,具有一定的真实感效果。复杂的光照模型除了考虑上述因素外,还要考虑周围环境的光对物体表面的影响。如光亮平滑的物体表面会将环境中其它物体映像在表面上,而通过透明物体也可看到其后的环境景象。这类光照模型称为整体光照模型,它能模拟出镜面映像、透明等较精致的光照效果。为了更真实的绘制图形,还要考虑物体表面的细节纹理,这通常使用一种称为“纹理映射”(texture mapping)的技术把已有的平面花纹图案映射到物体表面上,并在应用光照模型时将这些花纹的颜色考虑进去,物体表面细节的模拟使绘制的图形更接近自然景物。
  以上内容中,真实感图形绘制的四大步骤前两步在前面的章节已经详细介绍过,这里不再重复,第三步OpenGL将自动完成所有消隐过程,第四步下面几节详述。另外,部分复杂光照模型应用将在后续章节里介绍。

10.2、光照模型

  10.2.1 简单光照模型
  当光照射到一个物体表面上时,会出现三种情形。首先,光可以通过物体表面向空间反射,产生反射光。其次,对于透明体,光可以穿透该物体并从另一端射出,产生透射光。最后,部分光将被物体表面吸收而转换成热。在上述三部分光中,仅仅是透射光和反射光能够进入人眼产生视觉效果。这里介绍的简单光照模型只考虑被照明物体表面的反射光影响,假定物体表面光滑不透明且由理想材料构成,环境假设为由白光照明。
  一般来说,反射光可以分成三个分量,即环境反射、漫反射和镜面反射。环境反射分量假定入射光均匀地从周围环境入射至景物表面并等量地向各个方向反射出去,通常物体表面还会受到从周围环境来的反射光(如来自地面、天空、墙壁等的反射光)的照射,这些光常统称为环境光(Ambient Light);漫反射分量表示特定光源在景物表面的反射光中那些向空间各方向均匀反射出去的光,这些光常称为漫射光(Diffuse Light);镜面反射光为朝一定方向的反射光,如一个点光源照射一个金属球时会在球面上形成一块特别亮的区域,呈现所谓“高光(Highlight)”,它是光源在金属球面上产生的镜面反射光(Specular Light)。对于较光滑物体,其镜面反射光的高光区域小而亮;相反,粗糙表面的镜面反射光呈发散状态,其高光区域大而不亮。下面先看一个简单的光照例程。

  例10-1 简单光照例程light0.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 light_position[] = { 1.0, 1.0, 1.0, 0.0 };
 
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);
 
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}
 
void CALLBACK display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  auxSolidSphere(1.0);
  glFlush();
}
 
void CALLBACK 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();
}
 
void main(void)
{
  auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
  auxInitPosition (0, 0, 500, 500);
  auxInitWindow ("Simple Lighting");
  myinit();
  auxReshapeFunc (myReshape);
  auxMainLoop(display);
}
  以上程序运行结果是显示一个具有灰色光影的球。其中函数myinit()中包含了关键的设定光源位置、启动光照等几句,而其它程序语言几乎与以前的没有多大区别,但效果却完全不一样。下面几个小节将详细介绍有关函数的用法。

 
图10-1 带光影的灰色球体

  10.2.2 OpenGL光组成
  在OpenGL简单光照模型中的几种光分为:辐射光(Emitted Light)、环境光(Ambient Light)、漫射光(Diffuse Light)、镜面光(Specular Light)。
  辐射光是最简单的一种光,它直接从物体发出并且不受任何光源影响。
  环境光是由光源发出经环境多次散射而无法确定其方向的光,即似乎来自所有方向。一般说来,房间里的环境光成分要多些,户外的相反要少得多,因为大部分光按相同方向照射,而且在户外很少有其他物体反射的光。当环境光照到曲面上时,它在各个方向上均等地发散(类似于无影灯光)。
  漫射光来自一个方向,它垂直于物体时比倾斜时更明亮。一旦它照射到物体上,则在各个方向上均匀地发散出去。于是,无论视点在哪里它都一样亮。来自特定位置和特定方向的任何光,都可能有散射成分。
  镜面光来自特定方向并沿另一方向反射出去,一个平行激光束在高质量的镜面上产生100%的镜面反射。光亮的金属和塑料具有很高非反射成分,而象粉笔和地毯等几乎没有反射成分。因此,三某种意义上讲,物体的反射程度等同于其上的光强(或光亮度)。

  10.2.3 创建光源Light Source
  光源有许多特性,如颜色、位置、方向等。选择不同的特性值,则对应的光源作用在物体上的效果也不一样,这在以后的章节中会逐步介绍的。下面详细讲述定义光源特性的函数glLight*():
void glLight{if}[v](GLenum light , GLenum pname, TYPE param)
  创建具有某种特性的光源。其中第一个参数light指定所创建的光源号,如GL_LIGHT0、GL_LIGHT1、...、GL_LIGHT7。第二个参数pname指定光源特性,这个参数的辅助信息见表10-1所示。最后一个参数设置相应的光源特性值。

 
pname 参数名 缺省值 说明
GL_AMBIENT (0.0, 0.0, 0.0, 1.0) RGBA模式下环境光
GL_DIFFUSE (1.0, 1.0, 1.0, 1.0) RGBA模式下漫反射光
GL_SPECULAR (1.0,1.0,1.0,1.0) RGBA模式下镜面光
GL_POSITION (0.0,0.0,1.0,0.0) 光源位置齐次坐标(x,y,z,w)
GL_SPOT_DIRECTION (0.0,0.0,-1.0) 点光源聚光方向矢量(x,y,z)
GL_SPOT_EXPONENT 0.0 点光源聚光指数
GL_SPOT_CUTOFF 180.0 点光源聚光截止角
GL_CONSTANT_ATTENUATION 1.0 常数衰减因子
GL_LINER_ATTENUATION 0.0 线性衰减因子
GL_QUADRATIC_ATTENUATION 0.0 平方衰减因子
表10-1 函数glLight*()参数pname说明

   注意:以上列出的GL_DIFFUSE和GL_SPECULAR的缺省值只能用于GL_LIGHT0,其他几个光源的GL_DIFFUSE和GL_SPECULAR缺省值为(0.0,0.0,0.0,1.0)。另外,表中后六个参数的应用放在下一篇中介绍。在上面例程中,光源的创建为:
 
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);

  其中light_position是一个指针,指向定义的光源位置齐次坐标数组。其它几个光源特性都为缺省值。同样,我们也可用类似的方式定义光源的其他几个特性值,例如:
 

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_AMBIENT , light_ambient );
glLightfv(GL_LIGHT0, GL_DIFFUSE , light_diffuse );
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);

 10.2.4 启动光照
  在OpenGL中,必须明确指出光照是否有效或无效。如果光照无效,则只是简单地将当前颜色映射到当前顶点上去,不进行法向、光源、材质等复杂计算,那么显示的图形就没有真实感,如前几章例程运行结果显示。要使光照有效,首先得启动光照,即:
 

glEnable(GL_LIGHTING);

若使光照无效,则调用gDisable(GL_LIGHTING)可关闭当前光照。然后,必须使所定义的每个光源有效,例light0.c中只用了一个光源,即:
 

glEnable(GL_LIGHT0);

  其它光源类似,只是光源号不同而已。

10.3、明暗处理
  在计算机图形学中,光滑的曲面表面常用多边形予以逼近和表示,而每个小多边形轮廓(或内部)就用单一的颜色或许多不同的颜色来勾画(或填充),这种处理方式就称为明暗处理。在OpenGL中,用单一颜色处理的称为平面明暗处理(Flat Shading),用许多不同颜色处理的称为光滑明暗处理(Smooth Shading),也称为Gourand明暗处理(Gourand Shading)。设置明暗处理模式的函数为:

 

void glShadeModel(GLenum mode);
  函数参数为GL_FLAT或GL_SMOOTH,分别表示平面明暗处理和光滑明暗处理。
  应用平面明暗处理模式时,多边形内每个点的法向一致,且颜色也一致;应用光滑明暗处理模式时,多边形所有点的法向是由内插生成的,具有一定的连续性,因此每个点的颜色也相应内插,故呈现不同色。这种模式下,插值方法采用的是双线性插值法,如图10-2所示。

 
图10-2 Gouraud明暗处理

  Gouraud明暗处理通常算法为:先用多边形顶点的光强线性插值出当前扫描线与多边形边交点处的光强,然后再用交点的光强线插值处扫描线位于多边形内区段上每一象素处的光强值。图中显示出一条扫描线与多边形相交,交线的端点是A点和B点,P点是扫描线上位于多边形内的任一点,多边形三个顶点的光强分别为I1、I2和I3.取A点的光强Ia为I1和I2的线性插值,B点的光强Ib为I1和I3的线性插值,P点的光强Ip则为Ia和Ib的线性插值。采用Gouraud明暗处理不但可以使用多边形表示的曲面光强连续,而且计算量很小。这种算法还可以以增量的形式改进,且能用硬件直接实现算法,从而广泛用于计算机实时图形生成。请看下面光滑明暗处理的例程:

  例10-2 明暗处理例程Shading.c
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
 
void myinit(void);
void object(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
 
/* GL_SMOOTH is actually the default shading model. */
void myinit (void)
{
  glShadeModel (GL_SMOOTH);
}
 
void object(void)
{
  glBegin (GL_POLYGON);
  glColor3f (1.0, 0.0, 0.0);
  glVertex2f (4.0, 4.0);
  glColor3f(1.0,1.0,1.0);
  glVertex2f (12.0, 4.0);
  glColor3f(0.0,0.0,1.0);
  glVertex2f (12.0, 12.0);
  glColor3f(0.0,1.0,0.0);
  glVertex2f (4.0, 12.0);
  glEnd ();
}
 
void CALLBACK display(void)
{
  glClear (GL_COLOR_BUFFER_BIT);
  object ();
  glFlush ();
}
 
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= h)
    gluOrtho2D (0.0, 16.0, 0.0, 16.0 * (GLfloat) h/(GLfloat) w);
  else
    gluOrtho2D (0.0, 16.0 * (GLfloat) w/(GLfloat) h, 0.0, 16.0);
  glMatrixMode(GL_MODELVIEW);
}
 
void main(void)
{
  auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
  auxInitPosition (0, 0, 500, 500);
  auxInitWindow ("Smooth Shading");
  myinit();
  auxReshapeFunc (myReshape);
  auxMainLoop(display);
}
  以上程序运行结果是在屏幕上显示一个色彩连续变化的三角形。这个程序是用的RGBA显示模式,若改用颜色表模式,则颜色内插实际上是颜色表的内插,因此呈现的颜色可能不连续。网友不妨自己试试。
  另外,若在light0.c程序中加上一句定义GL_FLAT明暗处理模式,则又会出现怎样的情形呢?读者可以仔细比较一下。

 
图10-3 高氏明暗处理的正方形

10.4、材质

  10.4.1 材质颜色
  OpenGL用材料对光的红、绿、蓝三原色的反射率来近似定义材料的颜色。象光源一样,材料颜色也分成环境、漫反射和镜面反射成分,它们决定了材料对环境光、漫反射光和镜面反射光的反射程度。在进行光照计算时,材料对环境光的反射率与每个进入光源的环境光结合,对漫反射光的反射率与每个进入光源的漫反射光结合,对镜面光的反射率与每个进入光源的镜面反射光结合。对环境光与漫反射光的反射程度决定了材料的颜色,并且它们很相似。对镜面反射光的反射率通常是白色或灰色(即对镜面反射光中红、绿、蓝的反射率相同)。镜面反射高光最亮的地方将变成具有光源镜面光强度的颜色。例如一个光亮的红色塑料球,球的大部分表现为红色,光亮的高光将是白色的。

  10.4.2 材质定义
  材质的定义与光源的定义类似。其函数为:

  
void glMaterial{if}[v](GLenum face,GLenum pname,TYPE param);


  定义光照计算中用到的当前材质。face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明当前材质应该应用到物体的哪一个面上;pname说明一个特定的材质;param是材质的具体数值,若函数为向量形式,则param是一组值的指针,反之为参数值本身。非向量形式仅用于设置GL_SHINESS。pname参数值具体内容见表10-1。另外,参数GL_AMBIENT_AND_DIFFUSE表示可以用相同的RGB值设置环境光颜色和漫反射光颜色。

 
参数名 缺省值 说明
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_SHINESS 0.0 镜面指数(光亮度)
GL_EMISSION (0.0, 0.0, 0.0, 1.0) 材料的辐射光颜色
GL_COLOR_INDEXES (0, 1, 1) 材料的环境光、漫反射光和镜面光颜色
表10-2 函数glMaterial*()参数pname的缺省值

  例10-3 材质定义例程light1.c
#include "glos.h"
#include <GL/gl.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.8,0.8,0.8,1.0};
  GLfloat mat_diffuse[]={0.8,0.0,0.8,1.0}; /* 紫色 */
  GLfloat mat_specular[] = { 1.0, 0.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_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_POSITION, light_position);
 
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}
 
void CALLBACK display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  auxSolidSphere(1.0);
  glFlush();
}
 
void CALLBACK 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();
}
 
void main(void)
{
  auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH16);
  auxInitPosition (0, 0, 500, 500);
  auxInitWindow ("Lighting_1 ");
  myinit();
  auxReshapeFunc (myReshape);
  auxMainLoop(display);
}
 

  以上程序运行结果是一个紫色的球。在函数myinit()中定义了球的材质颜色,光源的定义仍延用light0.c中的,而light.c物体的光源定义为缺省形式。从例子中明显地看出,物体的材质颜色定义与光源颜色定义几乎一样,物体反射到眼中的颜色与二者都有关系,具体关系请看下一小节。

  10.4.3 材质RGB值和光源RGB值的关系
  材质的颜色与光源的颜色有些不同。对于光源,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)。
  同样,如果有两束光,相应的值分别为(R1、G1、B1)和(R2、G2、B2),则OpenGL将各个颜色成分相加,得到(R1+R2、G1+G2、B1+B2),若任一成分的和值大于1(超出了设备所能显示的亮度)则约简到1.0。下面一例程就说明了二者之间的关系。

  例10-4 材质与光源的RGB关系例程light2.c
 

#include "glos.h"
#include <GL/gl.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.8, 0.8, 0.8, 1.0 };
  GLfloat mat_diffuse[]= { 0.8, 0.0, 0.8, 1.0 }; /* 紫色 */
  GLfloat mat_specular[] = { 1.0, 0.0, 1.0, 1.0 };
  GLfloat mat_shininess[] = { 50.0 };
 
  GLfloat light_diffuse[]= { 0.0, 0.0, 1.0, 1.0}; /* 蓝色 */
  GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.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, light_diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);
 
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}
 
void CALLBACK display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  auxSolidSphere(1.0);
  glFlush();
}
 
void CALLBACK 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();
}
 
void main(void)
{
  auxInitDisplayMode (AUX_SINGLE | AUX_RGBA | AUX_DEPTH16);
  auxInitPosition (0, 0, 500, 500);
  auxInitWindow ("Lighting_2 ");
  myinit();
  auxReshapeFunc (myReshape);
  auxMainLoop(display);
}
  以上程序运行结果是一个蓝色的球,其中高光部分仍为上一例的亮紫色。从上可看出,球漫反射光的结果是mat_diffuse[]与light_diffuse[]中的三个颜色分量值相乘,即 (0.0*1.0,0.0*1.0,0.8*1.0,1.0*1.0)=(0.0,0.0,0.8,1.0),所以球大部分呈现蓝色。

 
图10-4 光照蓝色球高光为红色

  10.4.4 材质改变
  在实际应用的许多情况下,不同的物体或同一物体的不同部分都有可能设置不同的材质,OpenGL函数库提供了两种方式实现这种要求。下面一例程采用的是设置矩阵堆栈来保存不同物体的材质信息:

  例10-5 矩阵堆栈改变材质例程chgmat1.c
 
#include "glos.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
 
void myinit(void);
void CALLBACK display(void);
void CALLBACK myReshape(GLsizei w, GLsizei h);
 
/* 初始化z-buffer、光源和光照模型,在此不具体定义材质。*/
void myinit(void)
{
  GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
  GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat position[] = { 0.0, 3.0, 2.0, 0.0 };
  GLfloat lmodel_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
 
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
 
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, position);
 
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
 
  glClearColor(0.0, 0.1, 0.1, 0.0);
}
 
void CALLBACK display(void)
{
  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(1.0);
  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(1.0);
  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(1.0);
  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(1.0);
  glPopMatrix();
 
  /* 第二行第一列绘制的球有漫反射光和环境光,而镜面反射光。*/
  glPushMatrix();
  glTranslatef (-3.75, 0.0, 0.0);
  glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
  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(1.0);
  glPopMatrix();
 
  /* 第二行第二列绘制的球有漫反射光、环境光和镜面光,且有低高光。*/
  glPushMatrix();
  glTranslatef (-1.25, 0.0, 0.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, low_shininess);
  glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
 
  /* 第二行第三列绘制的球有漫反射光、环境光和镜面光,且有很亮的高光。*/
  glPushMatrix();
  glTranslatef (1.25, 0.0, 0.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, high_shininess);
  glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
 
  /* 第二行第四列绘制的球有漫反射光、环境光和辐射光,而无镜面光。*/
  glPushMatrix();
  glTranslatef (3.75, 0.0, 0.0);
  glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
  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(1.0); glPopMatrix();
 
  /* 第三行第一列绘制的球有漫反射光和有颜色的环境光,而无镜面光。*/
  glPushMatrix();
  glTranslatef (-3.75, -3.0, 0.0);
  glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
  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(1.0);
  glPopMatrix();
 
  /* 第三行第二列绘制的球有漫反射光和有颜色的环境光以及镜面光,且有低高光。*/
  glPushMatrix();
  glTranslatef (-1.25, -3.0, 0.0);
  glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
  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(1.0);
  glPopMatrix();
 
  /* 第三行第三列绘制的球有漫反射光和有颜色的环境光以及镜面光,且有很亮的高光。*/
  glPushMatrix();
  glTranslatef (1.25, -3.0, 0.0);
  glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
  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(1.0);
  glPopMatrix();
 
  /* 第三行第四列绘制的球有漫反射光和有颜色的环境光以及辐射光,而无镜面光。*/
  glPushMatrix();
  glTranslatef (3.75, -3.0, 0.0);
  glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
  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(1.0);
  glPopMatrix();
 
  glFlush();
}
 
void CALLBACK myReshape(GLsizei w, GLsizei h)
{
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= (h * 2))
    glOrtho (-6.0, 6.0, -3.0*((GLfloat)h*2)/(GLfloat)w,
    3.0*((GLfloat)h*2)/(GLfloat)w, -10.0, 10.0);
  else
    glOrtho (-6.0*(GLfloat)w/((GLfloat)h*2),
    6.0*(GLfloat)w/((GLfloat)h*2), -3.0, 3.0, -10.0, 10.0);
  glMatrixMode(GL_MODELVIEW);
}
 
void main(void)
{
  auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
  auxInitPosition (0, 0, 600, 450);
  auxInitWindow ("Material");
  myinit();
  auxReshapeFunc (myReshape);
  auxMainLoop(display);
}
图10-5 多种光和材质的变化效果

  以上程序运行结果是绘制12个球(3行4列)。第一行的球材质都没有环境反射光,第二行的都有一定的环境反射光,第三行的都有某种颜色的环境光。而第一列的球材质仅有蓝色的漫反射光;第二列的不仅有蓝漫反射光,而且还有镜面反射光,较低的高光;第三列的不仅有蓝漫反射光,而且还有镜面反射光,很亮的高光;第四列的还包括辐射光,但无镜面光。
  这个程序运用矩阵堆栈多次调用glMaterialfv()来设置每个球的材质,也就是改变同一场景中的不同物体的颜色。但由于这个函数的应用有个性能开销,因此建议最好尽可能少的改变材质,以减少改变材质时所带来的性能开销,可采用另一种方式即改变材质颜色,相应函数为glColorMaterial(),说明如下:
void glColorMaterial(GLenum face,GLenum mode);

  函数参数face指定面,值有GL_FRONT、GL_BACK或GL_FRONT_AND_BACK(缺省值)。mode指定材质成分,值有GL_AMBIENT、GL_DIFFUSE、GL_AMBIENT_AND_DIFFUSE(缺省值)、GL_SPECULAR或GLEMISSION。
  注意:这个函数说明了两个独立的值,第一个参数说明哪一个面和哪些面被修改,而第二个参数说明这些面的哪一个或哪些材质成分要被修改。OpenGL并不为每一种face保持独立的mode变量。在调用glColorMterial()以后,首先需要用GL_COLOR_MATERIAL作为参数调用glEnable()来启动颜色材质,然后在绘图时调用glColor*()来改变当前颜色,或用glMaterial()来改变材质成分。当不用这种方式来改变材质时,可调用glDisable(GL_COLOR_MATERIAL)来关闭取消。如下面一段代码:
 

glColorMaterial(GL_FRONT,GL_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
glColor3f(0.3,0.5,0.7);
/* draw some objects here. */
glcolor3f(0.0,1.0,0.0);
/* draw other objects here.*/
glDisable(GL_COLOR_MATERIAL);

  当需要改变场景中大部分方面的单个材质时,最好调用glColorMaterial();当需要修改不止一个材质参数时,最好调用glMaterial*()。注意,当不需要颜色材质时一定要关闭它,以避免相应的开销。下面来看一个颜色材质的具体应用例子:

  例10-6 颜色定义改变材质例程chgmat2.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 light_position[] = { 1.0, 1.0, 1.0, 0.0 };
 
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);
 
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
 
  glColorMaterial(GL_FRONT, GL_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);
}
 
void CALLBACK display(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
  /* draw one yellow ball */
  glLoadIdentity();
  glTranslatef(-0.7,0.0,0.0);
  glColor3f(1.0,1.0,0.0);
  auxSolidSphere(0.5);
 
  /* draw one red cone */
  glLoadIdentity();
  glRotatef(-65.0,1.0,0.0,0.0);
  glTranslatef(0.7,0.0,0.0);
  glColor3f(1.0,0.0,0.0);
  auxSolidCone(0.4,0.6);
 
  glFlush();
}
 
void CALLBACK 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();
}
 
void main(void)
{
  auxInitDisplayMode (AUX_SINGLE | AUX_RGB | AUX_DEPTH16);
  auxInitPosition (0, 0, 500, 500);
  auxInitWindow ("ColorMaterial Mode");
  myinit();
  auxReshapeFunc (myReshape);
  auxMainLoop(display);
}
 以上程序改变的是漫反射颜色。场景中显示了一个黄色的球和一个红色的锥体。

 
图10-6 漫反射材质改变

posted on 2006-01-17 01:03 zmj 阅读(16265) 评论(3)  编辑 收藏 引用

评论

# re: OpenGL光照 2012-05-27 23:52 lowry

好厉害啊,顶一个~~~~~~~~~~~  回复  更多评论   

# re: OpenGL光照 2012-10-09 00:47 lcy

好厉害!虽然图都看不到还是帮大忙了!  回复  更多评论   

# re: OpenGL光照 2012-10-14 13:20 lcy

glMaterialfv的改变的值好像是全局的而不是堆栈中 chgmat1的程序之所以成功是因为每个球之前都重新设置过而不是使用了矩阵堆栈  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理