首页 OpenGL_Qt中文教程

OpenGL_Qt中文教程

举报
开通vip

OpenGL_Qt中文教程 Qt OpenGL 教程 最近一段时间除了学习 Qt,翻译 Qt 文档之外,由于工作和兴趣的原因,开始着 手看Qt OpenGL编程。在网上搜索了有关OpenGL的教程,发现NeHe 的OpenGL 教程的还很不错,作者是 NeHe。上面有很多种语言的实现,但是没有 Qt 和 Gtk 的,所以我就想着手写这个 Qt OpenGL 教程,每课的内容和 NeHe 是一样的。另 外,介绍 NeHe 的一个中文翻译站点 CSDN-CKer 翻译的 NeHe 的 OpenGL 教程, 翻译人是 CKer,在我学习这个教程...

OpenGL_Qt中文教程
Qt OpenGL 教程 最近一段时间除了学习 Qt,翻译 Qt 文档之外,由于工作和兴趣的原因,开始着 手看Qt OpenGL编程。在网上搜索了有关OpenGL的教程,发现NeHe 的OpenGL 教程的还很不错,作者是 NeHe。上面有很多种语言的实现,但是没有 Qt 和 Gtk 的,所以我就想着手写这个 Qt OpenGL 教程,每课的内容和 NeHe 是一样的。另 外,介绍 NeHe 的一个中文翻译站点 CSDN-CKer 翻译的 NeHe 的 OpenGL 教程, 翻译人是 CKer,在我学习这个教程的过程中,给了我很大的帮助。 下面就是 Qt OpenGL 教程的内容: Qt OpenGL 的准备工作 第一课:创建一个 OpenGL 窗口 第二课:你的第一个多边形 第三课:上色 第四课:旋转 第五课:向三维进军 第六课:纹理映射 第七课:纹理滤波、光源和键盘控制 第八课:融合 第九课:在三维空间中移动位图 第十课:载入一个三维世界并在其中移动 第十一课:旗的效果(波动纹理) 第十二课:显示列 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf 第十三课:位图字体 第十四课:轮廓字体 第十五课:使用纹理映射的轮廓字体 第十六课:看起来很棒的雾 因为本教程是从 NeHe 的 OpenGL 教程迁移过来的,代码变为 Qt 实现的。所以 有的课程一时还没有实现成功,所以可能有些教程是跳跃的。 因本人时间有限,所以难免有错误出现,如果您发现了这些错误,或者有什么建 议,请来信指教,谢谢。 Qt OpenGL 的准备工作 因为 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 企业版。 Microsoft Visual Studio:编译器。我现在使用的是 6.0。 创建一个 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; 用来保存窗口是否处于全屏状态的变量。 }; (由 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 ); 所作深度测试的类型。 上面这三行必须做的是关于 depth buffer(深度缓存)的。将深度缓存设想为屏 幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。我们本节的 程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示 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(); 重置模型观察矩阵。 } 上面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个 现实外观的场景。此处透视按照基于窗口宽度和高度的 45 度视角来计算。0.1, 100.0 是我们在场景中所能绘制深度的起点和终点。 glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响 projection matrix (投影矩阵)。投影矩阵负责为我们的场景增加透视。 glLoadIdentity()近似于 重置。它将所选的矩阵状态恢复成其原始状态。调用 glLoadIdentity()之后我们为 场景设置透视图。 glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix (模型观察矩阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型 观察矩阵。如果您还不能理解这些术语的含义,请别着急。在以后的教程里,我 会向大家解释。只要知道如果您想获得一个精彩的透视场景的话,必须这么做。 这个函数的作用是重新设置 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::Default ) ) { 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(); 程序返回。 } 本课程的源代码。 你的第一个多边形 上一课中,我教您如何创建一个 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 ); 开始绘制四边形。 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(); 四边形绘制结束。 } 本课程的源代码。 上色 上一课中我教给您三角形和四边形的绘制方法。这一课我将教您给三角形和四边 形添加两种不同类型的着色方法。使用单调着色(Flat coloring)给四边形涂上 固定的一种颜色。使用平滑着色(Smooth coloring)将三角形的三个顶点的不同 颜色混合在一起,创建漂亮的色彩混合。 我们只要修改第二课中的 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 多边形添加单调和平滑 的着色效果的步骤。改改代码中的红绿蓝分量值,看看最后有什么样的结果。 本课程的源代码。 旋转 上一课中我教给您三角形和四边形的着色。这一课我将教您如何将这些彩色对象 绕着坐标轴旋转。 其实只需在上节课的代码上增加几行就可以了。 我们将在 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 轴或者所有三个轴来转动:)。 本课程的源代码。 向三维进军 在上一课的内容上作些扩展,我们现在开始生成真正的三维对象,而不是象前两 节课中那样在三维世界中的二维对象。我们给三角形增加一个左侧面,一个右侧 面,一个后侧面来生成一个金字塔(四棱锥)。给正方形增加左、右、上、下及 背面生成一个立方体。 我们混合金字塔上的颜色,创建一个平滑着色的对象。给立方体的每一面则来个 不同的颜色。 其实只需在上节课的代码上增加几行就可以了。 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 坐标中心。 右侧面从上顶点开始向外侧倾斜至底边上。 这次的左下顶点用蓝色绘制,以保持与前侧面的右下顶点的一致。蓝色将从这个 角向金字塔的前侧面和右侧面扩展并与其他颜色混合。 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 屏幕想象成一张很大的画纸,后面还带着许多透明的层。差不多 就是个由大量的点组成的立方体。这些点从左至右、从上至下、从前到后的布满 了这个立方体。如果您能想象的出在屏幕的深度方向,应该在 设计 领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计 新三维对象时 没有任何问题。 如果您对三维空间的理解很困难的话,千万不要灰心!刚开始的时候,领会这些 内容会很难。象立方体这样的对象是您练习的好例子。继续努力吧! 本课程的源代码。 纹理映射 学习 texture map 纹理映射(贴图)有很多好处。比方说您想让一颗导弹飞过屏 幕。根据前几课的知识,我们最可行的办法可能是很多个多边形来构建导弹的轮 廓并加上有趣的颜色。使用纹理映射,您可以使用真实的导弹图像并让它飞过屏 幕。您觉得哪个更好看?照片还是一大堆三角形和四边形?使用纹理映射的好处 还不止是更好看,而且您的程序运行会更快。导弹贴图可能只是一个飞过窗口的 四边形。一个由多边形构建而来的导弹却很可能包括成百上千的多边形。很显然, 贴图极大的节省了 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 类。 { 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_Qt中文教程】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_764571
暂无简介~
格式:pdf
大小:903KB
软件:PDF阅读器
页数:50
分类:互联网
上传时间:2011-11-14
浏览量:66