Qt5版NeHe OpenGL教程之五:纹理映射
扫描二维码
随时随地手机看文章
在这一课里,将学会如何将纹理映射到立方体的六个面。
学习texture map纹理映射(贴图)有很多好处。比方说您想让一颗导弹飞过屏幕。根据前几课的知识,我们最可行的办法可能是很多个多边形来构建导弹的轮廓并加上有趣的颜色。使用纹理映射,您可以使用真实的导弹图像并让它飞过屏幕。您觉得哪个更好看?照片还是一大堆三角形和四边形?使用纹理映射的好处还不止是更好看,而且您的程序运行会更快。导弹贴图可能只是一个飞过窗口的四边形。一个由多边形构建而来的导弹却很可能包括成百上千的多边形。很显然,贴图极大的节省了CPU时间。
需要注意的是,用作纹理的图像的宽和高必须是2的n次方;宽度和高度最小必须是64象素;并且出于兼容性的原因,图像的宽度和高度不应超过256象素。
如果您的原始素材的宽度和高度不是64,128,256象素的话,使用图像处理软件重新改变图像的大小。
可以肯定有办法能绕过这些限制,但现在我们只需要用标准的纹理尺寸。
关于纹理映射,更详细的讲解可参考:https://blog.csdn.net/caoshangpa/article/details/80318959
这一课的代码较之前有了较大变化,全部贴出来。
lesson5.h
#ifndef LESSON5_H #define LESSON5_H #include#includeclass QPainter; class QOpenGLContext; class QOpenGLPaintDevice; class Lesson5 : public QWindow, QOpenGLFunctions_1_1 { Q_OBJECT public: explicit Lesson5(QWindow *parent = 0); ~Lesson5(); virtual void render(QPainter *); virtual void render(); virtual void initialize(); public slots: void renderNow(); protected: void exposeEvent(QExposeEvent *); void resizeEvent(QResizeEvent *); private: void myPerspective( GLdouble fov, GLdouble aspectRatio, GLdouble zNear, GLdouble zFar ); void loadGLTexture(); private: QOpenGLContext *m_context; GLfloat xrot; // X 旋转量 GLfloat yrot; // Y 旋转量 GLfloat zrot; // Z 旋转量 GLuint texture[1]; // 存储一个纹理 }; #endif // LESSON5_H
lesson5.cpp
#include "lesson5.h" #include#include#include#includeLesson5::Lesson5(QWindow *parent) : QWindow(parent) , m_context(0) { setSurfaceType(QWindow::OpenGLSurface); xrot=45.0f; yrot=45.0f; zrot=45.0f; } Lesson5::~Lesson5() { glDeleteTextures(1, &texture[0]); } void Lesson5::render(QPainter *painter) { Q_UNUSED(painter); } void Lesson5::myPerspective( GLdouble fov, GLdouble aspectRatio, GLdouble zNear, GLdouble zFar ) { // 使用glu库函数,需要添加glu.h头文件 // gluPerspective(fov, aspectRatio, zNear, zFar); GLdouble rFov = fov * 3.14159265 / 180.0; glFrustum( -zNear * tan( rFov / 2.0 ) * aspectRatio, zNear * tan( rFov / 2.0 ) * aspectRatio, -zNear * tan( rFov / 2.0 ), zNear * tan( rFov / 2.0 ), zNear, zFar ); } void Lesson5::render() { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glViewport(0,0,(GLint)width(),(GLint)height()); // 重置当前视口 glMatrixMode(GL_PROJECTION); // 选择投影矩阵 glLoadIdentity(); // 重置投影矩阵为单位矩阵 // glu库函数Qt不支持,但是glu库函数是对gl库函数的封装,方便使用。因此我们可以自己 // 写一个类似gluPerspective的函数myPerspective,用于设置透视。 //gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); myPerspective(45.0,(GLfloat)width()/(GLfloat)height(),0.1,100.0); glMatrixMode(GL_MODELVIEW);// 选择模型视图矩阵 glLoadIdentity(); // 重置模型视图矩阵为单位矩阵 glTranslatef(0.0f,0.0f,-6.0f); // 移入屏幕6.0 //下面三行使立方体绕X、Y、Z轴旋转。旋转多少依赖于变量 xrot , yrot 和 zrot 的值。 glRotatef(xrot,1.0f,0.0f,0.0f); // X轴旋转 glRotatef(yrot,0.0f,1.0f,0.0f); // Y轴旋转 glRotatef(zrot,0.0f,0.0f,1.0f); // Z轴旋转 //下一行代码选择我们使用的纹理。 //如果您在您的场景中使用多个纹理,您应该使用来 glBindTexture(GL_TEXTURE_2D, texture[ 所使用纹理对应的数字 ]) 选择要绑定的 //纹理。当您想改变纹理时,应该绑定新的纹理。有一点值得指出的是,您不能在 glBegin() 和 glEnd() 之间绑定纹理,必须在 glBegin() //之前或 glEnd() 之后绑定。注意我们在后面是如何使用 glBindTexture 来指定和绑定纹理的。 glBindTexture(GL_TEXTURE_2D, texture[0]); // 选择纹理 //为了将纹理正确的映射到四边形上,您必须将纹理的右上角映射到四边形的右上角,纹理的左上角映射到四边形的左上角, //纹理的右下角映射到四边形的右下角,纹理的左下角映射到四边形的左下角。 //如果映射错误的话,图像显示时可能上下颠倒,侧向一边或者什么都不是。 //glTexCoord2f 的第一个参数是X坐标。 0.0f 是纹理的左侧。 0.5f 是纹理的中点, 1.0f 是纹理的右侧。 //glTexCoord2f 的第二个参数是Y坐标。 0.0f 是纹理的底部。 0.5f 是纹理的中点, 1.0f 是纹理的顶部。 //所以纹理的左上坐标是 X:0.0f,Y:1.0f ,四边形的左上顶点是 X: -1.0f,Y:1.0f 。其余三点依此类推。 //试着玩玩 glTexCoord2f X, Y坐标参数。把 1.0f 改为 0.5f 将只显示纹理的左半部分,把 0.0f 改为 0.5f 将只显示纹理的右半部分。 glBegin(GL_QUADS); // 前面 glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); // 纹理和四边形的左下 glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的左上 // 后面 glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 纹理和四边形的左下 // 顶面 glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.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); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 // 底面 glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.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); // 纹理和四边形的右下 // 右面 glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); // 纹理和四边形的左上 glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.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); // 纹理和四边形的右下 glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); // 纹理和四边形的右上 glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); // 纹理和四边形的左上 glEnd(); } void Lesson5::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); } void Lesson5::renderNow() { if (!isExposed()) return; bool needsInitialize = false; if (!m_context) { m_context = new QOpenGLContext(this); m_context->setFormat(requestedFormat()); m_context->create(); needsInitialize = true; } m_context->makeCurrent(this); if (needsInitialize) { initializeOpenGLFunctions(); initialize(); } render(); m_context->swapBuffers(this); } void Lesson5::loadGLTexture() { // 现在载入图像,并将其转换为纹理。 QImage image(":/image/NeHe.bmp"); image = image.convertToFormat(QImage::Format_RGB888); image = image.mirrored(); // 创建纹理 glGenTextures(1, &texture[0]); // 使用来自位图数据生成的典型纹理 glBindTexture(GL_TEXTURE_2D, texture[0]); // 下来我们创建真正的纹理。下面一行告诉OpenGL此纹理是一个2D纹理 ( GL_TEXTURE_2D )。 // 参数“0”代表图像的详细程度,通常设置为0。参数三是数据的成分数。 // 因为图像是由红色数据,绿色数据,蓝色数据三种组分组成。 TextureImage[0]->sizeX 是纹理的宽度。 // 如果您知道宽度,您可以在这里填入,但计算机可以很容易的为您指出此值。 // TextureImage[0]->sizey 是纹理的高度。参数零是边框的值,一般就是“0”。 // GL_RGB 告诉OpenGL图像数据由红、绿、蓝三色数据组成。 // GL_UNSIGNED_BYTE 意味着组成图像的数据是无符号字节类型的。 // 最后TextureImage[0]->data 告诉OpenGL纹理数据的来源。此例中指向存放在 TextureImage[0] 记录中的数据。 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, image.bits()); // 下面的两行告诉OpenGL在显示图像时, // 当它比放大得原始的纹理大 ( GL_TEXTURE_MAG_FILTER )或缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )时 // OpenGL采用的滤波方式。通常这两种情况下我都采用 GL_LINEAR 。 // 这使得纹理从很远处到离屏幕很近时都平滑显示。使用 GL_LINEAR 需要CPU和显卡做更多的运算。 // 如果您的机器很慢,您也许应该采用 GL_NEAREST 。过滤的纹理在放大的时候,看起来斑驳的很『译者注:马赛克啦』。 // 您也可以结合这两种滤波方式。在近处时使用 GL_LINEAR ,远处时 GL_NEAREST 。 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // 线形滤波 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // 线形滤波 } void Lesson5::exposeEvent(QExposeEvent *event) { Q_UNUSED(event); if (isExposed()) renderNow(); } void Lesson5::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); if (isExposed()) renderNow(); }
main.cpp
#include#includeint main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QSurfaceFormat format; format.setSamples(16); Lesson5 window; window.setFormat(format); window.resize(640, 480); window.show(); return app.exec(); }
运行效果