OpenGL(八)——Qt OpenGL实现一个旋转的正方体
一、前言
上一篇文章介绍了参考NeHe教程实现的旋转的正方体,上篇文章链接如下:
先看看效果:
二、代码
实现这样的一个效果主要的步骤就是,先绘制一个正方体,然后通过纹理贴图的方式把图片,贴到你想贴到的具体的面上,总而言之,就是绘制的原理其实就是多个纹理的绘制过程。
其中,正方体的旋转调用了glRotatef()函数:
glRotatef( Angle, Xvector, Yvector, Zvector )负责让对象绕某个轴旋转。这个函数有很多用处。 Angle 通常是个变量代表对象转过的角度。Xvector,Yvector和Zvector三个参数则共同决定旋转轴的方向。比如( 1, 0, 0 )所描述的矢量经过X坐标轴的1个单位处并且方向向右。( -1, 0, 0 )所描述的矢量经过X坐标轴的1个单位处,但方向向左。
其中类的.h文件如下所示:
#include <QObject>
#include <QWidget>
#include <qgl.h>
#include <QTimer>
/*
*绘制多个纹理.
*/
class NeHe_6_2_Widget : public QGLWidget
{
Q_OBJECT
public:
NeHe_6_2_Widget(QWidget *parent = 0);
~NeHe_6_2_Widget();
public slots:
void slotTimer();
protected:
void initializeGL(); //是用来初始化这个OpenGL窗口部件的,可以在里面设定一些有关选项.
void paintGL(); //是用来绘制OpenGL的窗口了,只要有更新发生,这个函数就会被调用.
void resizeGL( int width, int height ); //就是用来处理窗口大小变化这一事件的,width和height就是新的大小状态下的宽和高了,另外resizeGL()在处理完后会自动刷新屏幕。
void loadGLTextures(); //在这个函数中我们会载入指定的图片并生成相应当纹理。
GLfloat xRot, yRot, zRot ;
GLuint texture[6];
private:
QTimer* m_timer;
};
类的cpp文件如下所示:
#include <GL/glu.h>
#include <GL/gl.h>
#include <QImage>
#include <QDebug>
/*
学习texture map纹理映射(贴图)有很多好处。比方说您想让一颗导弹飞过屏幕。根据前几课的知识,\
我们最可行的办法可能是很多个多边形来构建导弹的轮廓并加上有趣的颜色。使用纹理映射,您可以使用真实\
的导弹图像并让它飞过屏幕。您觉得哪个更好看?照片还是一大堆三角形和四边形?使用纹理映射的好处还不止\
是更好看,而且您的程序运行会更快。导弹贴图可能只是一个飞过窗口的四边形。一个由多边形构建而来的导弹\
却很可能包括成百上千的多边形。很显然,贴图极大的节省了CPU时间。
我们要在第一课的代码上增加几行就可以了。
我们将要增加一个loadGLTextures()函数来处理有关纹理操作的。我们将在NeHeWidget类中增加三个变量\
xRot、yRot、zRot来处理立方体的旋转。还有一个用来存储纹理的texture[1]。
*/
NeHe_6_2_Widget::NeHe_6_2_Widget(QWidget *parent):QGLWidget(parent)
{
xRot = yRot = zRot = 0.0;
setGeometry( 100, 200, 640, 480 );
//setCaption( "NeHe's Texture Mapping Tutorial" );
m_timer = new QTimer(this);
m_timer->setInterval(50);
connect(m_timer,SIGNAL(timeout()),this,SLOT(slotTimer()));
m_timer->start();
}
NeHe_6_2_Widget::~NeHe_6_2_Widget()
{
}
void NeHe_6_2_Widget::initializeGL()
{
loadGLTextures();
//载入纹理.
glEnable( GL_TEXTURE_2D );
//启用纹理。如果没有启用的话,你的对象看起来永远都是纯白色
glShadeModel( GL_SMOOTH );
glClearColor( 0.0, 0.0, 0.0, 0.5 );
glClearDepth( 1.0 );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
//所作深度测试的类型。
//上面这三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。\
我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将\
一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
//真正精细的透视修正。这一行告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
}
void NeHe_6_2_Widget::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是我们在场景中所能绘制深度的起点和终点。
}
void NeHe_6_2_Widget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
//清楚屏幕和深度缓存。
glLoadIdentity();
//重置当前的模型观察矩阵。
glTranslatef( 0.0, 1.0, -7.0 );
glRotatef( xRot, 1.0, 0.0, 0.0 );
glRotatef( yRot, 0.0, 1.0, 0.0 );
glRotatef( zRot, 0.0, 0.0, 1.0 );
//根据xRot、yRot、zRot的实际值来旋转正方体。
glBindTexture( GL_TEXTURE_2D, texture[4] );
//选择我们使用的纹理。如果您在您的场景中使用多个纹理,您应该使用来glBindTexture(GL_TEXTURE_2D, texture[所使用纹理对应的数字]) \
选择要绑定的纹理。当您想改变纹理时,应该绑定新的纹理。有一点值得指出的是,您不能在glBegin()和glEnd()之间绑定纹理,必须在glBegin()\
之前或glEnd()之后绑定。注意我们在上面是如何使用glBindTexture来指定和绑定纹理的。
glBegin( GL_QUADS );
//为了将纹理正确的映射到四边形上,您必须将纹理的右上角映射到四边形的右上角,纹理的左上角映射到四边形的左上角,纹理的右下角映射到四边形的右下角,\
纹理的左下角映射到四边形的左下角。如果映射错误的话,图像显示时可能上下颠倒,侧向一边或者什么都不是。
//glTexCoord2f的第一个参数是X坐标。0.0是纹理的左侧。0.5是纹理的中点,1.0是纹理的右侧。glTexCoord2f的第二个参数是Y坐标。0.0是纹理的底部。\
0.5是纹理的中点,1.0是纹理的顶部。
//所以纹理的左上坐标是X:0.0,Y:1.0f,四边形的左上顶点是X:-1.0,Y:1.0。其余三点依此类推。
//试着玩玩glTexCoord2f的X、Y坐标参数。把1.0改为0.5将只显示纹理的左半部分,把0.0改为0.5将只显示纹理的右半部分。
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 ); //前面.
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glEnd();
//glActiveTexture();
//glActiveTextureARB();
//glBindTexture( GL_TEXTURE_2D, texture[1] );
glBindTexture( GL_TEXTURE_2D, texture[1] );
glBegin( GL_QUADS );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 ); //后面
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glEnd();
glBindTexture( GL_TEXTURE_2D, texture[2] );
glBegin( GL_QUADS );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 ); //顶面
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glEnd();
glBindTexture( GL_TEXTURE_2D, texture[3] );
glBegin( GL_QUADS );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, -1.0, -1.0 ); //底面
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, -1.0, -1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glEnd();
glBindTexture( GL_TEXTURE_2D, texture[5] );
glBegin( GL_QUADS );
glTexCoord2f( 1.0, 0.0 ); glVertex3f( 1.0, -1.0, -1.0 ); //右面
glTexCoord2f( 1.0, 1.0 ); glVertex3f( 1.0, 1.0, -1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( 1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( 1.0, -1.0, 1.0 );
glEnd();
glBindTexture( GL_TEXTURE_2D, texture[2] );
glBegin( GL_QUADS );
glTexCoord2f( 0.0, 0.0 ); glVertex3f( -1.0, -1.0, -1.0 ); //左面
glTexCoord2f( 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 1.0 );
glTexCoord2f( 1.0, 1.0 ); glVertex3f( -1.0, 1.0, 1.0 );
glTexCoord2f( 0.0, 1.0 ); glVertex3f( -1.0, 1.0, -1.0 );
glEnd();
xRot += 0.3;
yRot += 0.2;
zRot += 0.4;
//现在改变xRot、yRot、zRot的值。尝试变化每次各变量的改变值来调节立方体的旋转速度,或改变+/-号来调节立方体的旋转方向。
}
//loadGLTextures()函数就是用来载入纹理的。
void NeHe_6_2_Widget::loadGLTextures()
{
QStringList strList;
strList<<":/data/pg001.jpeg"
<<":/data/pg002.jpeg"
<<":/data/pg003.jpeg"
<<":/data/pg004.jpeg"
<<":/data/pg005.jpeg"
<<":/data/NeHe.bmp";
glGenTextures( 6, &texture[0] );
QString strPath;
QImage tex,buf;
for(int i=0; i<strList.count(); i++)
{
strPath = strList.at(i);
qDebug()<<strPath;
if (!buf.load( strPath )){
qWarning( "Could not read image file, using single-color instead." );
QImage dummy( 128, 128, QImage::Format_RGB32 );
dummy.fill( Qt::green );
buf = dummy;
//如果载入不成功,自动生成一个128*128的32位色的绿色图片。
}
tex = QGLWidget::convertToGLFormat(buf);
//glGenTextures( i+1, &texture[i] );
glBindTexture( GL_TEXTURE_2D, texture[i] );
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 );
}
// glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
// glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
//上面的两行告诉OpenGL在显示图像时,当它比放大得原始的纹理大(GL_TEXTURE_MAG_FILTER)或缩小得比原始得\
纹理小(GL_TEXTURE_MIN_FILTER)时OpenGL采用的滤波方式。通常这两种情况下我都采用GL_LINEAR。这使得纹理\
从很远处到离屏幕很近时都平滑显示。使用GL_LINEAR需要CPU和显卡做更多的运算。如果您的机器很慢,您也许应该采用\
GL_NEAREST。过滤的纹理在放大的时候,看起来斑驳的很。您也可以结合这两种滤波方式。在近处时使用GL_LINEAR,\
远处时GL_NEAREST。
}
void NeHe_6_2_Widget::slotTimer()
{
xRot += 0.3;
yRot += 0.2;
zRot += 0.4;
update();
}
下一篇:OpenGL(九)——Qt OpenGL创建一个OpenGL的窗口
本文原创作者:冯一川(ifeng12358@163.com),未经作者授权同意,请勿转载。