• Qt OpenGL 在3D空间中移动位图


    想知道如何在3D空间中移动物体,想知道如何在屏幕上绘制一个图像,而让图像的背景色变为透明,希望有一个简单的动画。这次教程中将教会你所以的一切。当然,这一课是在前面几课知识的基础上创建的,请确保你已经掌握了前面几课的知识,再进入本课教程。

    欢迎进入这次教程,这一课将是前面几课的综合。前面的学习中,我们学会了设置一个OpenGL窗口的每个细节,学会在旋转的物体上贴图并打上光线以及混色(透明)处理。这一课中,我们将在3D场景中移动位图,并去除位图上的黑色像素(使用混色)。接着为黑白纹理上色,最后我们将学会创建丰富的色彩,并把混合了不同色彩的纹理相互混合,得到简单的动画效果。

    程序运行时效果如下:

    下面进入教程:

    我们这次将在第01课的基础上修改代码,其中一些与前几课重复的地方我不作过多解释。首先打开myglwdiget.h文件,将类声明更改如下:

     1 #ifndef MYGLWIDGET_H
     2 #define MYGLWIDGET_H
     3  
     4 #include <QWidget>
     5 #include <QGLWidget>
     6  
     7 class MyGLWidget : public QGLWidget
     8 {
     9     Q_OBJECT
    10 public:
    11     explicit MyGLWidget(QWidget *parent = 0);
    12     ~MyGLWidget();
    13  
    14 protected:
    15     //对3个纯虚函数的重定义
    16     void initializeGL();
    17     void resizeGL(int w, int h);
    18     void paintGL();
    19  
    20     void keyPressEvent(QKeyEvent *event);           //处理键盘按下事件
    21  
    22 private:
    23     bool fullscreen;                                //是否全屏显示
    24  
    25     QString m_FileName;                             //图片的路径及文件名
    26     GLuint m_Texture;                               //储存一个纹理
    27     bool m_Twinkle;                                 //星星是否闪烁
    28  
    29     static const int num = 50;                      //星星的数目
    30     struct star{                                    //为星星创建的结构体
    31         int r, g, b;                                //星星的颜色
    32         GLfloat dist;                               //星星距离中心的距离
    33         GLfloat angle;                              //当前星星所处的角度
    34     } m_stars[num];
    35  
    36     GLfloat m_Deep;                                 //星星离观察者的距离
    37     GLfloat m_Tilt;                                 //星星的倾角
    38     GLfloat m_Spin;                                 //星星的自转
    39 };
    40  
    41 #endif // MYGLWIDGET_H

    首先是一个布尔变量m_Twinkle用来表示星星是否闪烁。然后我们创建了一个星星的结构体,结构体包含星星的颜色,离中心距离以及所处角度,并创建了一个大小为50的星星数组。最后三个浮点变量依次表示星星离观察者距离,星星的倾角,星星的自转,这三个浮点变量用于对整体视图的控制。

    接下来,我们还是打开myglwidget.cpp,加上声明#include <QTimer>,在构造函数中对新增变量进行初始化,只解释小部分,希望大家结合注释可以理解,代码如下:

     1 MyGLWidget::MyGLWidget(QWidget *parent) :
     2     QGLWidget(parent)
     3 {
     4     fullscreen = false;
     5     m_FileName = "D:/QtOpenGL/QtImage/Star.bmp";        //应根据实际存放图片的路径进行修改
     6     m_Twinkle = false;                                  //默认初始状态为不闪烁
     7  
     8     for (int i=0; i<num; i++)                           //循环初始化所有的星星
     9     {
    10         //随机获得星星颜色
    11         m_stars[i].r = rand() % 256;
    12         m_stars[i].g = rand() % 256;
    13         m_stars[i].b = rand() % 256;
    14  
    15         m_stars[i].dist = ((float)i / num) * 5.0f;      //计算星星离中心的距离,最大半径为5.0
    16         m_stars[i].angle = 0.0f;                        //所以星星都从0度开始旋转
    17     }
    18  
    19     m_Deep = -15.0f;                                    //深入屏幕15.0单位
    20     m_Tilt = 90.0f;                                     //初始倾角为90度(面对观察者)
    21     m_Spin = 0.0f;                                      //从0度开始自转
    22  
    23     QTimer *timer = new QTimer(this);                   //创建一个定时器
    24     //将定时器的计时信号与updateGL()绑定
    25     connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
    26     timer->start(10);                                   //以10ms为一个计时周期
    27 }

    利用循环对星星的数据进行初始化,第i 颗星星离中心的距离是将i 的值除以星星的总数,然后乘上5.0f。基本上这样使得后一颗星星比前一颗星星离中心更远一点,这样当i = 50时,就刚好达到最大半径5.0f了。然后我们选择颜色都是从0~255之间取一个随机数,为何这里不是通常的0.0f~1.0f呢?这里我们使用的颜色设置函数时glColor4ub,而不是之前的glColor4f,ub意味着参数是Unsigned Byte型的,同时这里去随机数整数似乎要比取一个浮点的随机数更容易一些。

    然后我们要对initializeGL()函数作一定的修改,修改后代码如下:

     1 void MyGLWidget::initializeGL()                         //此处开始对OpenGL进行所以设置
     2 {
     3     m_Texture = bindTexture(QPixmap(m_FileName));
     4     glEnable(GL_TEXTURE_2D);
     5  
     6     glClearColor(0.0, 0.0, 0.0, 0.0);                   //黑色背景
     7     glShadeModel(GL_SMOOTH);                            //启用阴影平滑
     8     glClearDepth(1.0);                                  //设置深度缓存
     9     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //告诉系统对透视进行修正
    10  
    11     glBlendFunc(GL_SRC_ALPHA, GL_ONE);                  //设置混色函数取得半透明效果
    12     glEnable(GL_BLEND);                                 //开启混合(混色)
    13 }

    这里我们不打算使用深度测试,如果你使用第01课的代码的话,请确认是否已经去掉了glDepthFunc(GL_LEQUAL);和glEnable(GL_DEPTH_TEST);两行。否则,你所见到的最终效果会一团糟。这里我们使用了纹理映射,因此请你确认你已经加入了这些这一课中所没有的代码。同样要注意的是我们也开启了混合(混色),这是为了给纹理上色,产生不同颜色的星星。

    还有就是最重点的paintGL()函数,我会一一作出解释,具体代码如下:

     1 void MyGLWidget::paintGL()                              //从这里开始进行所以的绘制
     2 {
     3     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
     4     glBindTexture(GL_TEXTURE_2D, m_Texture);            //选择纹理
     5  
     6     for (int i=0; i<num; i++)
     7     {
     8         glLoadIdentity();                               //绘制每颗星星之前,重置模型观察矩阵
     9         glTranslatef(0.0f, 0.0f, m_Deep);               //深入屏幕里面
    10         glRotatef(m_Tilt, 1.0f, 0.0f, 0.0f);            //倾斜视角
    11         glRotatef(m_stars[i].angle, 0.0f, 1.0f, 0.0f);  //旋转至当前所画星星的角度
    12         glTranslatef(m_stars[i].dist, 0.0f, 0.0f);      //沿x轴正向移动
    13         glRotatef(-m_stars[i].angle, 0.0f, 1.0f, 0.0f); //取消当前星星的角度
    14         glRotatef(-m_Tilt, 1.0f, 0.0f, 0.0f);           //取消视角倾斜
    15  
    16         if (m_Twinkle)                                  //启动闪烁效果
    17         {
    18             //使用byte型数据值指定一个颜色
    19             glColor4ub(m_stars[num-i-1].r, m_stars[num-i-1].g, m_stars[num-i-1].b, 255);
    20             glBegin(GL_QUADS);                          //开始绘制纹理映射过的四边形
    21                 glTexCoord2f(0.0f, 0.0f);
    22                 glVertex3f(-1.0f, -1.0f, 0.0f);
    23                 glTexCoord2f(1.0f, 0.0f);
    24                 glVertex3f(1.0f, -1.0f, 0.0f);
    25                 glTexCoord2f(1.0f, 1.0f);
    26                 glVertex3f(1.0f, 1.0f, 0.0f);
    27                 glTexCoord2f(0.0f, 1.0f);
    28                 glVertex3f(-1.0f, 1.0f, 0.0f);
    29             glEnd();                                    //四边形绘制结束
    30         }
    31  
    32         glRotatef(m_Spin, 0.0f, 0.0f, 1.0f);            //绕z轴旋转星星
    33         //使用byte型数据值指定一个颜色
    34         glColor4ub(m_stars[i].r, m_stars[i].g, m_stars[i].b, 255);
    35         glBegin(GL_QUADS);                          //开始绘制纹理映射过的四边形
    36             glTexCoord2f(0.0f, 0.0f);
    37             glVertex3f(-1.0f, -1.0f, 0.0f);
    38             glTexCoord2f(1.0f, 0.0f);
    39             glVertex3f(1.0f, -1.0f, 0.0f);
    40             glTexCoord2f(1.0f, 1.0f);
    41             glVertex3f(1.0f, 1.0f, 0.0f);
    42             glTexCoord2f(0.0f, 1.0f);
    43             glVertex3f(-1.0f, 1.0f, 0.0f);
    44         glEnd();                                    //四边形绘制结束
    45  
    46         m_Spin += 0.01f;                            //星星的自转
    47         m_stars[i].angle += (float)i / num;         //改变星星的公转角度
    48         m_stars[i].dist -= 0.01f;                   //改变星星离中心的距离
    49         if (m_stars[i].dist < 0.0f)                 //星星到达中心了么
    50         {
    51             m_stars[i].dist += 5.0f;                //往外移5.0单位
    52             m_stars[i].r = rand() % 256;
    53             m_stars[i].g = rand() % 256;
    54             m_stars[i].b = rand() % 256;
    55         }
    56     }
    57 }

    首先是清屏和绑定纹理,接着进入循环,画每颗星星前当然要重置模型观察矩阵并进行视图的移动旋转,然后我们来移动星星。我们要做的第一件事是把场景沿y轴旋转。如果我们旋转90度的话,x轴就不再是从左到右的了,它将从里到外穿出屏幕。第二行代码沿x轴移动一个正值,通常这样代表移向了屏幕的右侧,但由于我们绕y轴旋转了坐标系,x轴的正向可以使任意方向。因此,当我们沿x轴正向移动时,可能向左、向右、向前、向后。

    接着的代码带一点小技巧。我们绘制的星星实际上是一个平面的纹理,现在我们在屏幕中心画了个平面的四边形然后贴上纹理,这看起来很不错。但是当我们绕着y轴转上个90度的话,纹理在屏幕上就只剩下右侧和左侧的两条边朝着我们了,看起来就是一条细线,这不并不是我们所想要的,我们希望星星永远正面朝着我们。因此,在绘制星星之前,我们通过逆序旋转来抵消之前对星星所作的任何旋转,当然旋转的角度就要加上- 号了。

    然后到了if 条件从句,如果m_Twinkle为TRUE,我们在屏幕上先画一次不旋转的星星,当我们画第i颗星星时,将采用第num-i-1颗星星的颜色使得颜色不同。由于开启了m_Twinkle,每颗星星最后会被绘制两遍,两遍绘制的星星颜色相互融合,会产生很棒的效果,看起来比原来亮了许多。值得注意的是,给纹理上色是件很容易的事,尽管纹理本身是黑白的,纹理将变成我们在绘制它之前选定的任意颜色。if 条件从句后,我们要绘制第二遍的星星,和前面不同的是,这一遍的星星肯定会被绘制,并且这次的星星绕着z轴旋转(星星的自转)。

    后面的代码代表星星的运动,我们增加m_Spin的值来控制星星自转,然后将每颗星星的公转角度增加 i/num这使得离中心更远的星星转得更快,最后减少每颗星星离屏幕中心的距离,这样看起来星星们好像被不断地吸入屏幕的中心。

    最后几行是检查星星是否已经碰到了屏幕中心。当星星碰到屏幕中心时,我们为它赋上新颜色,然后往外移5.0单位,这颗星星将重新踏上回归屏幕中心的旅程。

    最后就是键盘控制部分了,具体代码如下(相信大家结合注释可以很容易看懂):

     1 void MyGLWidget::keyPressEvent(QKeyEvent *event)
     2 {
     3     switch (event->key())
     4     {
     5     case Qt::Key_F1:                                //F1为全屏和普通屏的切换键
     6         fullscreen = !fullscreen;
     7         if (fullscreen)
     8         {
     9             showFullScreen();
    10         }
    11         else
    12         {
    13             showNormal();
    14         }
    15         updateGL();
    16         break;
    17     case Qt::Key_Escape:                            //ESC为退出键
    18         close();
    19         break;
    20     case Qt::Key_T:                                 //T为星星开启关闭闪烁的切换键
    21         m_Twinkle = !m_Twinkle;
    22         break;
    23     case Qt::Key_Up:                                //Up按下屏幕向上倾斜
    24         m_Tilt -= 0.5f;
    25         break;
    26     case Qt::Key_Down:                              //Down按下屏幕向下倾斜
    27         m_Tilt += 0.5f;
    28         break;
    29     case Qt::Key_PageUp:                            //PageUp按下缩小
    30         m_Deep -= 0.1f;
    31         break;
    32     case Qt::Key_PageDown:                          //PageDown按下放大
    33         m_Deep += 0.1f;
    34         break;
    35     }
    36 }

    现在就可以运行程序查看效果了!

  • 相关阅读:
    10.26 饮食
    10.25 饮食
    10.24饮食
    关于 wpf 的ICommand 的 CanExecute CanExecuteChanged func action的认识
    2018 10 23
    [Java]先有Class还是先有Object?
    [Java]如何制作一个WordCount-Plus的Plugin
    [java] 软工实践WordCount-Plus
    [java]基本数据类型
    [java]第一个程序
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/14048075.html
Copyright © 2020-2023  润新知