这次教程中,我将教大家如何加载一个3D世界,并在3D世界中漫游。这相较于我们只能创造一个旋转的立方体或一群星星时有很大的进步了,当然这节课代码难度不低,但也不会很难,只要你跟着我慢慢一步一步来。

一个3D世界当然不像我们之前那样,只要几个对象就搞定了,因此,我们会选择将3D环境用数据来表达,并存放在一个文本中。随着环境复杂度的上升,这个工作得难度也会随之上升。出于这个原因,我们必须将数据归类,使其具有更多的可操作性风格。后面程序中,我们会把3D世界看作是区段(sector)的集合。一个区段可以是一个房间、一个立方体或者任意一个闭合的空间。

程序运行时效果如下:

下面进入教程:

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

CSDN QT技术栈大纲:Qt开发必备技术栈学习路线和资料

 1 #ifndef MYGLWIDGET_H2 #define MYGLWIDGET_H3  4 #include <QWidget>5 #include <QGLWidget>6  7 typedef struct tagVERTEX                            //创建Vertex顶点结构体8 {9     float x, y, z;                                  //3D坐标
10     float u, v;                                     //纹理坐标
11 } VERTEX;
12
13 typedef struct tagTRIANGLE                          //创建Triangle三角形结构体
14 {
15     VERTEX vertexs[3];                              //3个顶点构成一个Triangle
16 } TRIANGLE;
17
18 typedef struct tagSECTOR                            //创建Sector区段结构体
19 {
20     int numtriangles;                               //Sector中的三角形个数
21     QVector<TRIANGLE> vTriangle;                            //储存三角形的向量
22 } SECTOR;
23
24 class MyGLWidget : public QGLWidget
25 {
26     Q_OBJECT
27 public:
28     explicit MyGLWidget(QWidget *parent = 0);
29     ~MyGLWidget();
30
31 protected:
32     //对3个纯虚函数的重定义
33     void initializeGL();
34     void resizeGL(int w, int h);
35     void paintGL();
36
37     void keyPressEvent(QKeyEvent *event);           //处理键盘按下事件
38
39 private:
40     bool fullscreen;                                //是否全屏显示
41
42     QString m_FileName;                             //图片的路径及文件名
43     GLuint m_Texture;                               //储存一个纹理
44     QString m_WorldFile;                            //存放世界的路径及文本名
45     SECTOR m_Sector;                                //储存一个区段的数据
46
47     static const float m_PIOVER180 = 0.0174532925f; //实现度和弧度直接的折算
48     GLfloat m_xPos;                                 //储存当前位置
49     GLfloat m_zPos;
50     GLfloat m_yRot;                                 //视角的旋转
51     GLfloat m_LookUpDown;                           //记录抬头和低头
52 };
53
54 #endif // MYGLWIDGET_H

可以看到我们定义了3个结构体,依次表示顶点,三角形和区段。一个区段包含一系列的多边形(三角形),三角形本质上是由三个以上顶点组合的图形,顶点就是我们最基本的分类单位了。顶点包含了OpenGL真正感兴趣的数据,我们用3D空间中的坐标值(x, y, z)以及它们的纹理坐标(u, v)来定义三角形的每个顶点。这次教程中,我们只加载了一个区段的数据,故只需一个m_Sector数据就够了(当然有兴趣的可以自己设计区段数据,多加载几个看看)。

其他增加的变量,m_PIOVER180就是一个度数和弧度制的折算因子,m_xPos、m_zPos用于记录游戏者的位置,m_yRot用于记录游戏者视角的旋转,m_LookUpDown用于控制游戏者的仰视俯视,简单点说就是抬头低头啦。

接下来,我们需要打开myglwidget.cpp,加上声明#include <QTimer>、#include <QTextStream>、#include <QtMath>,在构造函数中对数据进行初始化,具体代码如下:

 1 MyGLWidget::MyGLWidget(QWidget *parent) :2     QGLWidget(parent)3 {4     fullscreen = false;5     m_FileName = "D:/QtOpenGL/QtImage/Mud.bmp";         //应根据实际存放图片的路径进行修改6     m_WorldFile = "D:/QtOpenGL/QtImage/World.txt";7     m_Sector.numtriangles = 0;8  9     QFile file(m_WorldFile);
10     file.open(QIODevice::ReadOnly | QIODevice::Text);   //将要读入数据的文本打开
11     QTextStream in(&file);
12     while (!in.atEnd())
13     {
14         QString line[3];
15         for (int i=0; i<3; i++)                         //循环读入3个点数据
16         {
17             do                                          //读入数据并保证数据有效
18             {
19                 line[i] = in.readLine();
20             }
21             while (line[i][0] == '/' || line[i] == "");
22         }
23         m_Sector.numtriangles++;                        //每成功读入3个点构成一个三角形
24         TRIANGLE tempTri;
25         for (int i=0; i<3; i++)                         //将数据储存于一个三角形中
26         {
27             QTextStream inLine(&line[i]);
28             inLine >> tempTri.vertexs[i].x
29                    >> tempTri.vertexs[i].y
30                    >> tempTri.vertexs[i].z
31                    >> tempTri.vertexs[i].u
32                    >> tempTri.vertexs[i].v;
33         }
34         m_Sector.vTriangle.push_back(tempTri);          //将三角形放入m_Sector中
35     }
36     file.close();
37
38     m_xPos = 0.0f;
39     m_zPos = 0.0f;
40     m_yRot = 0.0f;
41     m_LookUpDown = 0.0f;
42
43     QTimer *timer = new QTimer(this);                   //创建一个定时器
44     //将定时器的计时信号与updateGL()绑定
45     connect(timer, SIGNAL(timeout()), this, SLOT(updateGL()));
46     timer->start(10);                                   //以10ms为一个计时周期
47 }

我们重点解释中间对于m_Sector的初始化,我们先将文件打开,再利用Qt的文本流一行一行的读取(为何一行一行读,大家看下存放数据的文本文件World.txt就知道了)并保证读入的数据是有效的。每当成功读入三行数据时,说明构成了一个三角形,就创建一个三角形来储存这些数据,并在最后把三角形放入m_Sector中,当然要给m_Sector的numtriangles加上一,说明多了一个三角形。最后录完数据后,关上文件。或者你会想如果有效数据行数不是3的倍数怎么办,这个问题其实已经不是我们的问题了,而且提供的数据文本存在问题,因此不必考虑。接着的数据初始化不作解释了。

然后在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     glEnable(GL_DEPTH_TEST);                            //启用深度测试
10     glDepthFunc(GL_LEQUAL);                             //所作深度测试的类型
11     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //告诉系统对透视进行修正
12 }

任何一个不错的的3D引擎都会允许用户在这个世界中游走和遍历,我们的这个也一样,实现这个功能当然要通过键盘控制。具体实现的途径有一种是直接移动镜头并绘制以镜头为中心的3D环境,但这样会很慢并且不易用代码实现,我们的解决方法如下:

根据用户的指令旋转并变换视角位置。

围绕原点,以与视角相反的旋转方向来旋转世界(让人产生视角旋转的错觉)。

以与视角平移方向相反的方向来平移世界(让人产生视角移动的错觉)。

这样实现起来就简单多了。下面我们先通过键盘控制,来实现平移并旋转视角。

 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_PageUp:                                //按下PageUp视角向上转
21         m_LookUpDown -= 1.0f;
22         if (m_LookUpDown < -90.0f)
23         {
24             m_LookUpDown = -90.0f;
25         }
26         break;
27     case Qt::Key_PageDown:                              //按下PageDown视角向下转
28         m_LookUpDown += 1.0f;
29         if (m_LookUpDown > 90.0f)
30         {
31             m_LookUpDown = 90.0f;
32         }
33         break;
34     case Qt::Key_Right:                                 //Right按下向左旋转场景
35         m_yRot -= 1.0f;
36         break;
37     case Qt::Key_Left:                                  //Left按下向右旋转场景
38         m_yRot += 1.0f;
39         break;
40     case Qt::Key_Up:                                    //Up按下向前移动
41         //向前移动分到x、z上的分量
42         m_xPos -= (float)sin(m_yRot * m_PIOVER180) * 0.05f;
43         m_zPos -= (float)cos(m_yRot * m_PIOVER180) * 0.05f;
44         break;
45     case Qt::Key_Down:                                  //Down按下向后移动
46         //向后移动分到x、z上的分量
47         m_xPos += (float)sin(m_yRot * m_PIOVER180) * 0.05f;
48         m_zPos += (float)cos(m_yRot * m_PIOVER180) * 0.05f;
49         break;
50     }
51 }

这个实现很简单。当左右方向键按下后,旋转变量m_yRot相应的增加或减少。当前后方向键按下时,我们使用sin()和cos()函数计算具体在x和z轴方向上的位移量,使得游戏者能准确的移动。

现在我们已经具备了一切所需的数据,可以开始进行步骤2和3了,当然我们也将进入重点的paintGL()函数。虽然重点,但代码并不难,具体代码如下:

 1 void MyGLWidget::paintGL()                              //从这里开始进行所以的绘制2 {3     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存4     glLoadIdentity();                                   //重置当前的模型观察矩阵5  6     GLfloat x_m, y_m, z_m, u_m, v_m;                    //顶点的临时x、y、z、u、v值7     GLfloat xTrans = -m_xPos;                           //游戏者沿x轴平移时的大小8     GLfloat zTrans = -m_zPos;                           //游戏者沿z轴平移时的大小9     GLfloat yTrans = -0.25f;                            //游戏者沿y轴略作平移,使视角准确
10     GLfloat sceneroty = 360.0f - m_yRot;                //游戏者的旋转
11
12     glRotatef(m_LookUpDown, 1.0f, 0.0f, 0.0f);          //抬头低头的旋转
13     glRotatef(sceneroty, 0.0f, 1.0f, 0.0f);             //根据游戏者正面所对方向所作的旋转
14     glTranslatef(xTrans, yTrans, zTrans);               //以游戏者为中心平移场景
15
16     glBindTexture(GL_TEXTURE_2D, m_Texture);            //绑定纹理
17     for (int i=0; i<m_Sector.numtriangles; i++)         //遍历所有的三角形
18     {
19         glBegin(GL_TRIANGLES);                          //开始绘制三角形
20             glNormal3f(0.0f, 0.0f, 1.0f);               //指向前面的法线
21             x_m = m_Sector.vTriangle[i].vertexs[0].x;
22             y_m = m_Sector.vTriangle[i].vertexs[0].y;
23             z_m = m_Sector.vTriangle[i].vertexs[0].z;
24             u_m = m_Sector.vTriangle[i].vertexs[0].u;
25             v_m = m_Sector.vTriangle[i].vertexs[0].v;
26             glTexCoord2f(u_m, v_m);
27             glVertex3f(x_m, y_m, z_m);
28
29             x_m = m_Sector.vTriangle[i].vertexs[1].x;
30             y_m = m_Sector.vTriangle[i].vertexs[1].y;
31             z_m = m_Sector.vTriangle[i].vertexs[1].z;
32             u_m = m_Sector.vTriangle[i].vertexs[1].u;
33             v_m = m_Sector.vTriangle[i].vertexs[1].v;
34             glTexCoord2f(u_m, v_m);
35             glVertex3f(x_m, y_m, z_m);
36
37             x_m = m_Sector.vTriangle[i].vertexs[2].x;
38             y_m = m_Sector.vTriangle[i].vertexs[2].y;
39             z_m = m_Sector.vTriangle[i].vertexs[2].z;
40             u_m = m_Sector.vTriangle[i].vertexs[2].u;
41             v_m = m_Sector.vTriangle[i].vertexs[2].v;
42             glTexCoord2f(u_m, v_m);
43             glVertex3f(x_m, y_m, z_m);
44         glEnd();                                        //三角形绘制结束
45     }
46 }

就正如我们之前步骤2和3所说,我们以相反的方式来平移和旋转场景,使得看上去是视角在平移和旋转,然后绑定纹理并绘制出整个场景就完成了!

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

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓

Qt OpenGL 加载3D世界,并在其中漫游相关推荐

  1. qt opengl 加载3d模型(obj格式)

    和一般c++程序加载3d模型一样,解读出数据内容,再用一个常规的着色程序就可以了. 我实现的效果如下,采用的免费模型 实现思路和前面的略有不同,就是把自己生成顶点.纹理.法线的过程变成从文件读取了. ...

  2. OpenGL(十八)——Qt OpenGL绘制一个3D世界

    OpenGL(十八)--Qt OpenGL绘制一个3D世界 一.说明 本篇介绍构建一个3D的世界. 二.简介 加载3D世界,并在其中漫游: 在这一课中,你将学会如何加载3D世界,并在3D世界中漫游. ...

  3. Qt和OpenGL:使用Open Asset Import Library(ASSIMP)加载3D模型

    Qt和OpenGL:使用Open Asset Import Library(ASSIMP)加载3D模型 翻译自:https://www.ics.com/blog/qt-and-opengl-loadi ...

  4. opengl加载显示3D模型UC类型文件

    opengl加载显示3D模型UC类型文件 前置条件 注意事项 项目展示 项目完整C++源代码 前置条件 opengl三方库freeglut,显示uc格式的三维模型文件, 注意事项 源代码 model_ ...

  5. opengl加载显示3D模型3d类型文件

    opengl加载显示3d模型UC类型文件 前置条件 注意事项 项目展示 项目完整C++源代码 前置条件 opengl三方库freeglut,显示3d格式的三维模型文件, 注意事项 源代码 model_ ...

  6. opengl加载显示3D模型3MF类型文件

    opengl加载显示3D模型3MF类型文件 前置条件 注意事项 项目展示 项目完整C++源代码 前置条件 opengl三方库freeglut,显示3MF格式的三维模型文件 注意事项 源代码 model ...

  7. opengl加载显示3D模型AC类型文件

    opengl加载显示3D模型AC类型文件 前置条件 注意事项 项目展示 项目完整C++源代码 前置条件 opengl三方库freeglut,显示AC格式的三维模型文件 注意事项 源代码 model_f ...

  8. opengl加载显示3D模型AMF类型文件

    opengl加载显示3D模型AMF类型文件 前置条件 注意事项 项目展示 项目完整C++源代码 前置条件 opengl三方库freeglut,显示AMF格式的三维模型文件 注意事项 源代码 model ...

  9. opengl加载显示3D模型ase类型文件

    opengl加载显示3D模型ase类型文件 前置条件 注意事项 项目展示 项目完整C++源代码 前置条件 opengl三方库freeglut,显示ase格式的三维模型文件 注意事项 源代码 C:/Us ...

最新文章

  1. linux 压缩解压打包
  2. music算法_“要热爱 请深爱”系列(5)浅谈模拟退火算法
  3. Codeforces Round #655 (Div. 2) E. Omkar and Last Floor 区间dp + 巧妙的状态设计
  4. Shell——test 命令
  5. javascript HTMLElement
  6. 基本功 | Java即时编译器原理解析及实践
  7. eclipse 返回上一个选项卡、注释及取消注释 、大写变小写、 光标跳到下一行快捷键
  8. ES6知识整理(2)--变量的解构赋值
  9. PC建立WIFI热点
  10. LINUX下Android NDK下载并配置
  11. 数据库基础(超详细版)
  12. matlab车牌识别错误,matlab车牌识别调入切割函数后就不出图了?也没有显示错误...
  13. EXCEL基本操作技巧
  14. CTGU实验6_2-创建还书存储过程
  15. pyqt5学习笔记——QListView与QListWidget
  16. icloud有linux客户端吗,icloud drive:Windows 版 iCloud 客户端在哪下载
  17. 服务器被挖矿入侵,进程 command为ld-linux-x86-64占用cpu很高,解决经历
  18. kotlin用it还是this?
  19. 高等数学 —— 映射与函数 —— 映射
  20. 小结Win7下开启硬盘NCQ功能

热门文章

  1. mybatis使用小结-以备后用
  2. CSS outline
  3. Potree:大规模点云渲染
  4. AAAI 2021最佳论文奖出炉,北航成最大赢家,还有这样一批华人学术新星!
  5. 大宗农产品交易知识图谱推理引擎的构建--需求分析
  6. 最新仿映客直播APP开发实战项目IOS开发实战8天(最全最新)
  7. python浮点数计算出现无限小数
  8. Java解决Excel导出大批量数据(附上测试代码)
  9. 短暂的人生,不管你选择什么,都要对得起时间
  10. 6张图让你搞懂浏览器渲染网页过程