【Qt OpenGL教程】06:纹理映射

时间:2022-09-10 17:44:28
第06课:纹理映射 (参照NeHe)
这次教程中,我教会大家如何把纹理映射到立方体的六个面上。学习texture map(纹理映射)有诸多好处。比如说想让一颗导弹飞过屏幕。根据前几课的知识,我们最可行的办法可能是很多个多边形来构建导弹的轮廓并加上有趣的颜色。而使用纹理映射,我们可以使用真实的导弹图像并让它飞过屏幕。你觉得哪个更好看?使用纹理映射的好处还不止是更好看,而且程序的运行会更快。导弹贴图可能只是一个飞过窗口的四边形,而一个导弹却需要成百上千的多边形组成,很明显,纹理映射极大的节省了CPU的时间。

程序运行时效果如下:
【Qt OpenGL教程】06:纹理映射

下面进入教程:

我们这次将在第01课得到的基础框架上开始添加代码,首先打开myglwidget.h文件,我们需要增加一些变量,将类声明更改如下:
#ifndef MYGLWIDGET_H
#define MYGLWIDGET_H

#include <QWidget>
#include <QGLWidget>

class MyGLWidget : public QGLWidget
{
    Q_OBJECT
public:
    explicit MyGLWidget(QWidget *parent = 0);
    ~MyGLWidget();

protected:
    //对3个纯虚函数的重定义
    void initializeGL();
    void resizeGL(int w, int h);
    void paintGL();

    void keyPressEvent(QKeyEvent *event);           //处理键盘按下事件

private:
    bool fullscreen;                                //是否全屏显示

    GLfloat m_xRot;                                 //绕x轴旋转的角度
    GLfloat m_yRot;                                 //绕y轴旋转的角度
    GLfloat m_zRot;                                 //绕z轴旋转的角度
    
    QString m_FileName;                             //图片的路径及文件名
    GLuint m_Texture;                               //储存一个纹理
};

#endif // MYGLWIDGET_H
增加的前三个变量用来使立方体绕x、y、z轴旋转,m_FileName用于储存图片的路径及文件名,m_Texture为一个纹理分配存储空间。如果需要不止一个纹理,可以创建一个数组来储存不同的纹理。

接下来,我们需要打开myglwidget.cpp,加上声明#include <QTimer>,在构造函数中对新增变量(除了m_Texture)进行初始化,同样不作过多解释,代码如下:
MyGLWidget::MyGLWidget(QWidget *parent) :
    QGLWidget(parent)
{
    fullscreen = false;
    m_xRot = 0.0f;
    m_yRot = 0.0f;
    m_zRot = 0.0f;
    m_FileName = "D:/QtOpenGL/QtImage/Nehe.bmp";        //应根据实际存放图片的路径进行修改

    QTimer *timer = new QTimer(this);                   //创建一个定时器
    //将定时器的计时信号与updateGL()绑定
    connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
    timer->start(10);                                   //以10ms为一个计时周期
}

然后这次我们需要对initializeGL()函数作一定的修改了,具体代码如下:
void MyGLWidget::initializeGL()                         //此处开始对OpenGL进行所以设置
{
    m_Texture = bindTexture(QPixmap(m_FileName));       //载入位图并转换成纹理
    glEnable(GL_TEXTURE_2D);                            //启用纹理映射

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);               //黑色背景
    glShadeModel(GL_SMOOTH);                            //启用阴影平滑

    glClearDepth(1.0);                                  //设置深度缓存
    glEnable(GL_DEPTH_TEST);                            //启用深度测试
    glDepthFunc(GL_LEQUAL);                             //所作深度测试的类型
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //告诉系统对透视进行修正
}
我们增加了两行代码,首先调用了Qt提供的bindTexture()函数将图片载入并转换成纹理,然后启用2D纹理映射。如果忘记启用的话,我们的对象看起来永远都是纯白色的,这明显与我们的预期大相径庭。

最后我们该开始绘制贴图过的立方体了,paintGL()函数具体代码如下:
void MyGLWidget::paintGL()                              //从这里开始进行所以的绘制
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
    glLoadIdentity();                                   //重置模型观察矩阵
    glTranslatef(0.0f, 0.0f, -5.0f);                    //移入屏幕5.0单位
    glRotatef(m_xRot, 1.0f, 0.0f, 0.0f);                //绕x轴旋转
    glRotatef(m_yRot, 0.0f, 1.0f, 0.0f);                //绕y轴旋转
    glRotatef(m_zRot, 0.0f, 0.0f, 1.0f);                //绕z轴旋转

    glBindTexture(GL_TEXTURE_2D, m_Texture);            //选择纹理
    glBegin(GL_QUADS);                                  //开始绘制立方体
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);                  //右上(顶面)
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);                 //左上(顶面)
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);                  //左下(顶面)
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);                   //右下(顶面)

        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);                  //右上(底面)
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);                 //左上(底面)
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);                //左下(底面)
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);                 //右下(底面)

        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);                   //右上(前面)
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);                  //左上(前面)
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);                 //左下(前面)
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);                  //右下(前面)

        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);                 //右上(后面)
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);                //左上(后面)
        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);                 //左下(后面)
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);                  //右下(后面)

        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);                  //右上(左面)
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);                 //左上(左面)
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);                //左下(左面)
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);                 //右下(左面)

        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);                  //右上(右面)
        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);                   //左上(右面)
        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);                  //左下(右面)
        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);                 //右下(右面)
    glEnd();                                            //立方体绘制结束

    m_xRot += 0.6f;                                     //x轴旋转
    m_yRot += 0.4f;                                     //y轴旋转
    m_zRot += 0.8f;                                     //z轴旋转
}
这次我们需要让对象依次绕x、y、z轴旋转,旋转多少依赖于变量m_xRot、m_yRot、m_zRot的值。下面我们调用glBindTexture()函数来选择要绑定的纹理,第2个参数表示所要绑定的纹理。当想改变纹理时,应该绑定新的纹理,要注意的是,我们不能在glBegin()和glEnd()直接绑定纹理,那样绑定的纹理时无效的。
为了将纹理正确地映射到四边形上,我们需要将纹理的四个角对应映射到四边形的四个角上。如果映射错误的话,图像显示时可能上下颠倒,侧向一边或者什么都不是。glTexCoord2f的两个参数分别表示x、y坐标,范围从0.0f到1.0f。
最后我们让m_xRot、m_yRot、m_zRot的值增加,大家可以尝试变化每次个变量的改变值来调节立方体的旋转速度,或改变+/-号来调节立方体的旋转方向。
现在就可以运行程序查看效果了!