Qt_OpenGL_教程
Qt OpenGL教程
Lesson 1. 创建一个 OpenGL窗口
我假设您对 Qt编程已经有了一定的了解,如果您还没有熟悉 Qt编程,建议您先学习一
下 Qt 编程的基础知识。Qt 中已经包含了 OpenGL 模块,具体情况您可以参考 Qt OpenGL
模块的相关内容。
NeHeWidget类 这就是我们继承 QGLWidget类得到的 OpenGL窗口部件类。
(由 nehewidget.h展开。)
#include
class NeHeWidget : public QGLWidg...
Qt OpenGL教程
Lesson 1. 创建一个 OpenGL窗口
我假设您对 Qt编程已经有了一定的了解,如果您还没有熟悉 Qt编程,建议您先学习一
下 Qt 编程的
。Qt 中已经包含了 OpenGL 模块,具体情况您可以参考 Qt OpenGL
模块的相关内容。
NeHeWidget类 这就是我们继承 QGLWidget类得到的 OpenGL窗口部件类。
(由 nehewidget.h展开。)
#include
class NeHeWidget : public QGLWidget
{
Q_OBJECT
因为 QGLWidget 类被包含在 qgl.h 头文件中,所以我们的类就需要包含这个头文件。
Q_OBJECT是 Qt中的一个专用的宏,具体说明请参见 Qt的文档。
public:
NeHeWidget( QWidget* parent = 0, const char* name = 0, bool fs = false );
~NeHeWidget();
protected:
void initializeGL();
void paintGL();
void resizeGL( int width, int height );
因为 QGLWidget 类已经内置了对 OpenGL 的处理,就是通过对 initializeGL()、paintGL()和
resizeGL()这个三个函数实现的,具体情况可以参考 QGLWidget类的文档。
因为我们的这个Qt OpenGL教程取材于NeHe OpenGL教程,所以这里就用这个NeHeWidget
类来继承 QGLWidget类来使用相关 OpenGL的功能。
initializeGL()是用来初始化这个 OpenGL 窗口部件的,可以在里面设定一些有关选项。
paintGL()就是用来绘制 OpenGL的窗口了,只要有更新发生,这个函数就会被调用。resizeGL()
就是用来处理窗口大小变化这一事件的,width和 height就是新的大小状态下的宽和高了,
另外 resizeGL()在处理完后会自动刷新屏幕。
void keyPressEvent( QKeyEvent *e );
这是 Qt里面的鼠标按下事件处理函数。
protected:
bool fullscreen;
用来保存窗口是否处于全屏状态的变量。
};
zhangshengsen
删划线
zhangshengsen
删划线
Administrator
铅笔
Administrator
铅笔
Administrator
铅笔
(由 nehewidget.cpp展开。)
#include "nehewidget.h"
NeHeWidget::NeHeWidget( QWidget* parent, const char* name, bool fs )
: QGLWidget( parent, name )
{
fullscreen = fs;
保存窗口是否为全屏的状态。
setGeometry( 0, 0, 640, 480 );
设置窗口的位置,即左上角为(0,0)点,大小为 640*480。
setCaption( "NeHe's OpenGL Framework" );
设置窗口的标题为“NeHe's OpenGL Framework”。
if ( fullscreen )
showFullScreen();
如果 fullscreen为真,那么就全屏显示这个窗口。
}
这个是构造函数,parent 就是父窗口部件的指针,name 就是这个窗口部件的名称,fs 就是
窗口是否最大化。
NeHeWidget::~NeHeWidget()
{
}
这个是析构函数。
void NeHeWidget::initializeGL()
{
glShadeModel( GL_SMOOTH );
这一行启用 smooth shading(阴影平滑)。阴影平滑通过多边形精细的混合色彩,并对外部光
进行平滑。我将在另一个教程中更详细的解释阴影平滑。
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 );
所作深度测试的类型。
Administrator
铅笔
Administrator
铅笔
Administrator
铅笔
上面这三行必须做的是关于 depthbuffer(深度缓存)的。将深度缓存设想为屏幕后面的层。
深度缓存不断的对物体进入屏幕内部有多深进行跟踪。我们本节的程序其实没有真正使用深
度缓存,但几乎所有在屏幕上显示 3D场景 OpenGL程序都使用深度缓存。它的排序决定那
个物体先画。这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是 OpenGL
十分重要的部分。
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
真正精细的透视修正。这一行告诉 OpenGL我们希望进行最好的透视修正。这会十分轻微的
影响性能。但使得透视图看起来好一点。
}
这个函数中,我们对 OpenGL进行所有的设置。我们设置清除屏幕所用的颜色,打开深度缓
存,启用 smooth shading(阴影平滑),等等。这个例程直到 OpenGL 窗口创建之后才会被
调用。
void NeHeWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
清楚屏幕和深度缓存。
glLoadIdentity();
重置当前的模型观察矩阵。
}
这个函数中包括了所有的绘图代码。任何您所想在屏幕上显示的东东都将在此段代码中出
现。以后的每个教程中我都会在例程的此处增加新的代码。如果您对 OpenGL 已经有所了
解的话,您可以在 glLoadIdentity()调用之后,函数返回之前,试着添加一些 OpenGL代码来
创建基本的形。如果您是 OpenGL新手,等着我的下个教程。目前我们所作的全部就是将屏
幕清除成我们前面所决定的颜色,清除深度缓存并且重置场景。我们仍没有绘制任何东东。
void NeHeWidget::resizeGL( int width, int height )
{
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();
重置模型观察矩阵。
}
Administrator
铅笔
Administrator
铅笔
上面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的
场景。此处透视按照基于窗口宽度和高度的 45 度视角来计算。0.1,100.0 是我们在场景中
所能绘制深度的起点和终点。
glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响 projectionmatrix(投影矩
阵)。投影矩阵负责为我们的场景增加透视。glLoadIdentity()近似于重置。它将所选的矩阵
状态恢复成其原始状态。调用 glLoadIdentity()之后我们为场景设置透视图。
glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelviewmatrix(模型观察矩
阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型观察矩阵。如果您还不能
理解这些术语的含义,请别着急。在以后的教程里,我会向大家解释。只要知道如果您想获
得一个精彩的透视场景的话,必须这么做。
这个函数的作用是重新设置 OpenGL场景的大小,而不管窗口的大小是否已经改变(假定您
没有使用全屏模式)。甚至您无法改变窗口的大小时(例如您在全屏模式下),它至少仍将运
行一次——在程序开始时设置我们的透视图。OpenGL场景的尺寸将被设置成它显示时所在
窗口的大小。
void NeHeWidget::keyPressEvent( QKeyEvent *e )
{
switch ( e->key() )
{
case Qt::Key_F2:
fullscreen = !fullscreen;
if ( fullscreen )
{
showFullScreen();
}
else
{
showNormal();
setGeometry( 0, 0, 640, 480 );
}
updateGL();
break;
如果按下了 F2键,那么屏幕是否全屏的状态就切换一次。然后再根据需要,显示所要的全
屏窗口或者普通窗口。
case Qt::Key_Escape:
close();
}
如果按下了 Escape键,程序退出。
}
main.cpp (由 main.cpp展开。)
#include
#include
Qt的应用程序都是一个 QApplication类,所以 qapplication.h必须要包含。因为我们在进入
OpenGL 窗口之前让用户选择是否使用全屏窗口,所以使用了 QMessageBox 类,所以
qmessagebox.h也要包含。
#include "nehewidget.h"
int main( int argc, char **argv )
{
bool fs = false;
我们把这个布尔型变量的初始值设置为 false。
QApplication a(argc,argv);
每一个 Qt应用程序都使用 QApplication类。
switch( QMessageBox::information( 0,
"Start FullScreen?",
"Would You Like To Run In Fullscreen Mode?",
QMessageBox::Yes,
QMessageBox::No | QMessageBox:efault ) )
{
case QMessageBox::Yes:
fs = true;
break;
case QMessageBox::No:
fs = false;
break;
}
这里弹出一个消息对话框,让用户选择是否使用全屏模式。
NeHeWidget w( 0, 0, fs );
创建一个 NeHeWidget对象。
a.setMainWidget( &w );
设置应用程序的主窗口部件为 w。
w.show();
显示 w。
return a.exec();
程序返回。
}
Lesson 2. 你的第一个多边形
上一课中,我教您如何创建一个 OpenGL窗口。这一课中,我将教您如何创建三角形和四
边形。我们讲使用 GL_TRIANGLES来创建一个三角形,GL_QUADS来创建一个四边形。
我们只要修改第一课中的 NeHeWidget类中的 paintGL()函数就可以了。
NeHeWidget类 (由 nehewidget.cpp展开。)
void NeHeWidget::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 );
Administrator
铅笔
Administrator
铅笔
Administrator
铅笔
Administrator
铅笔
开始绘制四边形。
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();
四边形绘制结束。
}
Lesson 3. 上色
上 一课中我教给您三角形和四边形的绘制方法。这一课我将教您给三角形和四边形添加两
种不同类型的着色方法。使用单调着色(Flatcoloring)给四边 形涂上固定的一种颜色。使
用平滑着色(Smoothcoloring)将三角形的三个顶点的不同颜色混合在一起,创建漂亮的色
彩混合。
我们只要修改第二课中的 NeHeWidget类中的 paintGL()函数就可以了。
NeHeWidget类 (由 nehewidget.cpp展开。)
void NeHeWidget::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多边形添加单调和平滑的着色效果
的步骤。改改代码中的红绿蓝分量值,看看最后有什么样的结果。
Lesson 4. 旋转
上一课中我教给您三角形和四边形的着色。这一课我将教您如何将这些彩色对象绕着坐
标轴旋转。
其实只需在上节课的代码上增加几行就可以了。
我们将在 NeHeWidget类中增加两个变量来控制这两个对象的旋转。它们是浮点类型的
变量,使得我们能够非常精确地旋转对象。浮点数包含小数位置,这意味着我们无需使用 1、
2、3...的角度。你会发现浮点数是 OpenGL编程的基础。新变量中叫做 rTri的用来旋转三角
形,rQuad旋转四边形。
NeHeWidget类 (由 nehewidget.h展开。)
protected:
bool fullscreen;
GLfloat rTri;
GLfloat rQuad;
};
上面就是添加的两个变量。rTri是用于三角形的角度,rQuad是用于四边形的角度。
(由 nehewidget.cpp展开。)
NeHeWidget::NeHeWidget( QWidget* parent, const char* name, bool fs )
: QGLWidget( parent, name )
{
rTri = 0.0;
rQuad = 0.0;
fullscreen = fs;
setGeometry( 0, 0, 640, 480 );
setCaption( "NeHe's Rotation Tutorial" );
if ( fullscreen )
showFullScreen();
}
我们需要在构造函数中给 rTri和 rQuad赋初值,都是 0.0。
void NeHeWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
glTranslatef( -1.5, 0.0, -6.0 );
glRotatef( rTri, 0.0, 1.0, 0.0 );
glRotatef( Angle, Xvector, Yvector, Zvector )负责让对象绕某个轴旋转。这个函数有很多
用处。Angle 通常是个变量代表对象转过的角度。Xvector,Yvector 和 Zvector 三个参数则
共同决定旋转轴的方向。比如( 1, 0,0 )所描述的矢量经过 X坐标轴的 1个单位处并且方向向
右。( -1, 0, 0 )所描述的矢量经过 X坐标轴的 1个单位处,但方向向左。
D. Michael Traub:提供了对 Xvector , Yvector 和 Zvector 的上述解释。
为了更好的理解 X, Y 和 Z的旋转,我举些例子...
X轴-您正在使用一台台锯。锯片中心的轴从左至右摆放(就像 OpenGL中的 X轴)。
尖利的锯齿绕着 X轴狂转,看起来要么向上转,要么向下转。取决于锯片开始转时的方向。
这与我们在 OpenGL 中绕着 X 轴旋转什么的情形是一样的。(CKer 注:这会儿您要把脸蛋
凑向显示器的话,保准被锯开了花^-^。)
Y 轴-假设您正处于一个巨大的龙卷风中心,龙卷风的中心从地面指向天空(就像
OpenGL 中的 Y 轴)。垃圾和碎片围着 Y 轴从左向右或是从右向左狂转不止。这与我们在
OpenGL中绕着 Y轴旋转什么的情形是一样的。
Z轴-您从正前方看着一台风扇。风扇的中心正好朝着您(就像 OpenGL中的 Z轴)。风
扇的叶片绕着 Z 轴顺时针或逆时针狂转。这与我们在 OpenGL 中绕着 Z 轴旋转什么的情形
是一样的。
上面的一行代码中,如果 rtri等于 7,我们将三角形绕着 Y轴从左向右旋转 7 。您也可
以改变参数的值,让三角形绕着 X和 Y轴同时旋转。
glBegin( GL_TRIANGLES );
glColor3f( 1.0, 0.0, 0.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();
上面的绘制三角形的代码没有改变。在屏幕的左面画了一个彩色渐变三角形,并绕着 Y
轴从左向右旋转。
glLoadIdentity();
我们增加了另一个 glLoadIdentity()调用。目的是为了重置模型观察矩阵。如果我们没有
重置,直接调用 glTranslate的话,会出现意料之外的结果。因为坐标轴已经旋转了,很可能
没有朝着您所希望的方向。所以我们本来想要左右移动对象的,就可能变成上下移动了,取
决于您将坐标轴旋转了多少角度。试试将 glLoadIdentity() 注释掉之后,会出现什么结果。
重置模型观察矩阵之后,X、Y、Z轴都以复位,我们调用 glTranslate。您会注意到这次
我们只向右移了 1.5单位,而不是上节课的 3.0单位。因为我们重置场景的时候,焦点又回
到了场景的中心(0.0)处。这样就只需向右移 1.5单位就够了。当我们移到新位置后,绕 X轴
旋转四边形。正方形将上下转动。
glTranslatef( 1.5, 0.0, -6.0 );
glRotatef( rQuad, 1.0, 0.0, 0.0 );
绕 X轴旋转四边形。
glColor3f( 0.5, 0.5, 1.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();
rTri += 0.2;
rQuad -= 0.15;
我们在构造函数中已经将 rTri和 rQuad的值设为 0.0,在这里我们每绘制完一次图像,
就修改一下这两个变量。两个变量的变化会使对象的旋转角度发生变化。
尝试改变下面代码中的+和-,来体会对象旋转的方向是如何改变的。并试着将 0.2改成
1.0。这个数字越大,物体就转的越快,这个数字越小,物体转的就越慢。
}
在这一课中,我试着尽量详细的解释如何让对象绕某个轴转动。改改代码,试着让对象
绕着 Z轴、X+Y轴或者所有三个轴来转动。
Lesson 5. 向三维进军
在上一课的内容上作些扩展,我们现在开始生成真正的三维对象,而不是象前两节课中
那样在三维世界中的二维对象。我们给三角形增加一个左侧面,一个右侧面,一个后侧面来
生成一个金字塔(四棱锥)。给正方形增加左、右、上、下及背面生成一个立方体。
我们混合金字塔上的颜色,创建一个平滑着色的对象。给立方体的每一面则来个不同的
Administrator
铅笔
颜色。
其实只需在上节课的代码上增加几行就可以了。
NeHeWidget类 (由 nehewidget.cpp展开。)
void NeHeWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glLoadIdentity();
glTranslatef( -1.5, 0.0, -6.0 );
glRotatef( rTri, 0.0, 1.0, 0.0 );
有些人可能早已在上节课中的代码上尝试自行创建 3D对象了。但经常有人来信问我:
“我的对象怎么不会绕着其自身的轴旋转?看起来总是在满屏乱转。”要让您的对象绕自身
的轴旋转,您必须让对象的中心坐标总是( 0.0, 0,0, 0,0 )。
下面的代码创建一个绕者其中心轴旋转的金字塔。金字塔的上顶点离中心一个单位,底
面离中心也是一个单位。上顶点在底面的投影位于底面的中心。
注意所有的面-三角形都是逆时针次序绘制的。这点十分重要,在以后的课程中我会作
出解释。现在,您只需明白要么都逆时针,要么都顺时针,但永远不要将两种次序混在一起,
除非您有足够的理由必须这么做。
glBegin( GL_TRIANGLES );
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f( -1.0, -1.0, 1.0 );
glColor3f( 0.0, 0.0, 1.0 );
glVertex3f( 1.0, -1.0, 1.0 );
上面是我们绘制的金字塔的前侧面。因为所有的面都共享上顶点,我们将这点在所有的
三角形中都设置为红色。底边上的两个顶点的颜色则是互斥的。前侧面的左下顶点是绿色的,
右下顶点是蓝色的。这样相邻右侧面的左下顶点是蓝色的,右下顶点是绿色的。这样四边形
的底面上的点的颜色都是间隔排列的。
还应注意到后面的三个侧面和前侧面处于同一个 glBegin( GL_TRIANGLES)和 glEnd()
语句中间。因为我们是通过三角形来构造这个金字塔的。OpenGL知道每三个点构成一个三
角形。当它画完一个三角形之后,如果还有余下的点出现,它就以为新的三角形要开始绘制
了。OpenGL在这里并不会将四点画成一个四边形,而是假定新的三角形开始了。所以千万
不要无意中增加任何多余的点。
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glColor3f( 0.0, 0.0, 1.0 );
glVertex3f( 1.0, -1.0, 1.0 );
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f( 1.0, -1.0, -1.0 );
绘制右侧面。注意其底边上的两个顶点的 X 坐标位于中心右侧的一个单位处。顶点则
位于 Y轴上的一单位处,且 Z坐标正好处于底边的两顶点的 Z坐标中心。右侧面从上顶点
开始向外侧倾斜至底边上。
这次的左下顶点用蓝色绘制,以保持与前侧面的右下顶点的一致。蓝色将从这个角向金
字塔的前侧面和右侧面扩展并与其他颜色混合。
zhangshengsen
高亮
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f( 1.0, -1.0, -1.0 );
glColor3f( 0.0, 0.0, 1.0 );
glVertex3f( -1.0, -1.0, -1.0 );
后侧面。再次切换颜色。左下顶点又回到绿色,因为后侧面与右侧面共享这个角。
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 0.0, 1.0, 0.0 );
glColor3f( 0.0, 0.0, 1.0 );
glVertex3f( -1.0, -1.0, -1.0 );
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f( -1.0, -1.0, 1.0 );
最后画左侧面。又要切换颜色。左下顶点是蓝色,与后侧面的右下顶点相同。右下顶点
是蓝色,与前侧面的左下顶点相同。
到这里金字塔就画完了。因为金字塔只绕着 Y 轴旋转,我们永远都看不见底面,因而
没有必要添加底面。如果您觉得有经验了,尝试增加底面(正方形),并将金字塔绕 X轴旋
转来看看您是否作对了。确保底面四个顶点的颜色与侧面的颜色相匹配。
glEnd();
接下来开始画立方体。他由六个四边形组成。所有的四边形都以逆时针次序绘制。就是
说先画右上角,然后左上角、左下角、最后右下角。您也许认为画立方体的背面的时候这个
次序看起来好像顺时针,但别忘了我们从立方体的背后看背面的时候,与您现在所想的正好
相反。(译者注:您是从立方体的外面来观察立方体的。)
glLoadIdentity();
glTranslatef( 1.5, 0.0, -7.0 );
注意到这次我们将立方体移地更远离屏幕了。因为立方体的大小要比金字塔大,同样移
入 6个单位时,立方体看起来要大的多。这是透视的缘故。越远的对象看起来越小 。
glRotatef( rQuad, 1.0, 1.0, 1.0 );
glBegin( GL_QUADS );
glColor3f( 0.0, 1.0, 0.0 );
glVertex3f( 1.0, 1.0, -1.0 );
glVertex3f( -1.0, 1.0, -1.0 );
glVertex3f( -1.0, 1.0, 1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
先画立方体的顶面。从中心上移一单位,注意 Y 坐标始终为一单位,表示这个四边形
与 Z 轴平行。先画右上顶点,向右一单位,再屏幕向里一单位。然后左上顶点,向左一单
位,再屏幕向里一单位。然后是靠近观察者的左下和右下顶点。就是屏幕往外一单位。
glColor3f( 1.0, 0.5, 0.0 );
glVertex3f( 1.0, -1.0, 1.0 );
glVertex3f( -1.0, -1.0, 1.0 );
glVertex3f( -1.0, -1.0, -1.0 );
glVertex3f( 1.0, -1.0, -1.0 );
底面的画法和顶面十分类似。只是 Y 坐标变成了-1。如果我们从立方体的下面来看立
方体的话,您会注意到右上角离观察者最近,因此我们先画离观察者最近的顶点。然后是左
上顶点最后才是屏幕里面的左下和右下顶点。
如果您真的不在乎绘制多边形的次序(顺时针或者逆时针)的话,您可以直接拷贝顶面
的代码,将 Y 坐标从 1 改成-1,也能够工作。但一旦您进入象纹理映射这样的领域时,忽
略绘制次序会导致十分怪异的结果。
glColor3f( 1.0, 0.0, 0.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( -1.0, 1.0, 1.0 );
glVertex3f( -1.0, -1.0, 1.0 );
glVertex3f( 1.0, -1.0, 1.0 );
立方体的前面。保持 Z坐标为一单位,前面正对着我们。
glColor3f( 1.0, 1.0, 0.0 );
glVertex3f( 1.0, -1.0, -1.0 );
glVertex3f( -1.0, -1.0, -1.0 );
glVertex3f( -1.0, 1.0, -1.0 );
glVertex3f( 1.0, 1.0, -1.0 );
立方体后面的绘制方法与前面类似。只是位于屏幕的里面。注意 Z 坐标现在保持-1 不
变。
glColor3f( 0.0, 0.0, 1.0 );
glVertex3f( -1.0, 1.0, 1.0 );
glVertex3f( -1.0, 1.0, -1.0 );
glVertex3f( -1.0, -1.0, -1.0 );
glVertex3f( -1.0, -1.0, 1.0 );
还剩两个面就完成了。您会注意到总有一个坐标保持不变。这一次换成了 X 坐标。因
为我们在画左侧面。
glColor3f( 1.0, 0.0, 1.0 );
glVertex3f( 1.0, 1.0, -1.0 );
glVertex3f( 1.0, 1.0, 1.0 );
glVertex3f( 1.0, -1.0, 1.0 );
glVertex3f( 1.0, -1.0, -1.0 );
立方体的最后一个面了。X坐标保持为一单位。逆时针绘制。您愿意的话,留着这个面
不画也可以,这样就是一个盒子
glEnd();
或者您要是有兴趣可以改变立方体所有顶点的色彩值,象金字塔那样混合颜色。您会看
见一个非常漂亮的彩色立方体,各种颜色在它的各个表面流淌。
rTri += 0.2;
rQuad -= 0.15;
}
这一课又结束了。到这里您应该已经较好的掌握了在三维空间创建对象的方法。必须将
OpenGL屏幕想象成一张很大的画纸,后面还带着许多透明的层。差不多就是个由大量的点
组成的立方体。这些点从左至右、从上至下、从前到后的布满了这个立方体。如果您能想象
的出在屏幕的深度方向,应该在新三维对象时没有任何问题。
如果您对三维空间的理解很困难的话,千万不要灰心!刚开始的时候,领会这些内容会
很难。象立方体这样的对象是您练习的好例子。继续努力吧!
Lesson 6. 纹理映射
学习 texturemap纹理映射(贴图)有很多好处。比方说您想让一颗导弹飞过屏幕。根据
前几课的知识,我们最可行的办法可能是很多个多边形来构建导弹的轮廓并加上有趣的颜
色。使用纹理映射,您可以使用真实的导弹图像并让它飞过屏幕。您觉得哪个更好看?照片
还是一大堆三角形和四边形?使用纹理映射的好处还不止是更好看,而且您的程序运行会更
快。导弹贴图可能只是一个飞过窗口的四边形。一个由多边形构建而来的导弹却很可能包括
成百上千的多边形。很显然,贴图极大的节省了 CPU时间。
我们要在第一课的代码上增加几行就可以了。
我们将要增加一个 loadGLTextures()函数来处理有关纹理操作的。我们将在 NeHeWidget类中
增加三个变量 xRot、yRot、zRot来处理立方体的旋转。还有一个用来存储纹理的 texture[1]。
NeHeWidget类 (由 nehewidget.h展开。)
protected:
void loadGLTextures();
在这个函数中我们会载入指定的图片并生成相应当纹理。
protected:
bool fullscreen;
GLfloat xRot, yRot, zRot;
GLuint texture[1];
};
上面就是添加的三个变量 xRot、yRot、zRot 来处理立方体在三个方向上的旋转。texture[1]
用来存储一个纹理。
(由 nehewidget.cpp展开。)
NeHeWidget::NeHeWidget( QWidget* parent, const char* name, bool fs )
: QGLWidget( parent, name )
{
xRot = yRot = zRot = 0.0;
fullscreen = fs;
setGeometry( 0, 0, 640, 480 );
setCaption( "NeHe's Texture Mapping Tutorial" );
if ( fullscreen )
showFullScreen();
}
我们需要在构造函数中给 xRot、yRot、zRot赋初值,都是 0.0。
void NeHeWidget::loadGLTextures()
{
QImage tex, buf;
if ( !buf.load( "./data/NeHe.bmp" ) )
载入纹理图片。这里使用了 QImage类。
zhangshengsen
高亮
zhangshengsen
高亮
zhangshengsen
高亮
{
qWarning( "Could not read image file, using single-color instead." );
QImage dummy( 128, 128, 32 );
dummy.fill( Qt::green.rgb() );
buf = dummy;
如果载入不成功,自动生成一个 128*128的 32位色的绿色图片。
}
tex = QGLWidget::convertToGLFormat( buf );
这里使用了 QGLWidget 类中提供的一个静态函数 converToGLFormat(),专门用来转换图片
的,具体情况请参见相应文档。
glGenTextures( 1, &texture[0] );
创建一个纹理。告诉 OpenGL我们想生成一个纹理名字(如果您想载入多个纹理,加大数字)。
值得注意的是,开始我们使用 GLuinttexture[1] 来创建一个纹理的存储空间,您也许会认为
第一个纹理就是存放在 &texture[1]中的,但这是错的。正确的地址应该是 &texture[0] 。同
样如果使用 GLuint texture[2] 的话,第二个纹理存放在 texture[1] 中。
glBindTexture( GL_TEXTURE_2D, texture[0] );
使用来自位图数据生成的典型纹理。告诉 OpenGL将纹理名字 texture[0]绑定到纹理目标上。
2D纹理只有高度(在 Y轴上)和宽度(在 X轴上)。 主函数将纹理名字指派给纹理数据。
本例中我们告知 OpenGL,&texture[0]处的内存已经可用。我们创建的纹理将存储 在
&texture[0]的指向的内存区域。
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
这里真正的创建纹理。GL_TEXTURE_2D 告诉 OpenGL 此纹理是一个 2D 纹理。数字零代
表图像的详细程度,通常就由它为零去了。数字三是数据的 成分数。因为图像是由红色数
据,绿色数据,蓝色数据三种组分组成。tex.width()是纹理的宽度。tex.height()是纹理的高
度。数字零是 边框的值,一般就是零。GL_RGBA 告诉 OpenGL 图像数据由红、绿、蓝三
色数据以及 alpha 通道数据组成,这个是由于 QGLWidget 类的 converToGLFormat()函数的
原因。 GL_UNSIGNED_BYTE 意味着组成图像的数据是无符号字节类型的。最后 tex.bits()
告诉 OpenGL纹理数据的来源。
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
上 面 的 两 行 告 诉 OpenGL 在 显 示 图 像 时 , 当 它 比 放 大 得 原 始 的 纹 理 大
( GL_TEXTURE_MAG_FILTER ) 或 缩 小 得 比 原 始 得 纹 理 小
(GL_TEXTURE_MIN_FILTER)时 OpenGL采用的滤波方式。通常这两种情况下我都采用
GL_LINEAR。这使得纹理从很远处到离屏 幕很近时都平滑显示。使用 GL_LINEAR 需要
CPU和显卡做更多的运算。如果您的机器很慢,您也许应该采用 GL_NEAREST。过滤的纹
理在放大的 时候,看起来斑驳的很。您也可以结合这两种滤波方式。在近处时使用
GL_LINEAR,远处时 GL_NEAREST。
}
loadGLTextures()函数就是用来载入纹理的。
void NeHeWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
本文档为【Qt_OpenGL_教程】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑,
图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。