OpenGL是一套跨平台的用来渲染3D图形的API。OpenGL自身是一个巨大的状态机:一系列的变量描述OpenGL此刻应当如何运行,OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲,最后,我们使用当前OpenGL上下文来渲染。
假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。
当使用OpenGL的时候,我们会遇到一些状态设置函数,这类函数将会改变上下文。以及状态使用函数,这类函数会根据当前OpenGL的状态执行一些操作。比如下面例子中的glClearColor()函数是一个状态设置函数,而glClear()函数则是一个状态使用的函数,它使用了当前的状态来获取应该清除为的颜色。
Qt中的QtOpenGL模块提供了使用OpenGL的方法,使用它需要在.pro项目文件中添加QT+=opengl。QGLWidget是QtOpenGL模块中的一个类,它是一个用来渲染OpenGL图形的部件,可以继承该类后来像使用其它QWidget部件一样使用它。QGLWidget提供了3个虚函数重写它们来实现指定操作:
initializeGL():设置OpenGL渲染环境,定义显示列表等,只在resizeGL()、paintGL()之前被调用一次。
resizeGL():设置OpenGL的视口,投影等,部件大小改变的时候被调用。
paintGL():渲染OpenGL场景,当部件需要更新时被调用。
Qt中有一个Hello GL示例程序,它在OpenGL分类中。
1、基本绘制
下面代码使用QGLWidget绘制了一个直线、三角形、文本,其中的MyGLWidget继承自QGLWidget:
#include <QApplication> #include "myglwidget.h" int main(int argc, char**argv) { QApplication app(argc, argv); MyGLWidget w; w.resize(600, 400); w.show(); return app.exec(); }
#ifndef MYGLWIDGET_H #define MYGLWIDGET_H #include <QGLWidget> #include <GL/glu.h> class MyGLWidget: public QGLWidget { void initializeGL() { //void glClearColor(GLclampf red,GLclampf green,GLclampf blue,GLclampf alpha); glClearColor(1.0, 0.0, 0.0, 0.0); //设置清除屏幕(glClear()方法)时使用的颜色为红色 glShadeModel(GL_SMOOTH); //设置阴影平滑 //void glClearDepth(GLclampd depth); glClearDepth(1.0); //设置深度缓存(深度缓冲区中每个像素需要的值) glEnable(GL_DEPTH_TEST); //启用深度测试功能,根据坐标的远近自动隐藏被遮住的图形(材料) } void resizeGL(int w, int h) { //设置视口的大小:告诉OpenGL渲染窗口的尺寸大小 //也可以将视口的维度设置为比窗口的维度小,这样OpenGL渲染将会在一个小窗口中显示,这样子我们也可以将一些其它元素显示在OpenGL视口之外 glViewport(0, 0, (GLint)w, (GLint)h); glMatrixMode(GL_PROJECTION); //投影矩阵模式 glLoadIdentity(); //重置当前指定的矩阵为单位矩阵,这样可以将矩阵恢复到初始状态。 gluPerspective(45.0, (GLfloat)w / (GLfloat)h, 0.1, 100.0); //设置透视投影矩阵:视角为45度,纵横比为窗口的纵横比,最近和最远的位置分别为0.1和100 glMatrixMode(GL_MODELVIEW); //设置模型视图矩阵 glLoadIdentity(); //重置模型视图矩阵 } void paintGL() { //在每个新的渲染迭代开始的时候我们总是希望清屏,否则我们仍能看见上一次迭代的渲染结果 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕:使用glClearColor()设置的颜色和glClearDepth()设置的深度缓存 glLoadIdentity(); //重置模型视图矩阵,这样便将坐标原点移到了窗口中心 GLfloat z_translate = -6.0; //void glTranslatef(GLfloat x,GLfloat y,GLfloat z); //移动坐标原点,是相对于当前点来移动的,对于z来说正数为向屏幕外移动(相当于在眼睛后方,所以就看不到了),负数为向屏幕内移动(绝对值越大离眼睛越远,看起来越小) glTranslatef(0.0, 0.0, z_translate); //将坐标原点内移6.0 glBegin(GL_LINES); //开始绘制直线 glVertex3f(0.0, 1.0, 0.0); glVertex3f(1.0, 1.0, 0.0); glEnd(); glBegin(GL_TRIANGLES); //开始绘制三角形 //逆时针绘制各顶点 //void glVertex3f(GLfloat x,GLfloat y,GLfloat z); glVertex3f(0.0, 1.0, 0.0); glVertex3f(-1.0, -1.0, 0.0); glVertex3f(1.0, -1.0, 0.0); glEnd(); //void renderText(double x, double y, double z, const QString & str, // const QFont & fnt = QFont(), int listBase = 2000); renderText(0.0, 2.0, 0.0, "test"); //绘制文本 } }; #endif // MYGLWIDGET_H
下面分别是z_translate为-6、-4、-2、-0.1、-0.09时的效果,可以看到其绝对值越小,即为离眼睛越来越近,所以看起来越大,而如果其绝对值小于gluPerspective()设置的最近位置则就看不见了。而如果将z_translate设为正值的话相当于是在眼睛的后方,这样更不能看见了:
void glMatrixMode(GLenum mode):将当前矩阵设置成参数所指定的模式,以满足不同绘图所需执行的矩阵变换。glMatrixMode()其实就是对接下来要做什么进行一下声明,参数mode指定哪一个矩阵堆栈是下一个矩阵操作的目标:
GL_MODELVIEW, 对模型视图矩阵堆栈应用随后的矩阵操作。可以在执行此命令后,输出自己的物体图形了。在需要绘制出对象或要对所绘制对象进行几何变换时,需要将变换矩阵设置成模型视图模式;
GL_PROJECTION, 对投影矩阵堆栈应用随后的矩阵操作。可以在执行此命令后,为我们的场景增加透视。当需要对绘制的对象设置某种投影方式时,则需要将变换矩阵设置成投影模式;
GL_TEXTURE, 对纹理矩阵堆栈应用随后的矩阵操作。可以在执行此命令后,为我们的图形增加纹理贴图。在进行纹理映射时,才需要将变换矩阵设置成纹理模式。
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar):设置矩阵:
fovy是眼睛上下睁开的幅度,角度值,值越小,视野范围越狭小(眯眼),感觉物体越近,值越大,视野范围越宽阔(睁大眼),感觉物体越远;
aspect表示裁剪面的宽w高h比,这个影响到视野的截面有多大。
zNear表示近裁剪面到眼睛的距离,zFar表示远裁剪面到眼睛的距离,注意zNear和zFar不能设置设置为负值(你怎么看到眼睛后面的东西),离眼睛越近东西看起来就越大
QGLWidget下还有一个renderPixmap()方法,使用它可以获得指定矩形的截图(QPixmap),然后可以使用QPixmap的save()来保存该截图。
还可以设置绘制的时候各个顶点使用的颜色,如下所示:
void MyGLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕:使用glClearColor()设置的颜色和glClearDepth()设置的深度缓存 glLoadIdentity(); //重置模型视图矩阵,这样便将坐标原点移到了窗口中心 GLfloat z_translate = -6.0; //void glTranslatef(GLfloat x,GLfloat y,GLfloat z); glTranslatef(-2.0, 0.0, z_translate); //将坐标原点左移2.0,内移6.0 glBegin(GL_TRIANGLES); //开始绘制三角形 //逆时针绘制各顶点 //void glVertex3f(GLfloat x,GLfloat y,GLfloat z); glColor3f(1.0, 0.0, 0.0); //红色 glVertex3f(0.0, 1.0, 0.0); glColor3f(0.0, 1.0, 0.0); //绿色 glVertex3f(-1.0, -1.0, 0.0); glColor3f(0.0, 0.0, 1.0); //蓝色 glVertex3f(1.0, -1.0, 0.0); glEnd(); glTranslatef(4.0, 0.0, 0.0); //将坐标原点右移4.0 glBegin(GL_QUADS); //开始绘制四边形 //顺时针绘制各顶点 glColor3f(1.0, 1.0, 0.0); //黄色 glVertex3f(-1.0, 1.0, 0.0); glVertex3f(1.0, 1.0, 0.0); glVertex3f(1.0, -1.0, 0.0); glVertex3f(-1.0, -1.0, 0.0); glEnd(); }
2、3D效果绘制
下面的代码绘制了一个立方体的三面,使用上、下、左、右、4、6键分别沿X轴向上、向下、沿Y轴向左、向右、沿Z轴向左、向右,使用8和2键进行缩小和放大:
class MyGLWidget : public QGLWidget { public: MyGLWidget(); protected: void initializeGL()override; void resizeGL(int w, int h)override; void paintGL()override; void keyPressEvent(QKeyEvent*)override; private: GLfloat translate = -6.0, xRot = 0.0, yRot = 0.0, zRot = 0.0; };
#include "myglwidget.h" #include<GL/glu.h> #include <QKeyEvent> MyGLWidget::MyGLWidget() { } void MyGLWidget::initializeGL() { //void glClearColor(GLclampf red,GLclampf green,GLclampf blue,GLclampf alpha); glClearColor(1.0, 0.0, 0.0, 0.0); //设置清除屏幕时使用的颜色为红色 glShadeModel(GL_SMOOTH); //设置阴影平滑 //void glClearDepth(GLclampd depth); glClearDepth(1.0); //设置深度缓存(深度缓冲区中每个像素需要的值) glEnable(GL_DEPTH_TEST); //启用深度测试功能,根据坐标的远近自动隐藏被遮住的图形(材料) } void MyGLWidget::resizeGL(int w, int h) { glViewport(0, 0, (GLint)w, (GLint)h); //设置视口的大小 glMatrixMode(GL_PROJECTION); //投影矩阵模式 glLoadIdentity(); //重置当前指定的矩阵为单位矩阵,这样可以将矩阵恢复到初始状态。 gluPerspective(45.0, (GLfloat)w / (GLfloat)h, 0.1, 100.0); //设置透视投影矩阵:视角为45度,纵横比为窗口的纵横比,最近和最远的位置分别为0.1和100 glMatrixMode(GL_MODELVIEW); //设置模型视图矩阵 glLoadIdentity(); //重置模型视图矩阵 } void MyGLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); //坐标原点沿Z轴移动translate,正数为向屏幕外移,负数为向屏幕内移,向屏幕外移相当于离眼睛越来越近所以更大 glTranslatef(0.0, 0.0, translate); //void glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z); glRotatef(xRot, 1.0, 0.0, 0.0); //绕X轴旋转到xRot度(正数为逆时针、负数为顺时针), glRotatef(xRot, -1.0, 0.0, 0.0)为绕X轴旋转到xRot度(负数为逆时针、正数为顺时针) glRotatef(yRot, 0.0, 1.0, 0.0); //绕Y轴旋转到yRot度 glRotatef(zRot, 0.0, 0.0, 1.0); //绕Z轴旋转到zRot度 glBegin(GL_QUADS); //逆时针(从上往下看)绘制正方体的上面 glColor3f(1.0, 1.0, 0.0); //黄色 glVertex3f(1.0, 1.0, 1.0); glVertex3f(1.0, 1.0, -1.0); glVertex3f(-1.0, 1.0, -1.0); glVertex3f(-1.0, 1.0, 1.0); //逆时针(从上往下看)绘制正方体的下面 glColor3f(0.0, 1.0, 0.0); //绿色 glVertex3f(1.0, -1.0, 1.0); glVertex3f(1.0, -1.0, -1.0); glVertex3f(-1.0, -1.0, -1.0); glVertex3f(-1.0, -1.0, 1.0); //逆时针绘制正方体的前面 glColor3f(0.0, 0.0, 1.0); //蓝色 glVertex3f(1.0, 1.0, 1.0); glVertex3f(-1.0, 1.0, 1.0); glVertex3f(-1.0, -1.0, 1.0); glVertex3f(1.0, -1.0, 1.0); glEnd(); } void MyGLWidget::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Up: xRot -= 10; break; case Qt::Key_Down: xRot += 10; break; case Qt::Key_Left: yRot -= 10; break; case Qt::Key_Right: yRot += 10; break; case Qt::Key_4: zRot += 10; break; case Qt::Key_6: zRot -= 10; break; case Qt::Key_8: if(--translate <= -20) translate = -1; break; case Qt::Key_2: if(++translate >= -1) translate = -20; break; } updateGL(); //更新绘制 QGLWidget::keyPressEvent(event); }
3、使用纹理贴图
使用纹理贴图可以参考Qt中的Textures示例程序。
#include <QGLWidget> class MyGLWidget : public QGLWidget { public: MyGLWidget(); protected: void initializeGL()override; void resizeGL(int w, int h)override; void paintGL()override; void keyPressEvent(QKeyEvent*)override; private: GLfloat translate = -6.0, xRot = 0.0, yRot = 0.0, zRot = 0.0; GLuint textures[3]; //存储纹理 };
void MyGLWidget::initializeGL() { glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_SMOOTH); glClearDepth(1.0); glEnable(GL_DEPTH_TEST); //创建并绑定纹理 textures[0] = bindTexture(QPixmap("F://side1.png")); textures[1] = bindTexture(QPixmap("F://side2.png")); textures[2] = bindTexture(QPixmap("F://side3.png")); glEnable(GL_TEXTURE_2D); //开启2D纹理贴图功能 } ...... void MyGLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(0.0, 0.0, translate); glRotatef(xRot, 1.0, 0.0, 0.0); glRotatef(yRot, 0.0, 1.0, 0.0); glRotatef(zRot, 0.0, 0.0, 1.0); //绘制正方体的上面 glBindTexture(GL_TEXTURE_2D, textures[2]); //绑定纹理 glBegin(GL_QUADS); //更改使用的纹理必须再次使用glBegin()来开始一个新的绘图 //void glTexCoord2f(GLfloat s,GLfloat t); //设置纹理的坐标,应该在每次绘制顶点前调用它,纹理的四个顶点应该与四边形四个顶点正确对应 glTexCoord2f(1.0, 0.0); //第一个参数是X坐标(0.0为纹理的最左侧,1.0为纹理的最右侧),第二个参数是Y坐标(0.0为纹理的底部,1.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, textures[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, textures[0]); 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(); } ......
可以看到图形下面的2看起来是倒置的,因为我们绘制的时候是从上往下看的,将纹理的(0.0, 0.0)顶点和(0.0, 1.0)顶点对置、(1.0, 0.0)顶点和(1.0, 1.0)顶点对置即可,如下所示:
...... //绘制正方体的下面 glBindTexture(GL_TEXTURE_2D, textures[1]); glBegin(GL_QUADS); glTexCoord2f(1.0, 1.0); glVertex3f(1.0, -1.0, 1.0); glTexCoord2f(1.0, 0.0); glVertex3f(1.0, -1.0, -1.0); glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, -1.0); glTexCoord2f(0.0, 1.0); glVertex3f(-1.0, -1.0, 1.0); glEnd(); ......
4、在3D场景中绘制2D图形
QGLWidget可以实现3D场景绘图,而QGLWidget也是一个QWidget部件,所以也可以进行2D绘图,可以参考Overpainting示例程序。如下所示的示例先在构造函数中调用setAutoFillBackground(),然后在keyPressEvent()中将updateGL()替换为update(),然后将initializeGL()、resizeGL()、paintGL()中代码剪切到新的方法中后在paintEvent()方法中添加对它们的调用:
class MyGLWidget : public QGLWidget { public: MyGLWidget(); protected: void initializeGL()override; void resizeGL(int w, int h)override; void paintGL()override; void keyPressEvent(QKeyEvent*)override; void paintEvent(QPaintEvent*)override; void initializeGL2(); void resizeGL2(int w, int h); void paintGL2(); private: GLfloat translate = -6.0, xRot = 0.0, yRot = 0.0, zRot = 0.0; GLuint textures[3]; };
MyGLWidget::MyGLWidget() { setAutoFillBackground(false); //关闭自动填充背景 } void MyGLWidget::initializeGL() { } void MyGLWidget::resizeGL(int w, int h) { } void MyGLWidget::paintGL() { } void MyGLWidget::initializeGL2() { glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_SMOOTH); glClearDepth(1.0); glEnable(GL_DEPTH_TEST); textures[0] = bindTexture(QPixmap("F://side1.png")); textures[1] = bindTexture(QPixmap("F://side2.png")); textures[2] = bindTexture(QPixmap("F://side3.png")); glEnable(GL_TEXTURE_2D); } void MyGLWidget::resizeGL2(int w, int h) { glViewport(0, 0, (GLint)w, (GLint)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, (GLint)w / (GLint)h, 0.1, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void MyGLWidget::paintGL2() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(0.0, 0.0, translate); glRotatef(xRot, 1.0, 0.0, 0.0); glRotatef(yRot, 0.0, 1.0, 0.0); glRotatef(zRot, 0.0, 0.0, 1.0); glBindTexture(GL_TEXTURE_2D, textures[2]); 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, textures[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, textures[0]); 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(); } void MyGLWidget::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Up: xRot -= 10; break; case Qt::Key_Down: xRot += 10; break; case Qt::Key_Left: yRot -= 10; break; case Qt::Key_Right: yRot += 10; break; case Qt::Key_4: zRot += 10; break; case Qt::Key_6: zRot -= 10; break; case Qt::Key_8: if(--translate <= -20) translate = -1; break; case Qt::Key_2: if(++translate >= -1) translate = -20; break; } update(); //更新绘制 QGLWidget::keyPressEvent(event); } void MyGLWidget::paintEvent(QPaintEvent*) { makeCurrent(); //在当前窗口中进行OpenGL的绘制 glMatrixMode(GL_MODELVIEW); //将模型视图矩阵压入堆栈 glPushMatrix(); initializeGL2(); resizeGL2(width(), height()); paintGL2(); //关闭启用的功能并弹出模型视图矩阵 glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glMatrixMode(GL_MODELVIEW); glPopMatrix(); //进行2D绘图 QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(Qt::yellow); painter.drawRect(0, 0, 100, 100); painter.end(); }