Qt5版NeHe OpenGL教程之八:3D空间中移动图像
扫描二维码
随时随地手机看文章
欢迎进入第八课。到现在为止,您应该很好的理解OpenGL了。您已经学会了设置一个OpenGL窗口的每个细节。学会在旋转的物体上贴图并打上光线以及混色(透明)处理。
这一课应该算是第一课中级教程。您将学到如下的知识:在3D场景中移动位图,并去除位图上的黑色象素(使用混色)。接着为黑白纹理上色,最后您将学会创建丰富的色彩,并把上过不同色彩的纹理相互混合,得到简单的动画效果。
lesson8.h
#ifndef LESSON8_H #define LESSON8_H #include#include#includetypedef struct // 为星星创建一个结构 { int r, g, b; // 星星的颜色 GLfloat dist; // 星星距离中心的距离 GLfloat angle; // 当前星星所处的角度,自转角度 }stars; // 结构命名为stars const int starCount = 50; // 星星的个数 class QPainter; class QOpenGLContext; class QOpenGLPaintDevice; class Lesson8 : public QWindow, QOpenGLFunctions_1_1 { Q_OBJECT public: explicit Lesson8(QWindow *parent = 0); ~Lesson8(); virtual void render(QPainter *); virtual void render(); virtual void initialize(); public slots: void renderNow(); protected: void exposeEvent(QExposeEvent *); void resizeEvent(QResizeEvent *); void keyPressEvent(QKeyEvent *); // 键盘事件 private: void loadGLTexture(); private: QOpenGLContext *m_context; bool m_twinkle; // twinkle用来跟踪闪烁效果是否启用 QVectorm_stars; GLfloat m_zoom; // 星星离观察者的距离 GLfloat m_tilt; // 星星的倾角 GLfloat m_spin; // 闪烁星星的公转角度 GLuint m_texture[1]; // 存放一个纹理 }; #endif // LESSON8_H
lesson8.cpp
#include "lesson8.h" #include#include#include#include#include#includeLesson8::Lesson8(QWindow *parent) : QWindow(parent) , m_context(0) , m_twinkle(false) , m_zoom(-15.0f) , m_tilt(90.0f) , m_spin(0.0f) { setSurfaceType(QWindow::OpenGLSurface); } Lesson8::~Lesson8() { glDeleteTextures(1, &m_texture[0]); } void Lesson8::render(QPainter *painter) { Q_UNUSED(painter); } void Lesson8::render() { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glViewport(0,0,(GLint)width(),(GLint)height()); // 重置当前视口 glMatrixMode(GL_PROJECTION); // 选择投影矩阵 glLoadIdentity(); // 重置投影矩阵为单位矩阵 gluPerspective(45.0f,(GLdouble)width()/(GLdouble)height(),0.1f,100.0f); glMatrixMode(GL_MODELVIEW);// 选择模型视图矩阵 glLoadIdentity(); // 重置模型视图矩阵为单位矩阵 glBindTexture(GL_TEXTURE_2D, m_texture[0]); // 选择纹理 for (int i = 0, iend = starCount;i<iend; i++) // 循环设置所有的星星 { glLoadIdentity(); // 绘制每颗星星之前,重置模型观察矩阵 glTranslatef(0.0f,0.0f,m_zoom); // 深入屏幕里面 glRotatef(m_tilt,1.0f,0.0f,0.0f); // 倾斜视角,初始为90°,此时Y轴正向是指向观察者的。 //现在我们来移动星星。星星开始时位于屏幕的中心。我们要做的第一件事是把场景沿Y轴旋转。 //第二行代码沿x轴移动一个正值。通常x轴上的正值代表移向了屏幕的右侧(也就是通常的x轴的正向),但这里由于我们绕y轴旋转了坐标系, //x轴的正向可以是任意方向。 glRotatef(m_stars[i].angle,0.0f,1.0f,0.0f); // 旋转至当前所画星星的角度 glTranslatef(m_stars[i].dist,0.0f,0.0f); // X轴正向移动 //接着的代码带点小技巧。星星实际上是一个平面的纹理。现在您在屏幕中心画了个平面的四边形然后贴上纹理,这看起来很不错。 //但是当经过X轴和Y轴的旋转以后,星星的正面并没对着我们,如果倾斜角我饿90°,那么所有的星星 //看起来就是一条细线。这不是我们所想要的。我们希望星星永远正面朝着我们,而不管屏幕如何旋转或倾斜。 //我们通过在绘制星星之前,抵消对星星所作的任何旋转来实现这个愿望。您可以采用逆序来抵消旋转。 //当我们倾斜屏幕时,我们实际上以当前角度旋转了星星。通过逆序,我们又以当前角度"反旋转"星星。 //注意,旋转只能改变星星的角度,不能改变星星的位置。 glRotatef(-m_stars[i].angle,0.0f,1.0f,0.0f);// 取消当前星星的角度 glRotatef(-m_tilt,1.0f,0.0f,0.0f); // 取消屏幕倾斜 //如果 twinkle 为 TRUE,我们在屏幕上先画一次不旋转的星星:将星星总数(num) 减去当前的星星数(loop)再减去1, //来提取每颗星星的不同颜色(这么做是因为循环范围从0到num-1)。举例来说,结果为10的时候,我们就使用10号星星的颜色。 //这样相邻星星的颜色总是不同的。这不是个好法子,但很有效。最后一个值是alpha通道分量。这个值越小,这颗星星就越暗。 //由于启用了twinkle,每颗星星最后会被绘制两遍。程序运行起来会慢一些,这要看您的机器性能如何了。 //但两遍绘制的星星颜色相互融合,会产生很棒的效果。同时由于第一遍的星星没有旋转,启用twinkle后的星星看起来有一种动画效果。 //(如果您这里看不懂得话,就自己去看程序的运行效果吧。) //值得注意的是给纹理上色是件很容易的事。尽管纹理本身是黑白的,纹理将变成我们在绘制它之前选定的任意颜色。 //此外,同样值得注意的是我们在这里使用的颜色值是byte型的,而不是通常的浮点数。甚至alpha通道分量也是如此。 if (m_twinkle) // 启用闪烁效果 { // 使用byte型数值指定一个颜色 glColor4ub(m_stars[(iend-i)-1].r,m_stars[(iend-i)-1].g,m_stars[(iend-i)-1].b,255); glBegin(GL_QUADS); // 开始绘制纹理映射过的四边形 glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f); glEnd(); // 四边形绘制结束 } //现在绘制第二遍的星星。唯一和前面的代码不同的是这一遍的星星肯定会被绘制,并且这次的星星绕着z轴旋转。 glRotatef(m_spin,0.0f,0.0f,1.0f); // Z轴旋转星星 // 使用byte型数值指定一个颜色 glColor4ub(m_stars[i].r,m_stars[i].g,m_stars[i].b,255); glBegin(GL_QUADS); // 开始绘制纹理映射过的四边形 glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f); glEnd(); // 四边形绘制结束 //以下的代码代表星星的运动。我们增加spin的值来旋转所有的星星(公转)。 //然后,将每颗星星的自转角度增加loop/num。这使离中心更远的星星转的更快。 //最后减少每颗星星离屏幕中心的距离。这样看起来,星星们好像被不断地吸入屏幕的中心。 m_spin+=0.01f; // 星星的公转 m_stars[i].angle+=float(i)/iend; // 改变星星的自转角度 m_stars[i].dist-=0.01f; // 改变星星离中心的距离 //接着几行检查星星是否已经碰到了屏幕中心。当星星碰到屏幕中心时, //我们为它赋一个新颜色,然后往外移5个单位,这颗星星将踏上它回归屏幕中心的旅程。 if (m_stars[i].dist<0.0f) // 星星到达中心了么 { m_stars[i].dist+=5.0f; // 往外移5个单位 m_stars[i].r=qrand()%256; // 赋一个新红色分量 m_stars[i].g=qrand()%256; // 赋一个新绿色分量 m_stars[i].b=qrand()%256; // 赋一个新蓝色分量 } } } void Lesson8::initialize() { loadGLTexture(); // 加载纹理 glEnable(GL_TEXTURE_2D); // 启用纹理映射 glShadeModel(GL_SMOOTH); // 启用平滑着色 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 黑色背景 glClearDepth(1.0f); // 设置深度缓存 // glEnable(GL_DEPTH_TEST); // 启用深度测试 // glDepthFunc(GL_LEQUAL); // 深度测试类型 // 接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glBlendFunc(GL_SRC_ALPHA,GL_ONE); // 基于源象素alpha通道值的半透明混合函数 glEnable(GL_BLEND); // 打开混合 //以下是新增的代码。设置了每颗星星的起始角度、距离、和颜色。您会注意到修改结构的属性有多容易。 //全部50颗星星都会被循环设置。 for (int i = 0, iend = starCount; isetFormat(requestedFormat()); m_context->create(); needsInitialize = true; } m_context->makeCurrent(this); if (needsInitialize) { initializeOpenGLFunctions(); initialize(); } render(); m_context->swapBuffers(this); } void Lesson8::loadGLTexture() { QImage image(":/image/Star.bmp"); image = image.convertToFormat(QImage::Format_RGB888); image = image.mirrored(); glGenTextures(1, &m_texture[0]);// 创建一个纹理 // 创建一个线性滤波纹理 glBindTexture(GL_TEXTURE_2D, m_texture[0]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, image.bits()); } void Lesson8::exposeEvent(QExposeEvent *event) { Q_UNUSED(event); if (isExposed()) { renderNow(); } } void Lesson8::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); if (isExposed()) { renderNow(); } } void Lesson8::keyPressEvent(QKeyEvent *event) { int key=event->key(); switch(key) { case Qt::Key_T: { m_twinkle=!m_twinkle; // 控制闪烁 break; } case Qt::Key_Up: { m_tilt-=0.5f; break; } case Qt::Key_Down: { m_tilt+=0.5f; break; } case Qt::Key_Right: { m_zoom-=0.2f; break; } case Qt::Key_Left: { m_zoom+=0.2f; break; } } if(key==Qt::Key_T||key==Qt::Key_Up||key==Qt::Key_Down||key==Qt::Key_Right||key==Qt::Key_Left) { renderNow(); } }
main.cpp
#include#includeint main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QSurfaceFormat format; format.setSamples(16); Lesson8 window; window.setFormat(format); window.resize(640, 480); window.show(); return app.exec(); }
运行效果(通过方向键可得到下图)
按键控制
T键:打开和关闭星星闪烁效果
Left和Right:控制星星的远近
Up和Down:控制星星的倾角