Qt+OpenGL教程.docx
《Qt+OpenGL教程.docx》由会员分享,可在线阅读,更多相关《Qt+OpenGL教程.docx(100页珍藏版)》请在冰豆网上搜索。
![Qt+OpenGL教程.docx](https://file1.bdocx.com/fileroot1/2023-7/13/a2ea0c11-ab1f-4f36-9b00-96c29fdf8056/a2ea0c11-ab1f-4f36-9b00-96c29fdf80561.gif)
Qt+OpenGL教程
QtOpenGL教程
QtOpenGL的准备工作
第三课:
上色
第四课:
旋转
第一课:
创建一个OpenGL窗口
第二课:
你的第一个多边形
第五课:
向三维进军
第六课:
纹理映射
第七课:
纹理滤波、光源和键盘控制
第八课:
融合
第九课:
在三维空间中移动位图
第十课:
载入一个三维世界并在其中移动
第十一课:
旗的效果(波动纹理)
第十二课:
显示列表
第十三课:
位图字体
第十四课:
轮廓字体
第十五课:
使用纹理映射的轮廓字体
第十六课:
看起来很棒的雾
因为本教程是从NeHe的OpenGL教程迁移过来的,代码变为Qt实现的。
所以有的课程一时还没有实现成功,所以可能有些教程是跳跃的。
因本人时间有限,所以难免有错误出现,如果您发现了这些错误,或者有什么建议,请来信指教,谢谢。
QtOpenGL的准备工作
因为Qt存在很多版本,另外它支持的平台也很多,到目前为止我只实验了几个组合,所以就先把这些列出来吧,欢迎大家补充。
Unix/X11
Linux
Qt:
自由版或者企业版都支持OpenGL模块,而专业版则不能。
我现在使用的是3.1.0自由版和企业版。
gcc:
编译器。
我现在使用的是3.2。
X:
Linux下的图形环境。
我现在使用的是4.2.0。
Mesa:
自由的OpenGL。
我现在使用的是5.0。
Windows
Qt:
企业版支持OpenGL模块,而专业版则不能。
我现在使用的是3.1.0企业版。
MicrosoftVisualStudio:
编译器。
我现在使用的是6.0。
创建一个OpenGL窗口
我假设您对Qt编程已经有了一定的了解,如果您还没有熟悉Qt编程,建议您先学习一下Qt编程的基础知识。
Qt中已经包含了OpenGL模块,具体情况您可以参考QtOpenGL模块的相关内容。
NeHeWidget类
这就是我们继承QGLWidget类得到的OpenGL窗口部件类。
(由nehewidget.h展开。
)
#include
classNeHeWidget:
publicQGLWidget
{
Q_OBJECT
因为QGLWidget类被包含在qgl.h头文件中,所以我们的类就需要包含这个头文件。
Q_OBJECT是Qt中的一个专用的宏,具体说明请参见Qt的文档。
public:
NeHeWidget(QWidget*parent=0,constchar*name=0,boolfs=false);
~NeHeWidget();
protected:
voidinitializeGL();
voidpaintGL();
voidresizeGL(intwidth,intheight);
因为QGLWidget类已经内置了对OpenGL的处理,就是通过对initializeGL()、paintGL()和resizeGL()这个三个函数实现的,具体情况可以参考QGLWidget类的文档。
因为我们的这个QtOpenGL教程取材于NeHeOpenGL教程,所以这里就用这个NeHeWidget类来继承QGLWidget类来使用相关OpenGL的功能。
initializeGL()是用来初始化这个OpenGL窗口部件的,可以在里面设定一些有关选项。
paintGL()就是用来绘制OpenGL的窗口了,只要有更新发生,这个函数就会被调用。
resizeGL()就是用来处理窗口大小变化这一事件的,width和height就是新的大小状态下的宽和高了,另外resizeGL()在处理完后会自动刷新屏幕。
voidkeyPressEvent(QKeyEvent*e);
这是Qt里面的鼠标按下事件处理函数。
protected:
boolfullscreen;
用来保存窗口是否处于全屏状态的变量。
};
(由nehewidget.cpp展开。
)
#include"nehewidget.h"
NeHeWidget:
:
NeHeWidget(QWidget*parent,constchar*name,boolfs)
:
QGLWidget(parent,name)
{
fullscreen=fs;
保存窗口是否为全屏的状态。
setGeometry(0,0,640,480);
设置窗口的位置,即左上角为(0,0)点,大小为640*480。
setCaption("NeHe'sOpenGLFramework");
setWindowTitle("NeHe'sOpenGLFramework");
设置窗口的标题为“NeHe'sOpenGLFramework”。
if(fullscreen)
showFullScreen();
如果fullscreen为真,那么就全屏显示这个窗口。
}
这个是构造函数,parent就是父窗口部件的指针,name就是这个窗口部件的名称,fs就是窗口是否最大化。
NeHeWidget:
:
~NeHeWidget()
{
}
这个是析构函数。
voidNeHeWidget:
:
initializeGL()
{
glShadeModel(GL_SMOOTH);
这一行启用smoothshading(阴影平滑)。
阴影平滑通过多边形精细的混合色彩,并对外部光进行平滑。
我将在另一个教程中更详细的解释阴影平滑。
GL_FLAT/orGL_SMOOTH
glClearColor(0.0,0.0,0.0,0.0);
这一行设置清除屏幕时所用的颜色。
如果您对色彩的工作原理不清楚的话,我快速解释一下。
色彩值的范围从0.0到1.0。
0.0代表最黑的情况,1.0就是最亮的情况。
glClearColor后的第一个参数是红色,第二个是绿色,第三个是蓝色。
最大值也是1.0,代表特定颜色分量的最亮情况。
最后一个参数是Alpha值。
当它用来清除屏幕的时候,我们不用关心第四个数字。
现在让它为0.0。
我会用另一个教程来解释这个参数。
通过混合三种原色(红、绿、蓝),您可以得到不同的色彩。
希望您在学校里学过这些。
因此,当您使用glClearColor(0.0,0.0,1.0,0.0),您将用亮蓝色来清除屏幕。
如果您用glClearColor(0.5,0.0,0.0,0.0)的话,您将使用中红色来清除屏幕。
不是最亮(1.0),也不是最暗(0.0)。
要得到白色背景,您应该将所有的颜色设成最亮(1.0)。
要黑色背景的话,您该将所有的颜色设为最暗(0.0)。
glClearDepth(1.0);
设置深度缓存。
glEnable(GL_DEPTH_TEST);
启用深度测试。
glDepthFunc(GL_LEQUAL);
所作深度测试的类型。
上面这三行必须做的是关于depthbuffer(深度缓存)的。
将深度缓存设想为屏幕后面的层。
深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。
它的排序决定那个物体先画。
这样您就不会将一个圆形后面的正方形画到圆形上来。
深度缓存是OpenGL十分重要的部分。
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
真正精细的透视修正。
这一行告诉OpenGL我们希望进行最好的透视修正。
这会十分轻微的影响性能。
但使得透视图看起来好一点。
}
这个函数中,我们对OpenGL进行所有的设置。
我们设置清除屏幕所用的颜色,打开深度缓存,启用smoothshading(阴影平滑),等等。
这个例程直到OpenGL窗口创建之后才会被调用。
voidNeHeWidget:
:
paintGL()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
清楚屏幕和深度缓存。
glLoadIdentity();
重置当前的模型观察矩阵。
}
这个函数中包括了所有的绘图代码。
任何您所想在屏幕上显示的东东都将在此段代码中出现。
以后的每个教程中我都会在例程的此处增加新的代码。
如果您对OpenGL已经有所了解的话,您可以在glLoadIdentity()调用之后,函数返回之前,试着添加一些OpenGL代码来创建基本的形。
如果您是OpenGL新手,等着我的下个教程。
目前我们所作的全部就是将屏幕清除成我们前面所决定的颜色,清除深度缓存并且重置场景。
我们仍没有绘制任何东东。
voidNeHeWidget:
:
resizeGL(intwidth,intheight)
{
if(height==0)
{
height=1;
}
防止height为0。
glViewport(0,0,(GLint)width,(GLint)height);
重置当前的视口(Viewport)。
glMatrixMode(GL_PROJECTION);
选择投影矩阵。
glLoadIdentity();
重置投影矩阵。
gluPerspective(45.0,(GLfloat)width/(GLfloat)height,0.1,100.0);
建立透视投影矩阵。
glMatrixMode(GL_MODELVIEW);
选择模型观察矩阵。
glLoadIdentity();
重置模型观察矩阵。
}
上面几行为透视图设置屏幕。
意味着越远的东西看起来越小。
这么做创建了一个现实外观的场景。
此处透视按照基于窗口宽度和高度的45度视角来计算。
0.1,100.0是我们在场景中所能绘制深度的起点和终点。
glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响projectionmatrix(投影矩阵)。
投影矩阵负责为我们的场景增加透视。
glLoadIdentity()近似于重置。
它将所选的矩阵状态恢复成其原始状态。
调用glLoadIdentity()之后我们为场景设置透视图。
glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响modelviewmatrix(模型观察矩阵)。
模型观察矩阵中存放了我们的物体讯息。
最后我们重置模型观察矩阵。
如果您还不能理解这些术语的含义,请别着急。
在以后的教程里,我会向大家解释。
只要知道如果您想获得一个精彩的透视场景的话,必须这么做。
这个函数的作用是重新设置OpenGL场景的大小,而不管窗口的大小是否已经改变(假定您没有使用全屏模式)。
甚至您无法改变窗口的大小时(例如您在全屏模式下),它至少仍将运行一次——在程序开始时设置我们的透视图。
OpenGL场景的尺寸将被设置成它显示时所在窗口的大小。
voidNeHeWidget:
:
keyPressEvent(QKeyEvent*e)
{
switch(e->key())
{
caseQt:
:
Key_F2:
fullscreen=!
fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
setGeometry(0,0,640,480);
}
updateGL();
break;
如果按下了F2键,那么屏幕是否全屏的状态就切换一次。
然后再根据需要,显示所要的全屏窗口或者普通窗口。
caseQt:
:
Key_Escape:
close();
}
如果按下了Escape键,程序退出。
}
main.cpp
(由main.cpp展开。
)
#include
#include
Qt的应用程序都是一个QApplication类,所以qapplication.h必须要包含。
因为我们在进入OpenGL窗口之前让用户选择是否使用全屏窗口,所以使用了QMessageBox类,所以qmessagebox.h也要包含。
#include"nehewidget.h"
intmain(intargc,char**argv)
{
boolfs=false;
我们把这个布尔型变量的初始值设置为false。
QApplicationa(argc,argv);
每一个Qt应用程序都使用QApplication类。
switch(QMessageBox:
:
information(0,
"StartFullScreen?
",
"WouldYouLikeToRunInFullscreenMode?
",
QMessageBox:
:
Yes,
QMessageBox:
:
No|QMessageBox:
:
Default))
{
caseQMessageBox:
:
Yes:
fs=true;
break;
caseQMessageBox:
:
No:
fs=false;
break;
}
这里弹出一个消息对话框,让用户选择是否使用全屏模式。
NeHeWidgetw(0,0,fs);
创建一个NeHeWidget对象。
a.setMainWidget(&w);
设置应用程序的主窗口部件为w。
w.show();
显示w。
returna.exec();
程序返回。
}
本课程的源代码。
你的第一个多边形
上一课中,我教您如何创建一个OpenGL窗口。
这一课中,我将教您如何创建三角形和四边形。
我们讲使用GL_TRIANGLES来创建一个三角形,GL_QUADS来创建一个四边形。
我们只要修改第一课中的NeHeWidget类中的paintGL()函数就可以了。
NeHeWidget类
(由nehewidget.cpp展开。
)
voidNeHeWidget:
:
paintGL()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
清除屏幕和深度缓存。
GlLoadIdentity();
重置当前的模型观察矩阵。
当您调用glLoadIdentity()之后,您实际上将当前点移到了屏幕中心,X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。
OpenGL屏幕中心的坐标值是X和Y轴上的0.0点。
中心左面的坐标值是负值,右面是正值。
移向屏幕顶端是正值,移向屏幕底端是负值。
移入屏幕深处是负值,移出屏幕则是正值。
glTranslatef(-1.5,0.0,-6.0);
glTranslatef(x,y,z)沿着X,Y和Z轴移动。
根据前面的次序,下面的代码沿着X轴左移1.5个单位,Y轴不动(0.0),最后移入屏幕6.0个单位。
注意在glTranslatef(x,y,z)中当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。
现在我们已经移到了屏幕的左半部分,并且将视图推入屏幕背后足够的距离以便我们可以看见全部的场景-创建三角形。
glBegin(GL_TRIANGLES);
开始绘制三角形。
glBegin(GL_TRIANGLES)的意思是开始绘制三角形,glEnd()告诉OpenGL三角形已经创建好了。
通常您会需要画3个顶点,可以使用GL_TRIANGLES。
在绝大多数的显卡上,绘制三角形是相当快速的。
如果要画四个顶点,使用GL_QUADS的话会更方便。
但据我所知,绝大多数的显卡都使用三角形来为对象着色。
最后,如果您想要画更多的顶点时,可以使用GL_POLYGON。
本节的简单示例中,我们只画一个三角形。
如果要画第二个三角形的话,可以在这三点之后,再加三行代码(3点)。
所有六点代码都应包含在glBegin(GL_TRIANGLES)和glEnd()之间。
在他们之间再不会有多余的点出现,也就是说,(GL_TRIANGLES)和glEnd()之间的点都是以三点为一个集合的。
这同样适用于四边形。
如果您知道实在绘制四边形的话,您必须在第一个四点之后,再加上四点为一个集合的点组。
另一方面,多边形可以由任意个顶点,(GL_POLYGON)不在乎glBegin(GL_TRIANGLES)和glEnd()之间有多少行代码。
glVertex3f(0.0,1.0,0.0);
上顶点。
glBegin之后的第一行设置了多边形的第一个顶点,glVertex的第一个参数是X坐标,然后依次是Y坐标和Z坐标。
第一个点是上顶点,然后是左下顶点和右下顶点。
glEnd()告诉OpenGL没有其他点了。
这样将显示一个填充的三角形。
CKer注:
这里要注意的是存在两种不同的坐标变换方式,glTranslatef(x,y,z)中的x,y,z是相对与您当前所在点的位移,但glVertex(x,y,z)是相对于glTranslatef(x,y,z)移动后的新原点的位移。
因而这里可以认为glTranslate移动的是坐标原点,glVertex中的点是相对最新的坐标原点的坐标值。
glVertex3f(-1.0,-1.0,0.0);
左下顶点。
glVertex3f(1.0,-1.0,0.0);
右下顶点。
glEnd();
三角形绘制结束。
glTranslatef(3.0,0.0,0.0);
在屏幕的左半部分画完三角形后,我们要移到右半部分来画正方形。
为此要再次使用glTranslate。
这次右移,所以X坐标值为正值。
因为前面左移了1.5个单位,这次要先向右移回屏幕中心(1.5个单位),再向右移动1.5个单位。
总共要向右移3.0个单位。
glBegin(GL_QUADS);
开始绘制四边形。
glVertex3f(-1.0,1.0,0.0);
左上顶点。
glVertex3f(1.0,1.0,0.0);
右上顶点。
glVertex3f(1.0,-1.0,0.0);
右下顶点。
glVertex3f(-1.0,-1.0,0.0);
左下顶点。
glEnd();
四边形绘制结束。
}
本课程的源代码。
上色
上一课中我教给您三角形和四边形的绘制方法。
这一课我将教您给三角形和四边形添加两种不同类型的着色方法。
使用单调着色(Flatcoloring)给四边形涂上固定的一种颜色。
使用平滑着色(Smoothcoloring)将三角形的三个顶点的不同颜色混合在一起,创建漂亮的色彩混合。
我们只要修改第二课中的NeHeWidget类中的paintGL()函数就可以了。
NeHeWidget类
(由nehewidget.cpp展开。
)
voidNeHeWidget:
:
paintGL()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(-1.5,0.0,-6.0);
glBegin(GL_TRIANGLES);
glColor3f(1.0,0.0,0.0);
红色。
如果您还记得上节课的内容,这段代码在屏幕的左半部分绘制三角形。
这一行代码是我们第一次使用命令glColor3f(r,g,b)。
括号中的三个参数依次是红、绿、蓝三色分量。
取值范围可以从0.0到1.0。
类似于以前所讲的清除屏幕背景命令。
我们将颜色设为红色(纯红色,无绿色,无蓝色)。
glVertex3f(0.0,1.0,0.0);
上顶点。
接下来的一行代码设置三角形的第一个顶点(三角形的上顶点),并使用当前颜色(红色)来绘制。
从现在开始所有的绘制的对象的颜色都是红色,直到我们将红色改变成别的什么颜色。
glColor3f(0.0,1.0,0.0);
绿色。
glVertex3f(-1.0,-1.0,0.0);
左下顶点。
glColor3f(0.0,0.0,1.0);
蓝色。
glVertex3f(1.0,-1.0,0.0);
右下顶点。
glEnd();
glEnd()出现后,三角形将被填充。
但是因为每个顶点有不同的颜色,因此看起来颜色从每个角喷出,并刚好在三角形的中心汇合,三种颜色相互混合。
这就是平滑着色。
glTranslatef(3.0,0.0,0.0);
glColor3f(0.5,0.5,1.0);
一次性将颜色设置为蓝色。
现在我们绘制一个单调着色——蓝色的正方形。
最重要的是要记住,设置当前色之后绘制的所有东东都是当前色的。
以后您所创建的每个工程都要使用颜色。
即便是在完全采用纹理贴图的时候,glColor3f仍旧可以用来调节纹理的色调。
等等...,以后再说吧。
我们必须要做的事只需将颜色一次性的设为我们想采用的颜色(本例采用蓝色),然后绘制场景。
每个顶点都是蓝色的,因为我们没有告诉OpenGL要改变顶点的颜色。
最后的结果是.....全蓝色的正方形。
再说一遍,顺时针绘制的正方形意味着我们所看见的是四边形的背面。
glBegin(GL_QUADS);
glVertex3f(-1.0,1.0,0.0);
glVertex3f(1.0,1.0,0.0);
glVertex3f(1.0,-1.0,0.0);
glVertex3f(-1.0,-1.0,0.0);
glEnd();
}
在这一课中,我试着尽量详细的解释如何为您的OpenGL多边形添加单调和平滑的着色效果的步骤。
改改代码中的红绿蓝分量值,看看最后有什么样的结果。
本课程的源代码。
旋转
上一课中我教给您三角形和四边形的着色。
这一课我将教您如何将这些彩色对象绕着坐标轴旋转。
其实只需在上节课的代码上增加几行就可以了。
我们将在NeHeWidget类中增加两个变量来控制这两个对象的旋转。
它们是浮点类型的变量,使得我们能够非常精确地旋转对象。
浮点数包含小数位置,这意味着我们无需使用1、2、3...的角度。
你会发现浮点数是OpenGL编程的基础。
新变量中叫做rTri的用来旋转三角形,rQuad旋转四边形。
NeHeWidget类
(由nehewidget.h展开。
)
protected:
boolfullscreen;
GLfloatrTri;
GLfloatrQuad;
};
上面就是添加的两个变量。
rTri是用于三角形的角度,rQuad是用于四边形的角度。
(由nehewidget.cpp展开。
)
NeHeWidget:
:
NeHeWidget(QWidget*parent,constchar*name,bool