一、提要

   今天在网上看到一个消息,芬兰IT服务公司Digia Oyj称,公司将会出资400万欧元购买诺基亚旗下QT软件业务芬兰IT服务公司Digia Oyj称,公司将会出资400万欧元购买诺基亚旗下QT软件业务。
   Qt是一个非常优秀的团队,希望Qt离开Nokia之后能够发展得更好吧。
   经过了上一篇的教程,我们对3D世界已经有了初步的认识,今天我们要学习的内容是:光照和纹理滤波。

二、光照和纹理滤波

    OpenGL有四种类型的光照:环境光,漫射光,镜面光,发射光。这四种成分都可以单独进行计算,并且可以叠加。
    环境光来自于四面八方。所有场景中的对象都处于环境光的照射中。漫射光由特定的光源产生,并在您的场景中的对象表面上产生反射。处于漫射光直接照射下的任何对象表面都变得很亮,
而几乎未被照射到的区域就显得要暗一些。镜面光来自一个特定的方向,类似一组平行光。
发射光用于模拟发光物体,不受任何光源影响。
    关于光的颜色和材料的颜色有必要来说明一下。
    对于光线而言,颜色成分的数量对应于每种颜色的完全
强度百分比。如果一种光线颜色的R,G和B的值都为1.0,这种光线就是可以实现的最量的白色。如果都是0.5,那么这种光线还是白色,但强度只有一般,因此看山区像是灰色。如果R=G=1并且B=0(完全的红和绿,没有蓝),这种光线看上去就是×××。
    对于材料而言,它的RGB值和它对这些颜色的翻色比例相对应。因此,如果一种材料的R=1,G=0.5、B=0,那么它将反射所有的入射光,但是不反射任何入射蓝光。
    这篇教程我们暂讨论环境光和漫射光。
    首先在nehewidget.h中加入几个变量。
 
  
           //纹理存储数组            GLuint texture[3];            //场景深入屏幕的距离            GLfloat zoom;            //立方体在X轴和Y轴上旋转的速度            GLfloat xSpeed, ySpeed;            //表明使用哪个纹理            GLuint filter;            //是否开启灯光            bool isLighting;


 
 
  
然后在neheWidget.cpp中加入光源的信息数组:
 
  
GLfloat lightAmbient[4] = { 0.5, 0.5, 0.5, 1.0 }; GLfloat lightDiffuse[4] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat lightPosition[4] = { 0.0, 0.0, 2.0, 1.0 };


 
 
  
        创建光源的过程和颜色的创建完全一致。前三个参数分别是RGB三色分量,最后一个是alpha通道参数。
因此,第一行有关lightAmbient的代码使我们得到的是半亮(0.5)的白色环境光。如果没有环境光,未被漫射光照到的地方会变得十分黑暗。
第二行有关lightDiffuse的代码使我们生成最亮的漫射光。所有的参数值都取成最大值1.0。它将照在我们木板箱的前面,看起来挺好。
第三行有关lightPosition的代码使我们保存光源的位置。前三个参数和glTranslate中的一样。依次分别是XYZ轴上的位移。
 
  
在构造函数中对变量进行初始化:
 
 
  
 
    zoom = -5.0;       xSpeed = ySpeed = 0.0;       filter = 0;       isLighting = false;  装载纹理,这个在上一篇教程中已经实现了,不过这次要换成木箱的纹理。  void NeHeWidget::loadGLTextures() {         QImage tex, buf;       if ( !buf.load( ":/data/Crate.bmp" ) )       {         //如果载入不成功,自动生成一个128*128的32位色的绿×××片。           qWarning("Could not read image file!");           QImage dummy( 128, 128,QImage::Format_RGB32 );            dummy.fill( Qt::green );              buf = dummy;       }       //转换成纹理类型       tex = QGLWidget::convertToGLFormat( buf );       //创建纹理       glGenTextures( 3, &texture[0] );       //使用来自位图数据生成的典型纹理,将纹理名字texture[0]绑定到纹理目标上       glBindTexture( GL_TEXTURE_2D, texture[0] );       glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,       GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );       //绑定第二个纹理       glBindTexture( GL_TEXTURE_2D, texture[1] );       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );       glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,       GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );       //绑定第三个纹理       glBindTexture( GL_TEXTURE_2D, texture[2] );       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );       gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGB, tex.width(), tex.height(), GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );       } 

 
  
        装载好bmp图片之后,我们对纹理进行了三次绑定,采用了不同的滤波方式。
        上次我们采用的是线性滤波方式,这需要机器有相当高的处理能力,但它们看起来很不错。这一课中,我们接着要创建的第一种纹理使用GL_NEAREST方式。从原理上讲,这种方式没有真正进行滤波。它只占用很小的处理能力,看起来也很差。唯一的好处是这样我们的工程在很快和很慢的机器上都可以正常运行。您会注意到我们在MIN和MAG时都采用了GL_NEAREST,你可以混合使用GL_NEAREST和GL_LINEAR。纹理看起来效果会好些,但我们更关心速度,所以全采用低质量贴图。MIN_FILTER在图像绘制时小于贴图的原始尺寸时采用。MAG_FILTER在图像绘制时大于贴图的原始尺寸时采用。
这其实在游戏编程中有用到,比如在大的场景中,有很多很多的模型,但远处的模型并不是很重要,所以用GL_NEAREST方式就可以节约非常多的资源。
          最后一个纹理用到了新的纹理创建方法:Mipmapping!您可能会注意到当图像在屏幕上变得很小的时候,很多细节将会丢失。刚才还很不错的图案变得很难看。当您告诉OpenGL创建一个 mipmapped的纹理后,OpenGL将尝试创建不同尺寸的高质量纹理。当您向屏幕绘制一个mipmapped纹理的时候,OpenGL将选择它已经创建的外观最佳的纹理(带有更多细节)来绘制,而不仅仅是缩放原先的图像(这将导致细节丢失)。
gluBuild2DMipmaps可以使用任意的位图来创建纹理。OpenGL将自动将它缩放到正常的大小。
        修改paintGL().
 
  
        void NeHeWidget::paintGL()         {             // 清除屏幕和深度缓存             glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );             glLoadIdentity();             //移到屏幕的左半部分,并且将视图推入屏幕背后足够的距离以便我们可以看见全部的场景             glTranslatef(0.0f,0.0f,zoom);             glRotatef( xRot,  1.0,  0.0,  0.0 );             glRotatef( yRot,  0.0,  1.0,  0.0 );             //选择使用的纹理             glBindTexture( GL_TEXTURE_2D, texture[filter] );             glBegin( GL_QUADS );                 glNormal3f( 0.0, 0.0, 1.0 );                 glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0,  1.0 );                 glTexCoord2f( 1.0, 0.0 ); glVertex3f(  1.0, -1.0,  1.0 );                 glTexCoord2f( 1.0, 1.0 ); glVertex3f(  1.0,  1.0,  1.0 );                 glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0,  1.0,  1.0 );                 glNormal3f( 0.0, 0.0, -1.0 );                 glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );                 glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0,  1.0, -1.0 );                 glTexCoord2f( 0.0, 1.0 ); glVertex3f(  1.0,  1.0, -1.0 );                 glTexCoord2f( 0.0, 0.0 ); glVertex3f(  1.0, -1.0, -1.0 );                 glNormal3f( 0.0, 1.0, 0.0 );                 glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0,  1.0, -1.0 );                 glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0,  1.0,  1.0 );                 glTexCoord2f( 1.0, 0.0 ); glVertex3f(  1.0,  1.0,  1.0 );                 glTexCoord2f( 1.0, 1.0 ); glVertex3f(  1.0,  1.0, -1.0 );                 glNormal3f( 0.0, -1.0, 0.0 );                 glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, -1.0, -1.0 );                 glTexCoord2f( 0.0, 1.0 ); glVertex3f(  1.0, -1.0, -1.0 );                 glTexCoord2f( 0.0, 0.0 ); glVertex3f(  1.0, -1.0,  1.0 );                 glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0,  1.0 );                 glNormal3f( 1.0, 0.0, 0.0 );                 glTexCoord2f( 1.0, 0.0 ); glVertex3f(  1.0, -1.0, -1.0 );                 glTexCoord2f( 1.0, 1.0 ); glVertex3f(  1.0,  1.0, -1.0 );                 glTexCoord2f( 0.0, 1.0 ); glVertex3f(  1.0,  1.0,  1.0 );                 glTexCoord2f( 0.0, 0.0 ); glVertex3f(  1.0, -1.0,  1.0 );                 glNormal3f( -1.0, 0.0, 0.0 );                 glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 );                 glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0,  1.0 );                 glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0,  1.0,  1.0 );                 glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0,  1.0, -1.0 );                 glEnd();                 xRot += 0.3;                 yRot += 0.2;                 }         代码和之前的类似,就不解释了。         接着在initializeGL()中添加关于光源的代码。    glLightfv( GL_LIGHT1, GL_AMBIENT, lightAmbient );   glLightfv( GL_LIGHT1, GL_DIFFUSE, lightDiffuse );   glLightfv( GL_LIGHT1, GL_POSITION, lightPosition );   glEnable( GL_LIGHT1 );   glEnable(GL_LIGHTING);


 
 
  
        第一行设置环境光的发光量,光源GL_LIGHT1开始发光。这一课的开始处我们我们将环境光的发光量存放在lightAmbient数组中。现在我们就使用此数组(半亮度环境光)。
接下来我们设置漫射光的发光量。它存放在lightDiffuse数组中(全亮度白光)。
然后设置光源的位置。位置存放在lightPosition 数组中(正好位于木箱前面的中心,X-0.0,Y-0.0,Z方向移向观察者2个单位,位于屏幕外面)。
最后,我们启用一号光源。我们还没有启用GL_LIGHTING,所以您看不见任何光线。记住:只对光源进行设置、定位、甚至启用,光源都不会工作。除非我们启用GL_LIGHTING。
 
  
        接下来我们要用键盘上的L键来控制灯光的亮暗,选择纹理,控制物理距离。思路是在nehewidget类
加入关于控制的共有接口,然后manwindow接收键盘事件,再通过接口来控制灯光。
        首先在nehewidget.h中添加声明:
 
     
   public:         //返回灯光是否开启的状态         bool isLighting();         //开启灯光         void turnOnLight();         //关闭灯光         void turnOffLight();         然后在nehewidget.cpp里实现方法:         bool NeHeWidget::isLighting()         {             return this->isLight;         }         void NeHeWidget::turnOffLight()         {             this->isLight=false;             glDisable(GL_LIGHT1 );             qWarning("Off");         }         void NeHeWidget::turnOnLight()         {             this->isLight=true;             glEnable( GL_LIGHT1 );             qWarning("ON");         } 

 
  
         在manwindow.cpp的按键事件函数里,添加以下代码:
 
 
  
         case Qt::Key_L:          if ( neheWidget->isLighting()) neheWidget->turnOffLight();          else neheWidget->turnOnLight();          neheWidget->updateGL();          qWarning("LLLL");          break;          case Qt::Key_F:          neheWidget->changeFilter();          break;          case Qt::Key_PageUp:          neheWidget->zoomOut();          break;          case Qt::Key_PageDown:          neheWidget->zoomIn();          break; 


编译运行,就可以看到下面的效果:
这是灯打开的状态
灯关闭的状态。
 
下面是不同纹理滤波
 
 
  
 
  

四.参考资料

1.      《 OpenGL Reference Manual 》, OpenGL 参考手册

2.      《 OpenGL 编程指南》(《 OpenGL Programming Guide 》), Dave Shreiner , Mason Woo , Jackie Neider , Tom Davis 著,徐波译,机械工业出版社

3.         《win32 OpenGL编程 》   一个大牛的博客     http://blog.csdn.net/vagrxie/article/category/628716/3
4.       《OpenGL函数思考 》   里面有很多OpenGL函数的通俗解释     http://blog.csdn.net/shuaihj
5.       《nehe的OpenGL教程》 比较通俗易懂的OpenGL教程    http://nehe.gamedev.net/