当前位置: 代码迷 >> 综合 >> Nehe opengl编程一(Qt4修改版)
  详细解决方案

Nehe opengl编程一(Qt4修改版)

热度:57   发布时间:2023-12-09 03:23:15.0
原文:http://bbish.net/01toturial/12/nehe-opengl-qt4- Nehe opengl编程一(Qt4修改版) 星期二 8/11/2009 发布在 Toturial | 评论 参考文章:http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=01 参考文章:http://www.qiliang.net/old/nehe_qt/lesson01.html 帮助:http://doc.qtsoftware.com/4.5/qtopengl.html 创建一个OpenGL窗口 任务目标: 创建一个符合nehe tutorial 1里的opengl窗体 双击事件切换窗体的全屏和正常模式(代替原例中的键盘事件) 驱动部分 本小节处理驱动部分(就是一般性的main.cpp). 当程序运行的时候,弹出对话框让用户选择是否最大化. 然后按不同情况运行. Qt里主要用到的是QApplication和QMessageBox两个类. 以下是代码: #include #include #include "MyWidget.h" bool mode = false; int main(int argc, char* argv[]) { QApplication app(argc, argv); mode = QMessageBox::information(0, "Start Fullscreen?", "Would you like to run it in fullscreen mode?", QMessageBox::Yes, QMessageBox::No | QMessageBox::Default) == QMessageBox::Yes ? true : false; MyWidget w; w.show(); return app.exec(); } 1-2行是正常的qt4头文件包含. 6行我们用全局变量(尽管全局变量总是被BS)来表现窗口状态. 12行自然就是在问你是否进入全屏模式,然后对变量进行设置(我们不用switch/if/else判断完全是为了缩减行数) 驱动程序的其它部分和任何其它qt4程序一样. 不多解释. 主窗体 主窗体部分,我们主要要写5个函数. 1个是构造函数,用来初始化一些东西,比如窗口的大小和位置,以及标题(在qt4里,我们会用setWindowTitle), 还有处理那个运行程序时的对话框,假如确定最大化,那么就要用showFullScreen. 该方法(是QWidget的一个slot)将会将窗口全屏. 相应的还有showNormal. 代码的5-14行包含了该部分的实现. 然后是三个继承自QGLWidget的函数: 初始化,绘图,和变更大小. 最后一个函数是处理鼠标双击的. 该函数继承自QWidget. 代码如下: #include "MyWidget.h" extern bool mode; MyWidget::MyWidget(QWidget* parent) : QGLWidget(parent) { setGeometry(0, 20, 640, 480); setWindowTitle("Qt Opengl Nehe Tutorial 01"); if (mode) { showFullScreen(); } } void MyWidget::initializeGL() { glShadeModel(GL_SMOOTH); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClearDepth(1.0f); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); } void MyWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); renderText(10, 20, "This is my first opengl application."); } void MyWidget::resizeGL(int w, int h) { if (h == 0) { h = 1; } glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0f, (GLfloat)w/(GLfloat)h, 0.1f, 100.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void MyWidget::mouseDoubleClickEvent(QMouseEvent* event) { mode = !mode; if (mode) { showFullScreen(); } else { showNormal(); } } paintGL方法里,glClear是清屏指令. 但是输入的参数不是具体的用什么颜色清屏,而是方式: GL_COLOR_BUFFER_BIT和GL_DEPTH_BUFFER_BIT指示了对这两部分清屏. (具体内含咋也不懂,反正是opengl用来缓存的). 然后下面纯粹输出文字(用Qt的方法). 既然 glClear的参数不是颜色,那么具体颜色的定义就是在initializeGL那里了, 就是行20部分. resizeGL方法, 除了glViewport比较容易理解之外,其余有点莫名其妙. 但是gluPerspective这个函数对学美术的人来说还是相对能够容易理解一些的. 我们都知道画画的时候会有透视原理. 而该函数应该是设置和这一相关的东东了… 至于其余的指令,我们只能依靠英文来理解. 不过暂时不能理解也没关系…反正才第一课. (注意: 代码里我没包含qt的project文件, 因为产生的.pro本身无法跨平台.你需要首先qmake -project一下. 然后手动增加 QT += opengl 该程序分别在windows vista/vc++ express/qt4.5, linux/gcc/qt4.5下编译通过) 代码下载: 1.tar.gz 创建多边形 任务目标: 绘制三角形和四边形 创建多边形的过程其实和画图有点类似. 你画画的时候,会首先把注意力集中到需要画的那部分(就是你脑海中出现的那个草稿的位置). 假如是三角形,而且你足够理性的话,接着就会首先尝试点出3个点,然后把他们连起来 — 我记得当时我没考虑那么多… 而我们在这一讲里将介绍画三角形和四边形(为啥不叫四角形?). 两者的绘图原理基本和我上面说的类似. vista下的效果图: 以下是代码, 只需要变更MyWidget.cpp的paintGL部分就好了. void MyWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); renderText(10, 20, "This is my first opengl application."); glTranslatef(-1.5f, 0.0f, -6.0f); // 三角形 glBegin(GL_TRIANGLES); glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glEnd(); glTranslatef(3.0f, 0.0f, 0.0f); // 四边形 glBegin(GL_QUADS); glVertex3f(-1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glEnd(); } 在这里,我们注意到: glTranslatef的作用有点象画画时把我们的注意里集中到某个部分那种感觉 三角形和四边形填充的颜色是白色的. 三角形的高度是2, 底边也是2. 而四边形各边都是2. 练习: 你可以把某个1.0f改成2.0f看会变成什么. 上色 任务目标: 给三角形增加颜色 上面我们说过,三角形和四边形填充的颜色是白色. 这里就要开始上色了. 假如我们接触过photoshop的话, 我们知道有一种线性的颜色填充模式. 就是选好2种颜色,然后拉一条线. 然后颜色就会自动从一个颜色过渡到另外一种颜色(以前没想过原因, 觉得似乎很复杂…现在觉得很简单,纯粹就是增加或减少某个百分点).这里类似. 同样,变动的部分还是paintGL. void MyWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); renderText(10, 20, "This is my first opengl application."); glTranslatef(-1.5f, 0.0f, -6.0f); // 三角形 glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(1.0f, -1.0f, 0.0f); glEnd(); glTranslatef(3.0f, 0.0f, 0.0f); // 四边形 glColor3f(0.5f, 0.5f, 1.0f); glBegin(GL_QUADS); glVertex3f(-1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glEnd(); } glColor3f就是纯粹设置前景色. 参数分别就是有名的RGB. (这是一个相对通用的函数,比如你可以放在输出文本前,从而变更文字的颜色). 这样我们会得到一个多彩的三角形和一个浅蓝色的四边形. 旋转 任务目标: 产生旋转的三角形 哈,这是很有意思的一节. 不过也稍微复杂那么点点…(我在学习的时候出现了一点意外, 和qiliang.net那位兄弟不一致的地方). 但我们可以一步步来. 先静态的旋转 静态的转换不需要增加变量, 我们只需要改动下paintGL的代码就可以了. 如下: void MyWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); renderText(10, 20, "This is my first opengl application."); glTranslatef(-1.5f, 0.0f, -6.0f); glRotatef(60, 0.0f, 1.0f, 0.0f); // 三角形 glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(1.0f, -1.0f, 0.0f); glEnd(); glLoadIdentity(); glTranslatef(1.5f, 0.0f, -6.0f); glRotatef(60, 1.0f, 0.0f, 0.0f); // 四边形 glColor3f(0.5f, 0.5f, 1.0f); glBegin(GL_QUADS); glVertex3f(-1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glEnd(); } 比较先前代码,只是增加了9,20, 22行, 变更了21行. glLoadIdentify相当于载入原始状态(在你进行了坐标变换之后), 这样就要修改21行的值. 因为3.0f是对原来移动过的位置计算的. 另外一个函数是glRotatef. 该函数用来做旋转. 第一个参数无疑就是角度. 后面几个分别是x/y/z. 就是按哪个方向旋转的意思. 比如glRotatef(60, 0.0f, 1.0f, 0.0f)这个, 意思就是按y轴正方向旋转60度. (你可以用双手大拇指和食指构造一个三角形,然后双手按照中间的那条竖线y, 以左手朝里,右手朝外的方向旋转) 结果就是, 从平面的角度,左边相对长一点, 左边的低角也会比右边的小. 注意: glRotatef不能放glTranslatef前面. 否则变化就大了… 半自动旋转(手动旋转) 有意思的是我们只需要在MyWidget里增加一个GLfloat类型的变量, 并在paintGL里增加自增的一行就可以达到旋转的目的. 我们把控制三角形的那个变量叫rTri(为避免复杂度,我们不动四边形), 并且初始化为0.0f. 那么, 修改上例的行9为: glRotatef(rTri, 0.0f, 1.0f, 0.0f); 并且, 在paintGL快要结束的时候,增加 rTri += 60; 60能让我们看得更清楚一点. 编译运行. 我们只需要不断手工点窗口图标就行了, 每次出来都会有不同的变化. 当初始值是0, 角度是60的时候,经过3次变换,底部的颜色会换个个…当然180度是显然的. 自动旋转 当然,更好的是能出现动画了…不过我们只有一步之遥. 自动旋转的原理几乎和手动一致: 我们需要增加一个QTimer的计时器用来不断刷新图形窗口. 在MyWidget.h的头部增加对QTimer的包含: #include 并在MyWidget内部增加一个QTimer类型的变量: QTimer* t; 然后在构造函数里,增加对t的赋值和操作: t = new QTimer(this); connect(t, SIGNAL(timeout()), this, SLOT(updateGL())); t->start(10); 当然为了不至于变化太快, 我们把上面那个自增的60改成相对数值较小的角度,比如0.3之类. 那么基本上我们就能看见那个三角形以它的高宣传的动画了. lesson04源代码下载: 4.tar.gz 真正的三维 任务目标: 实现三维效果 上面我们做出的三维仅仅是一个平面. 而在本讲中,我们将会把平面扩展为真正的三维图形. 这有点象我们初高时学习计算三角形到计算锥形体积类似. 这里的锥形底面是个平面,也就是说它有4个侧面. 我们在考虑绘图的时候的情况和我们在这里的稍微有点不同,假如是绘图,那么我们可以象刚才一样只考虑顶点就行了. 不过这里,我们需要画5个面(包含底面), 也就是说,我们要写12个点,并且还有颜色. 另外根据教程的提示,我们最好按照某种次序来画这些三角形,比如逆时针. 代码类似, 也仅仅是修改了paintGL, 如下: void MyWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); renderText(10, 20, "This is my first opengl application."); glTranslatef(-1.5f, 0.0f, -6.0f); glRotatef(rTri, 0.0f, 1.0f, 0.0f); // 锥形 glBegin(GL_TRIANGLES); // 前面的那个面 glColor3f(1.0f, 0.0f, 0.0f); // 红 glVertex3f(0.0f, 1.0f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); // 绿 glVertex3f(-1.0f, -1.0f, 1.0f); glColor3f(0.0f, 0.0f, 1.0f); // 蓝 glVertex3f(1.0f, -1.0f, 1.0f); // 右边的那个面 glColor3f(1.0f, 0.0f, 0.0f); // 红 glVertex3f(0.0f, 1.0f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); // 蓝 glVertex3f(1.0f, -1.0f, 1.0f); glColor3f(1.0f, 1.0f, 0.0f); // 黄 glVertex3f(1.0f, -1.0f, -1.0f); // 最里面的那个面 glColor3f(1.0f, 0.0f, 0.0f); // 红 glVertex3f(0.0f, 1.0f, 0.0f); glColor3f(1.0f, 1.0f, 0.0f); // 黄 glVertex3f(1.0f, -1.0f, -1.0f); glColor3f(0.0f, 1.0f, 1.0f); // 青 glVertex3f(-1.0f, -1.0f, -1.0f); // 左边的那个面 glColor3f(1.0f, 0.0f, 0.0f); // 红 glVertex3f(0.0f, 1.0f, 0.0f); glColor3f(0.0f, 1.0f, 1.0f); // 青 glVertex3f(-1.0f, -1.0f, -1.0f); glColor3f(0.0f, 1.0f, 0.0f); // 绿 glVertex3f(-1.0f, -1.0f, 1.0f); glEnd(); // 底面 glBegin(GL_QUADS); glColor3f(0.0f, 0.0f, 1.0f); // 蓝 glVertex3f(1.0f, -1.0f, 1.0f); glColor3f(0.0f, 1.0f, 0.0f); // 绿 glVertex3f(-1.0f, -1.0f, 1.0f); glColor3f(0.0f, 1.0f, 1.0f); // 青 glVertex3f(-1.0f, -1.0f, -1.0f); glColor3f(1.0f, 1.0f, 0.0f); // 黄 glVertex3f(1.0f, -1.0f, -1.0f); glEnd(); glLoadIdentity(); glTranslatef(1.5f, 0.0f, -6.0f); glRotatef(60, 1.0f, 0.0f, 0.0f); // glColor3f(0.5f, 0.5f, 1.0f); glBegin(GL_QUADS); glVertex3f(-1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glEnd(); rTri += 0.3f; } 你可以把glRotatef里的x也修改成1.0f, 从而观察看看你的底面画的对不对… 纹理映射 任务目标: 学会用qt载入图片提供给opengl使用 学会贴图 就象我们任务里标明的一样,要进行纹理映射,可以分成2个步骤. 载入图片做纹理 在我们思考用qt写opengl程序的时候,我们需要有这样的立足点,那就是: qt和opengl是两套系统 qt是基于c++的窗口系统 opengl是基于c函数式的3D系统 当我们理解了这点,那么再思考载入过程,就容易的多了. 载入步骤可以分成: 利用qt类载入任意格式的图片(只要qt支持) 将该格式转化为符合opengl使用的图片格式 将该格式再转为纹理 在opengl里,储存纹理的变量实际上是个指针({_但为了跨平台性,opengl还是进行了封装_}). 所以,我们需要在MyWidget.h里增加一个成员变量. GLuint texs[1]; 注意: 因为我们只用一个纹理,所以这里的数组宽度是1. 也就是说第一元素在偏移量0处. 接着, 我们可以把载入过程也简单的写成一个方法, 都放在MyWidget.h里. void loadGLTextures(); 该函数的实现部分如下: void MyWidget::loadGLTextures() { QImage tex, buff; buff.load("./txp.png"); tex = QGLWidget::convertToGLFormat(buff); glGenTextures(1, texs); glBindTexture(GL_TEXTURE_2D, texs[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); } 在上面的代码里, 很清晰的说明QGLWidget提供了一个静态方法来处理从qt格式的图片到符合在opengl里使用的格式的转化. 7-9行在实现纹理部分的存储空间. 象我们前面所提到的那样, 既然opengl是以c的方式来写的. 那么有那么个glGenTextures来初始化纹理的内存空间就不奇怪了. 而且这里的初始化参数也很容易理解. 假如你来设计的话, 你也会构造一个指针数组,而每个指针就是对应的载入的不同的纹理. 你可以简单的拿那个指针数组的地方和个数来对所有的你希望的纹理效果做初始化. 所以我这里用的就是texs,数组名,我们在学c的时候清晰的知道数组名就是首地址. 1当然是个数,我们只用了一个纹理. 第八行有点类似把当前操作对象设为偏移量是0的指针所对应的那个纹理的意思. 而第9行才是具体执行某种拷贝 — 于是,符合opengl的图形数据tex.bits()就转化为纹理数据了, 并放在了那个指针所对应的某个地方. 后面几行是额外的操作,因为我们还不是很了解opengl, 所以大可不必深入 — 这是高手做的事情. 在完成了该函数之后,我们需要在initializeGL方法里调用它. 并且需要启用2D纹理贴图. 否则就会是原来的图片. 我猜测可能这样调试方便点. 把下面的2行加到initializeGL方法里. loadGLTextures(); glEnable(GL_TEXTURE_2D); 贴图 贴图的过程同样基本上在paintGL里实现. 这里我们去掉前面的颜色效果…我们这里使用的还是那个锥形(靠,我已经忘记了这是否叫锥形?反正有4个侧面,底是一个方形). 请看代码: void MyWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); renderText(10, 20, "This is my first opengl application."); glTranslatef(-1.5f, 0.0f, -6.0f); glRotatef(rTri, 1.0f, 1.0f, 0.0f); glBindTexture(GL_TEXTURE_2D, texs[0]); // 锥形 glBegin(GL_TRIANGLES); // 前面的那个面 glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 1.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); // 右边的那个面 glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f); // 最里面的那个面 glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 左边的那个面 glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glEnd(); // 底面 glBegin(GL_QUADS); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(1.0f, -1.0f, -1.0f); glEnd(); glLoadIdentity(); glTranslatef(1.5f, 0.0f, -6.0f); glRotatef(60, 1.0f, 0.0f, 0.0f); // glBegin(GL_QUADS); glVertex3f(-1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glEnd(); rTri += 0.3f; } 变动的部分其实是11-20行。而其余部分只是去掉颜色为了让效果更清楚而已. 第11行还是进行对纹理的选择…我们用哪个来做贴图呢? (根据作者提示, 该指令不能放在glBegin和glEnd内部) 而glTexCoord2f的作用有点象你在你墙壁上贴壁纸类似, 只不过它的参数代表了x/y来表现纹理的2D位置…然后点对点地粘在三角形顶点上. 我们这里用的是一个方形的图片,你会发现它会切割了一个三角形然后贴在锥形的正面上. 看下图效果: 光影效果 任务目标: 实现光源照射效果 在前面的图片里,我们看到左侧那个三角锥形除了正面贴了图之外,其余几乎就是白茫茫一片. 这很难让我们有立体感. 所以本节主要就是要处理光影效果. 光影效果也可以分成2步骤: 产生光源 opengl里的光源除了象手电筒的强力光源之外,还有环境光. (否则要是照的地方非常亮,不照的地方一片黑暗就很不自然了). 当然,光源还有光源的位置. 这些可以放在MyWidget.cpp的前头进行定义: GLfloat lightAmbient[4] = {0.5f, 0.5f, 0.5f, 1.0f}; GLfloat lightDiffuse[4] = {1.0f, 1.0f, 1.0f, 1.0f}; GLfloat lightPosition[4] = {0.0f, 0.0f, 2.0f, 1.0f}; 第一个就是环境光. 而数值也分别代表的是RGBA. 也就是说,这是一个半暗的白光. 第二个就是“手电筒“了…数值的意思和上面一致. 第三个是位置, 分别是x/y/z, 最后一个值1.0有特殊的含义, 我们这里不更多描述. 然后呢,需要启用他们. void MyWidget::initializeGL() { loadGLTextures(); glEnable(GL_TEXTURE_2D); glShadeModel(GL_SMOOTH); glColor3f(1.0f, 1.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClearDepth(1.0f); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient); glLightfv(GL_LIGHT1, GL_DIFFUSE, lightDiffuse); glLightfv(GL_LIGHT1, GL_POSITION, lightPosition); glEnable(GL_LIGHT1); glEnable(GL_LIGHTING); } 增加的是15-20行. 前3行都是使用上面的数值的情况来设定光源参数. 后面一行代表了启用这个光源. 而最后一行同样是启用光源的意思. 不过这个就有点象单独管某个灯的开关,管理所有灯的开关类似. 法线 光源已经产生了,按道理来说,我们不需要做什么了. 让它那么照着就可以了. 不过我们在先前的编程中我们知道,实际上上面的锥是由四个三角形和一个底面组成. 既然是面,那么就有相反两面. 我们需要明确哪边是接受照射的. 这里就是法线的概念. (虽说是线,不过编程里是个点…而该法线的说法,可能就是该点到原点的那条线了). 我们需要在每个面上做一个…假如没有的话,那么就只有环境光的效果. 这里可能就变得有点黑忽忽的了(半暗). 程序如下: void MyWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); renderText(10, 20, "This is my first opengl application."); glTranslatef(-1.5f, 0.0f, -6.0f); glRotatef(rTri, 1.0f, 1.0f, 0.0f); glBindTexture(GL_TEXTURE_2D, texs[0]); // 锥形 glBegin(GL_TRIANGLES); // 前面的那个面 glNormal3f(0.0f, 0.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(0.0f, 1.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); // 右边的那个面 glNormal3f(1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f); // 最里面的那个面 glNormal3f(0.0f, 0.0f, -1.0f); glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 左边的那个面 glNormal3f(-1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glEnd(); // 底面 glBegin(GL_QUADS); glNormal3f(0.0f, -1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(1.0f, -1.0f, -1.0f); glEnd(); glLoadIdentity(); glTranslatef(1.5f, 0.0f, -6.0f); glRotatef(60, 1.0f, 0.0f, 0.0f); // glBegin(GL_QUADS); glVertex3f(-1.0f, 1.0f, 0.0f); glVertex3f(1.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glEnd(); rTri += 0.3f; } 基本上,只是增加了glNormal3f到各个面而已. 注意: 原始的Nehe教程还包含了一些其他东西,但我感觉对初学者来说并不需要, 所以去掉了. 如果你想看具体细节, 请访问第7课:http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=07