【Qt OpenGL教程】23:球面映射
2015-08-10 12:39
447 查看
第23课:球面映射[b] (参照NeHe)[/b]
这次教程中,我们将学会如何把环境纹理包裹在我们的3D模型上,让它看起来像反射了周围的景象一样,我们把这种纹理映射的方式称为球体映射。球体映射是一种创建金属反射效果的方法,虽然它并不像真实世界里那么精确,但效果还是很不错的!
红宝书中,球体映射的定义为,把一幅位于无限远的图像映射到球面上。当然我们需要自己来创建一幅球体环境映射图,方法如下:
打开Photoshop,并在Photoshop中打开我们要转换成球体环境映射图的原图,选择所有的像素点,创建它的一个复制。接着我们把图像变为2的幂次方大小,一般为128×128或256×256.最后使用扭曲滤镜,并应用球体效果,然后把得到的图像保存为 *.bmp文件(PS:我给大家的资源文件中球体映射图是处理好的了,命名为Reflect.bmp)。
程序运行时效果如下:
下面进入教程:
我们这次将在第18课的基础上修改代码,这次相比前两次简单太多了,我只会对代码的变化作出解释。首先打开myglwidget.h文件,将类声明更改如下:
接下来,我们需要打开myglwidget.cpp,对构造函数进行修改(更换图片途径名和删掉不存在的变量),很简单不多解释,具体代码如下:
继续,我们要对glDrawCube()函数作一些小改动,具体代码如下:
然后我们需要修改一下initializeGL()函数,我们添加一些新的函数(glTexGeni)来使用球体纹理映射,具体代码如下:
还有就是paintGL()函数,我们也是作了部分的修改,我只解释增加的部分,具体代码如下:
然后我们选择作为背景的平面纹理,同样保存模型观察矩阵,平移后绘制出背景四边形,绘制完同样恢复模型观察矩阵。
最后我们修改一下键盘控制函数,不多解释了,就是改了一下m_Object的最大值,具体代码如下:
全部教程中需要的资源文件点此下载
一点内容的补充:这里我准备解释一下上面glDrawCube()函数中法线范围修改的问题,大家先看下图(左侧为修改前,右面为修改后):
很明显左侧立方体中,对于环境的映射有块状现象,模糊了很多。这是由于glNormal()函数会根据参数对纹理坐标进行一定比例的放大缩小,1.0时保持原状,大于1.0时为放大,小于1.0时为缩小,所以我们把参数方位调整为[-0.5, 0.5]实际上缩小纹理图像(这里说的放大缩小是针对图像的,实际纹理坐标范围是相反的;可以想象,纹理坐标范围缩小,映射出来的图像就像被放大了一样)。
那为什么右侧的图里立方体上的纹理看起来还是比背景图大呢?大家不要忘了,我们在绘制背景图时还向屏幕里移入24.0单位呢,看起来小没什么问题吧。
这次教程中,我们将学会如何把环境纹理包裹在我们的3D模型上,让它看起来像反射了周围的景象一样,我们把这种纹理映射的方式称为球体映射。球体映射是一种创建金属反射效果的方法,虽然它并不像真实世界里那么精确,但效果还是很不错的!
红宝书中,球体映射的定义为,把一幅位于无限远的图像映射到球面上。当然我们需要自己来创建一幅球体环境映射图,方法如下:
打开Photoshop,并在Photoshop中打开我们要转换成球体环境映射图的原图,选择所有的像素点,创建它的一个复制。接着我们把图像变为2的幂次方大小,一般为128×128或256×256.最后使用扭曲滤镜,并应用球体效果,然后把得到的图像保存为 *.bmp文件(PS:我给大家的资源文件中球体映射图是处理好的了,命名为Reflect.bmp)。
程序运行时效果如下:
下面进入教程:
我们这次将在第18课的基础上修改代码,这次相比前两次简单太多了,我只会对代码的变化作出解释。首先打开myglwidget.h文件,将类声明更改如下:
#ifndef MYGLWIDGET_H #define MYGLWIDGET_H #include <QWidget> #include <QGLWidget> class GLUquadric; class MyGLWidget : public QGLWidget { Q_OBJECT public: explicit MyGLWidget(QWidget *parent = 0); ~MyGLWidget(); protected: //对3个纯虚函数的重定义 void initializeGL(); void resizeGL(int w, int h); void paintGL(); void keyPressEvent(QKeyEvent *event); //处理键盘按下事件 private: void glDrawCube(); //绘制立方体 private: bool fullscreen; //是否全屏显示 QString m_FileName[2]; //图片的路径及文件名 GLuint m_Texture[2]; //储存两个纹理 bool m_Light; //光源的开/关 GLfloat m_xRot; //x旋转角度 GLfloat m_yRot; //y旋转角度 GLfloat m_xSpeed; //x旋转速度 GLfloat m_ySpeed; //y旋转速度 GLfloat m_Deep; //深入屏幕的距离 GLUquadric *m_Quadratic; //二次几何体 GLuint m_Object; //绘制对象标示符 }; #endif // MYGLWIDGET_H由于我们程序的背景图和用于纹理映射的图并不是同一张(后者是PS处理过的那张),所以我们需要储存两个不同的纹理,m_FileName和m_Texture就都变成长度为2的数组啦。然后我们删掉了m_Part1、m_Part2、m_P1、m_P2等变量,由于我们后面的绘制过程并不打算绘制圆盘和部分圆盘,这是因为它们绘制出来的映射效果并不好,所以我就不绘制它们了。
接下来,我们需要打开myglwidget.cpp,对构造函数进行修改(更换图片途径名和删掉不存在的变量),很简单不多解释,具体代码如下:
MyGLWidget::MyGLWidget(QWidget *parent) : QGLWidget(parent) { fullscreen = false; m_FileName[0] = "D:/QtOpenGL/QtImage/BG.bmp"; //应根据实际存放图片的路径进行修改 m_FileName[1] = "D:/QtOpenGL/QtImage/Reflect.bmp"; m_Light = false; m_xRot = 0.0f; m_yRot = 0.0f; m_xSpeed = 0.0f; m_ySpeed = 0.0f; m_Deep = -10.0f; m_Object = 0; QTimer *timer = new QTimer(this); //创建一个定时器 //将定时器的计时信号与updateGL()绑定 connect(timer, SIGNAL(timeout()), this, SLOT(updateGL())); timer->start(10); //以10ms为一个计时周期 }
继续,我们要对glDrawCube()函数作一些小改动,具体代码如下:
void MyGLWidget::glDrawCube() { glBegin(GL_QUADS); //开始绘制立方体 glNormal3f(0.0f, 0.5f, 0.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); //右下(顶面) glNormal3f(0.0f, -0.5f, 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); //左上(底面) glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); //左下(底面) glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f); //右下(底面) glNormal3f(0.0f, 0.0f, 0.5f); 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); //右下(前面) glNormal3f(0.0f, 0.0f, -0.5f); 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); //右下(后面) glNormal3f(-0.5f, 0.0f, 0.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); //右下(左面) glNormal3f(0.5f, 0.0f, 0.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); //右下(右面) glEnd(); //立方体绘制结束 }几乎没有什么改动,就是把法线(glNormal)的范围从[-1, 1]缩放到[-0.5, 0.5]。如果法向量太大的话,会产生一些块状效果,影响视觉效果。
然后我们需要修改一下initializeGL()函数,我们添加一些新的函数(glTexGeni)来使用球体纹理映射,具体代码如下:
void MyGLWidget::initializeGL() //此处开始对OpenGL进行所以设置 { m_Texture[0] = bindTexture(QPixmap(m_FileName[0])); //载入位图并转换成纹理 m_Texture[1] = bindTexture(QPixmap(m_FileName[1])); glEnable(GL_TEXTURE_2D); //启用纹理映射 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);//设置s方向的纹理坐标自动生成 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);//设置t方向的纹理坐标自动生成 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //黑色背景 glShadeModel(GL_SMOOTH); //启用阴影平滑 glClearDepth(1.0); //设置深度缓存 glEnable(GL_DEPTH_TEST); //启用深度测试 glDepthFunc(GL_LEQUAL); //所作深度测试的类型 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //告诉系统对透视进行修正 m_Quadratic = gluNewQuadric(); //创建二次几何体 gluQuadricNormals(m_Quadratic, GLU_SMOOTH); //使用平滑法线 gluQuadricTexture(m_Quadratic, GL_TRUE); //使用纹理 //光源部分 GLfloat LightAmbient[] = {0.5f, 0.5f, 0.5f, 1.0f}; //环境光参数 GLfloat LightDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f}; //漫散光参数 GLfloat LightPosition[] = {0.0f, 0.0f, 2.0f, 1.0f}; //光源位置 glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient); //设置环境光 glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse); //设置漫射光 glLightfv(GL_LIGHT1, GL_POSITION, LightPosition); //设置光源位置 glEnable(GL_LIGHT1); //启动一号光源 }注意到,我们除了加载了两个位图并转换成纹理外,我们还调用了glTexGeni()函数。这个函数在之前第15课有提到过,就是让OpenGL为我们在指定的方向上(S、T方向),自动生成纹理映射的坐标。然后函数的第三个参数设置为GL_SPHERE_MAP,使得创建出一种有金属质感的物体(大家不记得这个函数了的话,请参照前面第15课)。
还有就是paintGL()函数,我们也是作了部分的修改,我只解释增加的部分,具体代码如下:
void MyGLWidget::paintGL() //从这里开始进行所以的绘制 { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存 glLoadIdentity(); //重置模型观察矩阵 glTranslatef(0.0f, 0.0f, m_Deep); //移入屏幕 glEnable(GL_TEXTURE_GEN_S); //启用自动生成s方向纹理坐标 glEnable(GL_TEXTURE_GEN_T); //启用自动生成t方向纹理坐标 glBindTexture(GL_TEXTURE_2D, m_Texture[1]); //选择球体映射纹理 glPushMatrix(); //保存模型观察矩阵 glRotatef(m_xRot, 1.0f, 0.0f, 0.0f); //绕x轴旋转 glRotatef(m_yRot, 0.0f, 1.0f, 0.0f); //绕y轴旋转 switch(m_Object) { case 0: //绘制立方体 glDrawCube(); break; case 1: //绘制圆柱体 glTranslatef(0.0f, 0.0f, -1.5f); gluCylinder(m_Quadratic, 1.0f, 1.0f, 3.0f, 64, 64); break; case 2: //绘制球 gluSphere(m_Quadratic, 1.3f, 64, 64); break; case 3: //绘制圆锥 glTranslatef(0.0f, 0.0f, -1.5f); gluCylinder(m_Quadratic, 1.0f, 0.0f, 3.0f, 64, 64); break; } glPopMatrix(); //恢复模型观察矩阵 glDisable(GL_TEXTURE_GEN_S); //禁用自动生成纹理坐标 glDisable(GL_TEXTURE_GEN_T); glBindTexture(GL_TEXTURE_2D, m_Texture[0]); //选择平面纹理 glPushMatrix(); //保存模型观察矩阵 glTranslatef(0.0f, 0.0f, -24.0); //移入屏幕24.0单位 glBegin(GL_QUADS); //绘制背景四边形 glNormal3f(0.0f, 0.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-13.3f, -10.0f, 10.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(13.3f, -10.0f, 10.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(13.3f, 10.0f, 10.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-13.3f, 10.0f, 10.0f); glEnd(); glPopMatrix(); //恢复模型观察矩阵 m_xRot += m_xSpeed; //x轴旋转 m_yRot += m_ySpeed; //y轴旋转 }首先在移入屏幕(glTranslate)后,我们开启自动生成纹理坐标,要注意必须自己手动开启,不然前面initializeGL()函数中的设置是不会生效的。接着我们选择球面映射的纹理,保存模型观察矩阵(glPushMatrix)后,进行旋转后绘制会我们的3D模型。绘制完3D模型后,我们恢复模型观察矩阵(glPopMatrix)并禁用自动生成纹理坐标。正如前面提到,由于绘制出来的圆盘和部分圆盘效果并不好,所以我们把绘制它们的部分删掉了,对switch语句作了修改,现在我们将会绘制的3D模型有立方体、圆柱、球和圆锥。
然后我们选择作为背景的平面纹理,同样保存模型观察矩阵,平移后绘制出背景四边形,绘制完同样恢复模型观察矩阵。
最后我们修改一下键盘控制函数,不多解释了,就是改了一下m_Object的最大值,具体代码如下:
void MyGLWidget::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_F1: //F1为全屏和普通屏的切换键 fullscreen = !fullscreen; if (fullscreen) { showFullScreen(); } else { showNormal(); } break; case Qt::Key_Escape: //ESC为退出键 close(); break; case Qt::Key_L: //L为开启关闭光源的切换键 m_Light = !m_Light; if (m_Light) { glEnable(GL_LIGHTING); //开启光源 } else { glDisable(GL_LIGHTING); //关闭光源 } break; case Qt::Key_Space: //空格为物体的切换键 m_Object++; if (m_Object == 4) { m_Object = 0; } break; case Qt::Key_PageUp: //PageUp按下使木箱移向屏幕内部 m_Deep -= 0.1f; break; case Qt::Key_PageDown: //PageDown按下使木箱移向观察者 m_Deep += 0.1f; break; case Qt::Key_Up: //Up按下减少m_xSpeed m_xSpeed -= 0.1f; break; case Qt::Key_Down: //Down按下增加m_xSpeed m_xSpeed += 0.1f; break; case Qt::Key_Right: //Right按下减少m_ySpeed m_ySpeed -= 0.1f; break; case Qt::Key_Left: //Left按下增加m_ySpeed m_ySpeed += 0.1f; break; } }现在就可以运行程序查看效果了!
全部教程中需要的资源文件点此下载
一点内容的补充:这里我准备解释一下上面glDrawCube()函数中法线范围修改的问题,大家先看下图(左侧为修改前,右面为修改后):
很明显左侧立方体中,对于环境的映射有块状现象,模糊了很多。这是由于glNormal()函数会根据参数对纹理坐标进行一定比例的放大缩小,1.0时保持原状,大于1.0时为放大,小于1.0时为缩小,所以我们把参数方位调整为[-0.5, 0.5]实际上缩小纹理图像(这里说的放大缩小是针对图像的,实际纹理坐标范围是相反的;可以想象,纹理坐标范围缩小,映射出来的图像就像被放大了一样)。
那为什么右侧的图里立方体上的纹理看起来还是比背景图大呢?大家不要忘了,我们在绘制背景图时还向屏幕里移入24.0单位呢,看起来小没什么问题吧。
相关文章推荐
- QT程序登录界面回车按钮响应
- Qt 5.3 下OpenCV 2.4.11 开发(10)使用鼠标回调函数对视频帧截图
- Windows编译64位QT并使用VS2010开发
- QT5 QString
- unsigned int 与 unsigned long qt中的ulonglong
- PyQt5初级教程--PyQt5中绘画[11/13]
- QT变异版本下载(SJLJ长跳转,DWARF不传递错误(32位专用),SEH(64位专用)),以及QT的实验室项目
- PyQt5初级教程--PyQt5中的拖放[10/13]
- 转:Qt运行cmd命令
- Qt 操作系统环境变量
- QT 绘制 五角图案
- PyQt5初级教程--PyQt5中的部件II[9/13]
- Qt杂记
- 使用QT 2D绘图实现窗体的阴影效果
- 【Qt OpenGL教程】21:线、反走样、正投影和简单的声音
- QT中的2D绘图的总结
- Qt-QPalette类的用法
- qt下编写andriod程序必须报错总结
- QT中实现在控制台输出
- PyQt5初级教程--PyQt5中部件[8/13]