昨天七夕,关于七夕美好的爱情传说源自于浩瀚银河星空,又碰巧最近在学习QtOpenGL实现三维纹理防体重建,突发奇想用Qt实现一个立方体星空模型,并且能随着鼠标操作实现空间自由旋转![]()
核心思想是用到Qt OpenGL模块,将二维图片贴到立方体的六个面,鼠标可以自由旋转立方体,实现三维星空的动态变换,真正做出来后,感觉效果还挺好的,三维立体星空看起来还是很绚丽的,呵呵
下面直接从代码层面分析上述实例,我用的ubuntu-12.04 Qt-4.8.1
GLFrameWork.pro
QT += opengl一定不能少,因为本例中要调用OoenGL模块,如果没有此语句,后面的OpenGL模块中的函数则不能使用,SOURCES包含了本实例所需要的源文件:main.cpp, mainwindow.spp, nehewidget.cpp。Headers包含了头文件mainwindow.h, nehewidget.h。LIBS是附加库,因为后面的gluPerspective()函数属于本库,并且在gluPerspective()函数所在的头文件中要包含#include,这两个条件缺一不可,否则Qt会报错:undefined reference to gluPerspective()
- #-------------------------------------------------
- #
- # Project created by Wangchuan 2014-08-01T10:46:58
- #
- #-------------------------------------------------
- QT += opengl
- SOURCES += main.cpp\
- mainwindow.cpp \
- nehewidget.cpp
- HEADERS += mainwindow.h \
- nehewidget.h
- LIBS+=-lGLU\
main.cpp
几乎所有的Qt在新建工程时,自动生成了main.cpp,本实例中,也几乎没改变mian.cpp,只是头文件加入了#include "mainwindow.h",简单对此代码进行分析如下,#include中的QApplication类用于管理应用程序范围内的资源,对于Qt开发GUI通常只有一个QApplication工程。#include"mainwindow,h",MainWindow主要对图形界面交互以及逻辑控制设置。QApplication a(int argc, char *argv[ ])主要初始化窗口系统,重建应用工程,并且argc大于0,argv必须至少包含一个字符型数据,w.show()显示最终图形界面。return a.exec(),进入主事件循环,直到exit()函数被调用,如果exit()被调用,则返回0值。
- #include
- #include "mainwindow.h"
- int main(int argc, char *argv[])
- {
- QApplication a(argc, argv);
- MainWindow w;
- w.show();
- return a.exec();
- }
mainwindow.h
QMainWindow类主要提供主应用工程窗口。QKeyEvent类主要描述按键触发事件。扩展一下,C++类当中,定义成public的数据和函数,是外部可以访问的,定义成private的数据和函数,是私有的,外部不可访问,定义成protected的数据和函数,是保护的,只有friend友元可以访问。MainWindow类继承自QMainWindow类,public成员:MainWindow(QWidget *parent=0),将parent传递给QWidget类,QWidget类是用户界面工程的基础类,新widget表示新建一个窗口,~MainWindow()删除主窗口。public slots:公共信号槽函数,可以被外界访问。
- #ifndef MAINWINDOW_H
- #define MAINWINDOW_H
- #include
- #include
- #include "nehewidget.h"
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
- public:
- MainWindow(QWidget *parent = 0);
- ~MainWindow();
- public slots:
- void setXRotation(int angle);
- void setYRotation(int angle);
- void setZRotation(int angle);
- signals:
- void xRotationChanged(int angle);
- void yRotationChanged(int angle);
- void zRotationChanged(int angle);
- protected:
- //鼠标事件处理
- void mousePressEvent(QMouseEvent *event);
- void mouseMoveEvent(QMouseEvent *event);
- private:
- NeHeWidget *neheWidget;
- QPoint lastPos;
- void normalizeAngle(int *angle);
- int xRot;
- int yRot;
- int zRot;
- };
- #endif // MAINWINDOW_H
扩展介绍:信号和槽机制是Qt的核心机制,信号和槽是一种高级接口,应用于对象之间的通信,它是Qt的核心特征,也是Qt区别与其它工具包的重要地方,信号和槽是Qt自行定义的一种通信机制,它独立于标准C/C++语言,因此要正确处理信号和槽,必须借助一个成为moc(Meta Object Compiler)的Qt工具,该工具是一个C++预处理程序,它为高层次的事件处理自动生成所需要的附加代码,在我们熟知的很多GUI工具中窗口小部件(widget) 都有一个回调函数用于响应他们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针,但是在Qt中信号和槽取代了这种l凌乱的函数指针,它使得我们编写这些通信程序更为简洁命了,信号和槽能携带任意数量和任意类型的参数,他们是类型完全安全的,不会像回调函数那样产生core dunps。所有从QObject 或其子类(例如QWidget)派生的类都能购包含信号和槽,当对象改变其状态时,信号就由该对象发射(emit)出去,这就是对象所要做的全部事情,他不知道另一端是谁在接收这个信号,这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用,槽用于接收信号,但他们是普通的对象成员函数,一个槽并不知道是否有任何信号与自己相链接,而且,对象并不了解具体的通信机制。你可以将很多信号与单个槽进行连接,也可将单个信号与很多槽进行连接,甚至将一个信号与另外一个信号连接也是可能的,这时无论第一个信号什么时候发射,系统都会立刻发射第二个信号,总之信号与槽构造类一个强大的部件编程机制。
信号:当某个信号对其客户或者所有者发生的内部状态发生改变,信号被一个对象发射,只有定义过这个信号的类以及其派生类能够发射这个信号,当一个信号被发射时,与其相关联的槽会被立刻执行,就像一个正常的函数调用一样,信号-槽机制完全独立于任何GUI事件循环,只有当所有的槽返回以后发射函数(emit)才返回,如果存在多个槽与某个信号相关联,那么当这个信号被发射时,这些槽会一个接一个地执行,但是它们执行顺序是随机的、不确定的,我们不能人为的指定那个先执行、哪个后执行。信号的声明在头文件中进行的,QT的signals关键字指出进入类信号声明区,随后即可声明自己的信号。
槽:槽是普通的C++成员函数,可以被正常调用,他们唯一的特殊性就是很多信号可以与其关联,当与其关联信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值。既然槽是普通成员函数,因此与其他函数一样,他们也有存取权限,槽的存取权限决定类谁能与其相关联,同普通的C++成员函数一样,槽函数也分为三种类型,public slots, private slots, protected slots。public slots:在这个区内声明的槽意味着任何对象都可将信号与之相连,这对于组件编程非常有用,你可以创建彼此互补了解的对象,将它们的信号与槽进行链接以便信息能够正确的传递。protected slots:在这个区内声明的槽意味着当前类以及其子类可以将信号与之相链接,这适用于那些槽,他们是类实现的一部分,但其界面接口却面向外部。private slots:在这个区内声明的槽意味着只有类字节可以将信号与之相连接,这适用于联系非常紧密的类。槽也能够声明为虚函数,这也是非常有用的,槽的声明也是在头文件中进行的。
信号与槽的关联:通过调用QObject对象的connect函数来将某个对象的信号与另外一个对象的槽函数相关联,这样当发射者发射信号时,接收者的槽函数将被调用,该函数定义如下:bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)[static]这个函数作用就是将发射者sender对象中的信号signal与接收者receiver中的member槽函数联系起来,当指定信号signal时必须使用QT的宏SIGNAL(),当指定槽函数时必须使用宏SLOT()。如果发射者与接收者属于同一个对象的话,那么在connect调用中接收者参数可以省略。当信号与槽没必要继续保持关联时,使用disconnect函数来断开链接,其定义如下: bool QObject::disconnect(const QObject *sender, const char *signal, const Object *receiver, const char *member)[static]这个函数可以断开发射者中的信号与接收者中槽函数之间的关联。在disconnect函数中0可以用作一个通配符,分别表示任何信号、任何接收对象、接收对象中的任何槽函数。但是发射者sender不能为0,其他三个参数值可以为0.
元对象编译器moc(mete object compiler)对C++文件中的类声明进行分析并产生用于初始化元对象的C++代码,元对象包含全部信号和槽的名字以及指向这些函数的指针,moc读C++源文件,如果发现有Q_OBJECT宏声明类,它会生成另外一个C++源文件,这个新生成的文件中包含该类的元对象代码,例如,假如我们有一个头文件mysignal.h,在这个文件中包含有信号或者槽的声明,那么在编译之前moc 工具就会根据该文件自动生成一个mysignal.moc.h的C++源文件并将其提交给编译器,类似地,对应与mysignal.cpp文件moc工具自动生辰mysignal.moc.cpp文件提交给编译器,元对象代码是signal/slot机制所必须的,用moc 产生C++源文件必须与类实现一起进行编译和连接,或者用#include语句将其包含到类的源文件中,moc并不扩展#include或者#define宏定义,它只是简单的跳过所遇到的任何预处理指令。
本实例中,信号xRotationChanged(int angle),即就是当angle变化的时候,则信号开始发射给对应的槽,MainWindow类中的受保护成员函数mousePressEvent(QMouseEvent *event)用于处理鼠标按下时的事件响应,mouseMoveEvent(QMouseEvent *event)用于处理鼠标移动时的事件相应,私有成员函数以及参数不能被外部调用,只能内部使用,包括函数normalizeAngle(int *angle)主要用于标准调整鼠标旋转角度,neheWidget, lastPos, xRot, yRot, zRot都是私有参数。
mainwindow.cpp主要对应于mainwindow.h中的定义编写实现具体的函数实体,按动鼠标左键可以拖动立方体进行空间自由旋转,按动鼠标右键自动退出。
nehewidget.h代码
- #include "mainwindow.h"
- #include "math.h"
- MainWindow::MainWindow(QWidget *parent) :
- QMainWindow(parent)
- {
- neheWidget = new NeHeWidget();
- setGeometry(100,100,1000,768);
- setWindowTitle(tr("Nehe's OpenGL Framework"));
- setCentralWidget(neheWidget);
- }
- MainWindow::~MainWindow()
- {
- }
- void MainWindow::normalizeAngle(int *angle)
- {
- while(*angle 360 * 16)
- *angle -= 360 * 16;
- }
- void MainWindow::setXRotation(int angle)
- {
- normalizeAngle(&angle);
- if(angle != xRot){
- xRot = angle;
- emit xRotationChanged(angle);
- neheWidget->setxRot(xRot);
- neheWidget-> updateGL();
- }
- }
- void MainWindow::setYRotation(int angle)
- {
- normalizeAngle(&angle);
- if(angle != yRot){
- yRot = angle;
- emit yRotationChanged(angle);
- neheWidget->setyRot(yRot);
- neheWidget -> updateGL();
- }
- }
- void MainWindow::setZRotation(int angle)
- {
- normalizeAngle(&angle);
- if(angle != zRot){
- zRot = angle;
- emit zRotationChanged(angle);
- neheWidget->setzRot(zRot);
- neheWidget -> updateGL();
- }
- }
- void MainWindow::mousePressEvent(QMouseEvent *event)
- {
- lastPos = event ->pos();
- }
- void MainWindow::mouseMoveEvent(QMouseEvent *event)
- {
- int dx = event -> x() - lastPos.x();
- int dy = event -> y() - lastPos.y();
- switch(event -> buttons())
- {
- case Qt::LeftButton:
- setXRotation(xRot + 2 * dy);
- setYRotation(yRot + 2 * dx);
- setZRotation(zRot + 2 * dx);
- break;
- case Qt::RightButton:
- close();
- break;
- }
- lastPos = event -> pos();
- }
- #ifndef NEHEWIDGET_H
- #define NEHEWIDGET_H
- #include
- #include
- #include
- class NeHeWidget:public QGLWidget
- {
- Q_OBJECT
- public:
- explicit NeHeWidget(QWidget *parent = 0);
- ~NeHeWidget();
- void setxRot(int x){xRot = x;}
- void setyRot(int y){yRot = y;}
- void setzRot(int z){zRot = z;}
- protected:
- //设置渲染环境
- void initializeGL();
- //绘制窗口
- void paintGL();
- //响应窗口的大小变化
- void resizeGL(int width, int height);
- //加载纹理函数
- void loadGLTextures();
- //texture用来存储纹理
- GLuint texture[1];
- private:
- int xRot;
- int yRot;
- int zRot;
- };
- #endif // NEHEWIDGET_H
该头文件主要用来定义如何调用OpenGL模块实现三维立体渲染。
对具体定义分别介绍:
#include,其中QGLWidget类用来绘制OpenGL图形的窗口,QGLWidget提供一系列的函数来在一个QT应用程序里面绘制OpenGL,用起来很简单,我们可以派生它,然后使用像其他任何窗口一样使用子类,除非你选择类使用QPainter和标准的OpenGL绘图命令,QGLWidget提供三个方便的虚函数,我们可以在子类中重写他们,来完成一些典型OpenGL任务:1. paintGL()函数,绘制OpenGL图像,当窗口需要被刷新时候被调用;2.resizeGL()函数,建立OpenGL的视图窗口等一系列,当窗口大小改变时候被调用,(当第一次显示时候也会被调用,因为所有新创建的窗口都自动得到一个改变的大小事件);3.intializeGL()建立OpenGL绘图的上下文环境,声明播放列表等等,在第一次调用resizeGL()或paintGL()调用前使用。
#include,因为要包含两个类的定义,所以使用该声明,NeHeWidget类继承于QGLWidget类。
Q_OBJECT宏作用,只有加入此宏定义,你才能使用QT中的signal和slot机制。
NeHeWidget类的公共成员函数:explicit NeHeWidget(QWidget *parent=0),explicit用于构造函数,用来抑制隐式转换。扩展:widget被创建时都是不可见的,widget中可容纳其它widget,Qt中的widget在用户行为或者状态改变时会emit signal, QWidget类的构造函数需要一个QWidget*指针作为参数,表示其parent widget(默认值为0,即不存在parent widget ),在parent widget被删除时,Qt会自动删除其所有的child widget,Qt中有三种Layout Manager类:QHBoxLayout, QVBoxLayOut, QGridLayOut,基本模式是将widget添加进LayOut,由Layout自动接管widget的尺寸和位置。
nehewidget.cpp
nehewidget.cpp主要针对于nehewidget.h中的定义实现具体相对应的函数实体,对几个 函数进行解释说明:
- #include "nehewidget.h"
- #include
- #define PI 3.1415926
- NeHeWidget::NeHeWidget(QWidget *parent):
- QGLWidget(parent)
- {
- }
- NeHeWidget::~NeHeWidget()
- {
- }
- void NeHeWidget::initializeGL()
- {
- //启用阴影平滑
- glShadeModel(GL_SMOOTH);
- //黑色背景
- glClearColor(0.0,0.0,0.0,0.0);
- //设置深度缓存
- glClearDepth(1.0);
- //启用深度测试
- glEnable(GL_DEPTH_TEST);
- //所作深度测试的类型
- glDepthFunc(GL_LEQUAL);
- //告诉系统对透视进行修正
- glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
- //加载纹理
- loadGLTextures();
- glEnable(GL_TEXTURE_2D);
- }
- void NeHeWidget::paintGL()
- {
- // 清除屏幕和深度缓存
- glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
- glLoadIdentity();
- //移到屏幕的左半部分,并且将视图推入屏幕背后足够的距离以便我们可以看见全部的场景
- glTranslatef(0.0f,0.0f,-5.0f);
- glRotatef( xRot/16, 1.0, 0.0, 0.0 );
- glRotatef( yRot/16, 0.0, 1.0, 0.0 );
- glRotatef( zRot/16, 0.0, 0.0, 1.0 );
- //选择使用的纹理
- glBindTexture( GL_TEXTURE_2D, texture[0] );
- glBegin( GL_QUADS );
- 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 );
- 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 );
- 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 );
- 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 );
- 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 );
- 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();
- }
- //重置OpenGL窗口大小
- void NeHeWidget::resizeGL(int width, int height)
- {
- //防止窗口大小变为0
- if(height == 0)
- {
- height = 1;
- }
- //重置当前的视口
- glViewport(0,0,(GLint)width,(GLint)height);
- //选择投影矩阵
- glMatrixMode(GL_PROJECTION);
- //重置投影矩阵
- glLoadIdentity();
- //设置视口大小
- gluPerspective(45.0,(GLfloat)width/(GLfloat)height,0.1,100.0);
- //选择模型观察矩阵
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- }
- //纹理装载函数
- void NeHeWidget::loadGLTextures()
- {
- QImage tex,buf;
- if(!buf.load("/home/wangchuan/qtcreator-2.4.1/bin/Program/GLFrameWork/GLFrameWork/xingkong.jpg"))
- {
- //如果载入不成功,自动生成一个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(1, &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_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- }
glShadeModel函数,用于控制OpenGL中绘制指定两点间其他点颜色的过渡模式,参数一般为GL_SMOOTH(默认),GL_FLAT,OpenGL默认是将制定的两点颜色进行插值,绘制之间的其他点,如果两点颜色相同,使用两个参数效果相同,如果两点颜色不同,GL_SMOOTH会出现过渡效果,GL_FLAT则只是以指定的某一点的单一色绘制其他的所有点;glClearColor函数来自OPENGL,为颜色缓冲区指定确定的值,指定red,green,blue,alpha(透明)的值,当颜色缓冲区清空时使用,默认值都是0,其取值范围在0~1之间;glClearDepth函数,设置深度缓存的清除值,depth--指定清除深度缓存时使用的深度值,该值在[0,1]之间,如果设定为0.5,那么物体只有像素深度小于0.5的那部分才可见;glDepthFunc(GLenum func)函数,func:指定“目标像素与当前像素在z方向值大小比较”的函数,符合此函数关系的目标像素才进行绘制,否则目标像素不予绘制,该函数只有启用“深度测试时”glEnable(GL_DEPTH_TEST)和glDisable(GL_DEPTH_TEST)时才有效,参数:GL_LEQUAL如果目标像素z值
关注打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?