OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)

         

      本次实验主要是学习下opengl中光照的使用方法,opengl中的光照分为环境光,漫射光,镜面光,反射光4种,这里主要是学习环境光和漫射光的设置,同时对比下opengl中支持的几种纹理滤波方式的效果,另外也可以加入色彩融合效果。

 

  纹理滤波

     在上篇文章OpenGL_Qt学习笔记之_05(纹理映射)中我们采用的是GL_LINEAR方式进行滤波的,opengl还支持GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR等几种方式,其中mipmap为计算量最大的方式,因为它保留了纹理的很多细节。

     由于我们不想让纹理图片的像素限制为2的n次方,所以在创建纹理时我们可以使用gluBuild2DMipmaps()函数代替glTexImage2D(),而gluBuild2DMipmaps()又是以glu开头的。

  其实我在opengl学习系列的第一篇文章中就发现了Qt新版本中不能使用opengl的glu开头的函数,文章为OpenGL_Qt学习笔记之_01(创建一个OpenGL窗口),因为那个时候初学,所以遇到编译找不到gluPerspective()标识符时,我直接把这个函数屏蔽掉了,反正当时也不理解该函数的功能。而在本人博客OpenGL_Qt学习笔记之_02(绘制简单平面几何图形)中也发现没有使用gluPerspective()函数时,画几何图形时,坐标和网上很多人的例子不同,我这里全是-1~+1之间的小数,因此这些函数的功能还是挺重要的,比不可少。

     下面给出我在windows平台下的解决方法。

      http://hi.baidu.com/%B3%A4%C6%BD%C5%A9%B7%F2/blog/item/573cb1f9dbf83a07d8f9fd53.html#0  

     过在它的方法步骤4中,在建立glut文件,是这样的:新建一个txt文件,里面输入代码:#include “glut.h”,然后把该txt文件的名字重命名为glut,注意这个地方不需要加后缀。另外需要特别注意的是,虽然我们把glut.h文件放入在qt目录下的\include\QtOpenGL下,且我们也在GLWidget.cpp中也加入了头文件#include,但是因为QtSDK库编译的原因,我们在GLWidget.cpp中仍然需要添加#include语句才行。

  既然上面提到了gluPerspective()对结果的影响很大,那么就很有必要了解该函数。在NeHe等人的教程中,该函数使用其各个参数设置为如下:

  gluPerspective( 45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0 );

  该函数是设置场景的透视效果的,所谓透视,可以简单的理解为越远处的物体看起来越小。所以这里的参数1,2,3表示的是透视是按照基于窗口宽度和高度的45度视角来计算的,参数0.1和参数100是我们场景中所能绘制深度的起点和终点值。

 

     光照

  Opengl中使用光照时,首先定义了3个跟光滑有关的数组:

  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 };

  LightAmbient数组用来设置环境光的参数,前面3个参数表示光的rgb分量为0.5,后面那个是alpha通道,alph为1表示不透明,所谓环境光就是指物体所在的环境中的光照,一般比较均匀,四面八方都有;lightDiffuse数组用来设置漫射光的参数,这里讲漫射光强度设置为最大,漫射光是指的特定位置发出的光;lightPosition就是该光源的位置。

 

  上面数组只是这些光源的参数,如果我们要设置光源,则应该使用下面的代码:

  /*opengl中支持8个光源,即GL_LIGHT0~GL_LIGHT7*/

  glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient);//指定光源1的环境光参数

  glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);//指定光源1的漫射光参数

  glLightfv(GL_LIGHT1, GL_POSITION, light_position);//指定光源1的位置

  glEnable(GL_LIGHT1);//允许光源1的使用

  glEnable(GL_LIGHTING);//我们还需要启动总光源开关

 

  然后在对空间物体进行贴图时,需要指定物体表面的法向量,使用下面的函数

  glNormal3f(x, y, z);

  该函数指定是法线的方向为向量(x, y, z)方向,在使用光源时,且对空间物体进行纹理映射时,每个面都需要指定其法线的方向,否则会出现各种意外的结果。

     在使用光照时,在前面课程基础上注意上面几点就Ok了。

 

  色彩融合

  色彩融合简单的说就是将2幅图片按照一定的透明度比例进行叠加(每个像素点都进行叠加),其公式如下所示:

  (Rs As + Rd (1 - As), Gs As + Gd (1 - As), Bs As + Bd (1 - As), As As + Ad (1 - As))

  其中Rs,Gs,Bs,As分别表示源目标图像的r,g,b,alpha分量值,Rd,Gd,Bd,Ad分别表示目标图像的r,g,b,alpha分量值。如果As等于0.5,则这个公式会生成透明/半透明的效果。

  上面的公式表示各颜色通道的alpha相同,且源像素因子和目标像素因子之和为1.一般情况下,源像素因子和目标像素因子采用的是:

  void WINAPI glBlendFunc(GLenum sfactor, GLenum dfactor);

  参数1为源像素因子的取值,我在程序中用的GL_SRC_ALPHA表示采用alpha通道的取值;参数2表示目标像素因子的取值,程序中用的是GL_ONE,表示目标像素因子取值1.0.具体该函数的用法可以参考网友的这篇文章:http://www.cnblogs.com/yujunyong/archive/2011/04/13/2015467.html

         

 

  实验说明

  这次实验是将一个木箱纹理贴到一个立方体上,然后我们在空间屏幕正外方设置了一个光源,可以用键盘的L键来控制该光源的开启和关闭;用F键来旋转程序中用到的3种滤波方式,按下F键后,依次切换该3种方式;用PageUp键来使物体离观察者越来越远,相反,用PageDown来使物体离我们越来越近;使用向上光标键加快物体旋转的速度,向下光标键减小物体旋转的速度。

  开发环境:windows+QtCreator2.5.1+Qt4.8.2

 

  实验结果:

  关闭灯光时效果:

  OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)_第1张图片

 

  开启灯光时效果:

  OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)_第2张图片

 

  三种纹理对比效果,纹理1:

  OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)_第3张图片

 

  纹理2:

  OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)_第4张图片

 

  纹理3:

  OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)_第5张图片

 

  将物体远离观察者效果:

  OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)_第6张图片

  

  开灯后无色彩融合效果:

  OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)_第7张图片

 

  开灯后有色彩融合效果:

  OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合)_第8张图片

 

 

  实验主要部分代码及注释(附录有工程code下载链接地址):

#include "glwidget.h"
#include "ui_glwidget.h"

#include 
#include 
#include 
#include 

/*c++中可以在类的外部定义变量*/
GLfloat light_ambient[4]={0.5, 0.5, 0.5, 1.0};
GLfloat light_diffuse[4]={1.0, 1.0, 1.0, 1.0};
GLfloat light_position[4]={0.0, 0.0, 2.0, 0.0};

GLWidget::GLWidget(QGLWidget *parent) :
    QGLWidget(parent),
    ui(new Ui::GLWidget)
{
  //  setCaption("The Opengl for Qt Framework");
    ui->setupUi(this);
    fullscreen = false;
    rotate_angle = 0.0;
    zoom = -5.0;
    rotate_speed = 3.0;
    filter = 0;
    light = false;
    blend = false;
}

//这是对虚函数,这里是重写该函数
void GLWidget::initializeGL()
{
    setGeometry(300, 150, 500, 500);//设置窗口初始位置和大小
    loadTextures();
    glEnable(GL_TEXTURE_2D);//允许采用2D纹理技术
    glShadeModel(GL_SMOOTH);//设置阴影平滑模式
    glClearColor(0.0, 0.0, 0.0, 0);//改变窗口的背景颜色,不过我这里貌似设置后并没有什么效果
    glClearDepth(1.0);//设置深度缓存
    glEnable(GL_DEPTH_TEST);//允许深度测试
    glDepthFunc(GL_LEQUAL);//设置深度测试类型
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//进行透视校正

    /*opengl中支持8个光源,即GL_LIGHT0~GL_LIGHT7*/
    glLightfv(GL_LIGHT1, GL_AMBIENT, light_ambient);//指定光源1的环境光参数
    glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);//指定光源1的漫射光参数
    glLightfv(GL_LIGHT1, GL_POSITION, light_position);//指定光源1的位置
    glEnable(GL_LIGHT1);//允许光源1的使用
  //  glEnable(GL_LIGHTING);//我们还需要启动总光源开关,默认的时候不开,后面的L键来控制开启和关闭

    glColor4f(1.0, 1.0, 1.0, 0.5);//后面的步骤都是以全亮绘制物体,并且50%的透明度
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
}

void GLWidget::paintGL()
{
    //glClear()函数在这里就是对initializeGL()函数中设置的颜色和缓存深度等起作用
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /*下面开始画立方体,并对其进行纹理映射*/
    glLoadIdentity();
    glTranslatef(0.0, 0.0, zoom);
    glRotatef(rotate_angle, -0.4f, 0.4f, -1.0f);
    glBindTexture(GL_TEXTURE_2D, texture[filter]);//这句代码一定要,因为在initializeGL()函数中已绑定一个固定的纹理目标了
    glBegin(GL_QUADS);
    //上顶面
    glNormal3f(0.0, 1.0, 0.0);//该函数指定是法线的方向为向量(x, y, z)方向,在使用光源时,且对空间物体进行纹理映射时,
                              //每个面都需要指定其法线的方向,否则会出现各种意外的结果。
    glTexCoord2f(0.0, 1.0);//将2D的纹理坐标映射到3D的空间物体表面上
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0f, 1.0f, -1.0f);
    //下顶面
    glNormal3f(0.0, -1.0, 0.0);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0f, -1.0f, -1.0f);
    //正前面
    glNormal3f(0.0, 0.0, 1.0);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0f, 1.0f, 1.0f);
    //右侧面
    glNormal3f(1.0, 0.0, 0.0);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(1.0f, 1.0f, -1.0f);
    //背后面
    glNormal3f(0.0, 0.0, -1.0);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    //左侧面
    glNormal3f(-1.0, 0.0, 0.0);
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glEnd();
    rotate_angle += rotate_speed;
}

//该程序是设置opengl场景透视图,程序中至少被执行一次(程序启动时).
void GLWidget::resizeGL(int width, int height)
{
    if(0 == height)
        height = 1;//防止一条边为0
    glViewport(0, 0, (GLint)width, (GLint)height);//重置当前视口,本身不是重置窗口的,只不过是这里被Qt给封装好了
    glMatrixMode(GL_PROJECTION);//选择投影矩阵
    glLoadIdentity();//重置选择好的投影矩阵
    gluPerspective(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);//建立透视投影矩阵
    glMatrixMode(GL_MODELVIEW);//以下2句和上面出现的解释一样
    glLoadIdentity();

}
void GLWidget::keyPressEvent(QKeyEvent *e)
{
    switch(e->key())
    {
        /*L键位开启光照的开关*/
        case Qt::Key_L:
            light = !light;
            if(!light)
                glDisable(GL_LIGHTING);
            else
                glEnable(GL_LIGHTING);
            updateGL();
            break;
        /*B键位选择是否采用色彩融合*/
        case Qt::Key_B:
            blend = !blend;
            if(blend)
                {
                    glEnable(GL_BLEND);
                    glDisable(GL_DEPTH_TEST);
                }
            else
                {
                    glDisable(GL_BLEND);
                    glEnable(GL_DEPTH_TEST);
                }
            updateGL();
            break;
        /*F键位选择纹理滤波的方式*/
        case Qt::Key_F:
            filter += 1;
            if(filter > 2)
                filter = 0;
            updateGL();
            break;
        /*PageUp键为将木箱移到屏幕内部方向*/
        case Qt::Key_PageUp:
            zoom -= 0.2;
            updateGL();
            break;
        /*PageDown键为将木箱移到屏幕外部方向*/
        case Qt::Key_PageDown:
            zoom += 0.2;
            updateGL();
            break;
        /*Up键为加快立方体旋转的速度*/
        case Qt::Key_Up:
            rotate_speed += 1.0;
            updateGL();
            break;
        /*Down键为减慢立方体旋转的速度*/
        case Qt::Key_Down:
            rotate_speed -= 1.0;
            updateGL();
            break;
        /*F1键为全屏和普通屏显示切换键*/
        case Qt::Key_F1:
            fullscreen = !fullscreen;
            if(fullscreen)
                showFullScreen();
            else
            {
                setGeometry(300, 150, 500, 500);
                showNormal();
            }
            updateGL();
            break;
        /*Ese为退出程序键*/
        case Qt::Key_Escape:
            close();
    }
}

/*装载纹理*/
void GLWidget::loadTextures()
{
    QImage tex, buf;
    if(!buf.load("../opengl_qt_nehe_07/cat.jpg"))
   // if(!buf.load("../opengl_qt_nehe_07/crate.bmp"))
    {
        qWarning("Cannot open the image...");
        QImage dummy(128, 128, QImage::Format_RGB32);//当没找到所需打开的图片时,创建一副128*128大小,深度为32位的位图
        dummy.fill(Qt::green);
        buf = dummy;
    }
    tex = convertToGLFormat(buf);//将Qt图片的格式buf转换成opengl的图片格式tex
    glGenTextures(3, &texture[0]);//开辟3个纹理内存,索引指向texture[0]

    /*建立第一个纹理*/
    glBindTexture(GL_TEXTURE_2D, texture[0]);//将创建的纹理内存指向的内容绑定到纹理对象GL_TEXTURE_2D上,经过这句代码后,以后对
                                            //GL_TEXTURE_2D的操作的任何操作都同时对应与它所绑定的纹理对象
    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);//当所显示的纹理比加载进来的纹理小时,采用GL_NEAREST的方法来处理
                                                                      //GL_NEAREST方式速度非常快,因为它不是真正的滤波,所以占用内存非常
                                                                      // 小,速度就快了
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//当所显示的纹理比加载进来的纹理大时,采用GL_NEAREST的方法来处理

    /*建立第二个纹理*/
    glBindTexture(GL_TEXTURE_2D, texture[1]);
    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_LINEAR);//当所显示的纹理比加载进来的纹理小时,采用GL_LINEAR的方法来处理
                                                                    //GL_LINEAR方式速度非常稍微慢些,但它做到了线性滤波
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    /*建立第三个纹理*/
    glBindTexture(GL_TEXTURE_2D, texture[2]);
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, tex.width(), tex.height(), GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());//该函数对所加载的
                                                                                    //纹理像素没有要求是2的n次方,可以是任意的像素
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);//mipmap方式是当物体很远时,也能保留很好的细节,所以
                                                                   //它的运算量很大,速度很慢,这里GL_LINEAR_MIPMAP_NEAREST是混合使用
}

GLWidget::~GLWidget()
{
    delete ui;
}

 

 

 

 

  总结:

  本次实验对比了3中纹理滤波方式,学会了简单的光照的使用方法,并用键盘控制物体的某些特性。

 

  参考资料:

  http://nehe.gamedev.net/ 

  http://www.owlei.com/DancingWind/

  http://www.qiliang.net/old/nehe_qt/

 

  附录:

  实验工程code下载。

 

 

 

 

 

 

你可能感兴趣的:(OpenGL_Qt学习笔记之_06(纹理滤波、光照和色彩融合))