在QT中进行OpenGL开发的实现方式有很多种,最简单直接的方式就是通过子类化QOpenGLWindow实现OpenGL的绘制和渲染。QOpenGLWindow是用来显示OpenGL的窗口和QT中的其它的标准窗口没有什么区别,我们可以将它和其它的QT控件组合到一起使用。
QT通过QOpenGLContext类来保存OpenGL的上下文状态从而对OpenGL的渲染和绘制过程进行控制。我们的所有绘制和渲染操作都是针对当前的上下文的,可以通过切换上下文实现不同状态之间的切换。在进行渲染绘制的时候一定要把对应的上下文设置成当前的上下文状态,然后再进行绘制。
在子类化QOpenGLWindow的时候,我们主要实现下面三个接口:
//该函数在调用paintGL()和resizeGL()之前被调用一次,负责初始化绘制资源和状态值
//该函数绘把上下文激活设置为当前,说不在需要单独调用makeCurrent()函数了。
void QOpenGLWindow::initializeGL();
//paintGL()和QWidget的paintEvent()函数等价,负责对OpenGL图形进行绘制,当需要更新或者重绘
//界面的时候该函数也会被调用
void QOpenGLWindow::paintGL();
//窗口尺寸发生变化的时候被调用,通过实现该函数,我们可以在窗口尺寸发生变化的时候
//对绘制的图像进行调整
void QOpenGLWindow::resizeGL();
OpenGL窗口搭建完成之后,我们就可以对渲染的图像进行绘制了。OpenGL提供了一系列接口用来对图形进行绘制。如果开发跨平台的应用的时候,处理不同平台下这些接口的差异还是比较繁琐的。为了解决跨平台的问题,QT对这些绘制接口进行了封装方便应用的跨平台操作。在QT应用中我们通过QOpenGLFunctions类来访问OpenGL的函数。
QOpenGLFunctions类只提供了OpenGL ES 2.0标准的部分API,这些接口在支持QT框架的大多数桌面系统和嵌入式系统中都是可以直接调用的。 但是这些接口只是一些比较旧的接口,使用起来可能不是很方便。如果想使用一些比较新的接口,还可以单独引用固定版本的OpenGL函数库,比如QOpenGLFunctions_3_3_Core类就包含了OpenGL3.3版本的函数接口。采用新版本的函数库类,优点是接口丰富调用方便,缺点支持的平台可能比较少,在部分平台下可能无法使用。因此开发者需要根据自己的具体业务场景进行权衡。
为了在应用中使用QT提供的OpenGL的绘制函数,我们在实现自定义的窗口时,可以同时继承QOpenGLWindow类和QOpenGLFunctions类。采用这种方法开发OpenGL应用的时候一定要区分QOpenGLFunctions函数提供的绘制接口和原生的OpenGL绘制接口,因为如果QOpenGLFunctions没有对应的接口的话,应用中很有可能你调用的就是原生的接口。原生的接口在跨平台的时候可能会出问题。因此如果你的应用有跨平台的需求的话,一定要确保调用的是QOpenGLFunctions提供的封装接口。为了解决这个问题,我们还可以将QOpenGLFunctions类作为窗口的私有成员变量,然后通过成员变量的方式进行访问,这样就能确保调用的一定是QT提供的绘制接口了。
在调用QT提供的OpenGL函数接口的之前,一定要在当前的OpenGL context下调用initializeOpenGLFunctions() 函数对函数接口进行初始化操作。一般这个初始化操作在QOpenGLWindow::initializeGL()接口中进行。
绘制三角形
三角形是OpenGL绘制的基本图元,复杂图元在渲染的时候都会被拆分成一个个小三角形来进行渲染。这里以一个绘制三角形图元的例子来说明一下QT OpenGL如何实现自定义绘制:
//simpleglwindow.h
#ifndef SIMPLEGLWINDOW_H
#define SIMPLEGLWINDOW_H
#include <QOpenGLWindow>
#include <QOpenGLFunctions_1_1>
//使用的OpenGLFunction的版本是1.1
class SimpleGLWindow : public QOpenGLWindow, protected QOpenGLFunctions_1_1
{
public:
SimpleGLWindow(QWindow *parent = 0);
protected:
//初始化操作
void initializeGL();
//绘制函数
void paintGL();
protected:
//窗口尺寸变化的事件
void resizeGL(int w, int h);
};
#endif // SIMPLEGLWINDOW_H
//simpleglwindow.cpp
#include "simpleglwindow.h"
#include <QDebug>
SimpleGLWindow::SimpleGLWindow(QWindow *parent) :
QOpenGLWindow(NoPartialUpdate, parent)
{}
void SimpleGLWindow::initializeGL()
{
//初始化OpenGL函数
if (!initializeOpenGLFunctions())
{
qDebug() << "init opengl functions failed";
}
//设置刷新显示的时候的默认颜色为RGB(255,255,255)
glClearColor(1, 1, 1, 0);
}
void SimpleGLWindow::paintGL()
{
//清空颜色缓存
glClear(GL_COLOR_BUFFER_BIT);
//将OpenGL视口和窗口保持相同
glViewport(0, 0, width(), height());
//启动三角形绘制模式
//这里的点坐标不是绝对数值,而是到边界的比例,取值范围为(-1,1)
//数值1对应着有边界,-1对应着左边界
//颜色数值也是比例值,1对应着RGB中的255,0对应着RGB中的0
glBegin(GL_TRIANGLES);
{
//顶点1坐标XYZ(0,1,0)颜色RGB(255,0,0)
glColor3f(1, 0, 0);
glVertex3f( 0.0f, 1.0f, 0.0f);
//顶点2坐标XYZ(1,-1,0)颜色RGB(0,255,0)
glColor3f(0, 1, 0);
glVertex3f( 1.0f,-1.0f, 0.0f);
//顶点3坐标XYZ(-1,-1,0)颜色RGB(0,0,255)
glColor3f(0, 0, 1);
glVertex3f(-1.0f,-1.0f, 0.0f);
}
//结束绘制
glEnd();
}
void SimpleGLWindow::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
}
显示效果如下所示:
抗锯齿配置
默认情况下,OpenGL的绘制锯齿效果非常明显。我们可以通过开启多采样配置让OpenGL以更高的图像质量来进行绘制,从而实现抗锯齿效果,对应的配置如下所示:
SimpleGLWindow::SimpleGLWindow(QWindow *parent) :
QOpenGLWindow(NoPartialUpdate, parent)
{
//设置多采样的值
QSurfaceFormat fmt = format();
fmt.setSamples(18);
setFormat(fmt);
}
理论上讲多采样的值越高,图像质量越好。但是多采样的值越高对硬件性能带来的负担也越大。如果值超过了硬件的阈值,程序还会运行失败。所以开发者应该根据自己的硬件平台性能选定对应的值,不能盲目的选定一个很大的数值。
多采样绘制效果如下所示:
绘制点、线、四边形、多边形
下面介绍一下其它的一些图元在OpenGL中的绘制方法
绘制单个点
void SimpleGLWindow::paintGL()
{
//清空颜色缓存
glClear(GL_COLOR_BUFFER_BIT);
//将OpenGL视口和窗口保持相同
glViewport(0, 0, width(), height());
//为了显示清楚修改点的默认大小
glPointSize(10.0f);
//启动点绘制模式
glBegin(GL_POINTS);
{
//顶点坐标XYZ(0,0,0)颜色RGB(0,0,255)
//显示在屏幕中央的蓝色点
glColor3f(0, 0, 1);
glVertex3f( 0.0f, 0.0f, 0.0f);
}
//结束绘制
glEnd();
}
显示效果如下:
绘制多个点
void SimpleGLWindow::paintGL()
{
//清空颜色缓存
glClear(GL_COLOR_BUFFER_BIT);
//将OpenGL视口和窗口保持相同
glViewport(0, 0, width(), height());
//为了显示清楚修改点的默认大小
glPointSize(10.0f);
//启动点绘制模式
glBegin(GL_POINTS);
{
//顶点坐标XYZ(0,0,0)颜色RGB(0,0,255)
glColor3f(0, 0, 1);
glVertex3f( 0.0f, 0.0f, 0.0f);
//顶点坐标XYZ(0.5,0,0)颜色RGB(255,0,0)
glColor3f(1, 0, 0);
glVertex3f( 0.5f, 0.0f, 0.0f);
//顶点坐标XYZ(0,0.5,0)颜色RGB(0,255,0)
glColor3f(0, 1, 0);
glVertex3f( 0.0f, 0.5f, 0.0f);
//顶点坐标XYZ(-0.5,0,0)颜色RGB(0,255,255)
glColor3f(0, 1, 1);
glVertex3f( -0.5f, 0.0f, 0.0f);
}
//结束绘制
glEnd();
}
显示效果如下所示:
绘制直线
void SimpleGLWindow::paintGL()
{
//清空颜色缓存
glClear(GL_COLOR_BUFFER_BIT);
//将OpenGL视口和窗口保持相同
glViewport(0, 0, width(), height());
//启动直线绘制模式
glBegin(GL_LINES);
{
//直线1的起点和终点
//起点坐标XYZ(1,0,0)颜色RGB(255,0,0)
glColor3f(1, 0, 0);
glVertex3f( 1.0f, 0.0f, 0.0f);
//终点坐标XYZ(-1,0,0)颜色RGB(0,255,0)
glColor3f(0, 1, 0);
glVertex3f( -1.0f, 0.0f, 0.0f);
//直线2的起点和终点
//起点坐标XYZ(0,-1,0)颜色RGB(255,0,0)
glColor3f(1, 0, 0);
glVertex3f( 0.0f, -1.0f, 0.0f);
//终点坐标XYZ(0,1,0)颜色RGB(0,255,0)
glColor3f(0, 1, 0);
glVertex3f( 0.0f, 1.0f, 0.0f);
}
//结束绘制
glEnd();
}
显示效果如下所示:
绘制四边形
在绘制四边形的时候一定要注意,绘制点在程序中的先后顺序就是绘制的先后顺序。相同的四个点,如果绘制的先后顺序不同出现的四边形也可能不同。所以一定要注意点的绘制先后顺序。
void SimpleGLWindow::paintGL()
{
//清空颜色缓存
glClear(GL_COLOR_BUFFER_BIT);
//将OpenGL视口和窗口保持相同
glViewport(0, 0, width(), height());
//启动四边形绘制模式
glBegin(GL_QUADS);
{
//顶点1坐标XYZ(1,0,0)颜色RGB(255,0,0)
glColor3f(1, 0, 0);
glVertex3f( 1.0f, 0.0f, 0.0f);
//顶点2坐标XYZ(0,1,0)颜色RGB(0,255,0)
glColor3f(0, 1, 0);
glVertex3f( 0.0f, 1.0f, 0.0f);
//顶点3坐标XYZ(-1,0,0)颜色RGB(0,255,0)
glColor3f(0, 1, 0);
glVertex3f( -1.0f, 0.0f, 0.0f);
//顶点4标XYZ(0,-1,0)颜色RGB(255,0,0)
glColor3f(1, 0, 0);
glVertex3f( 0.0f, -1.0f, 0.0f);
}
//结束绘制
glEnd();
}
显示效果如下图所示:
绘制多边形
多边形绘制的时候也要注意点绘制的先后顺序,要不然对结果影响很大。
void SimpleGLWindow::paintGL()
{
//清空颜色缓存
glClear(GL_COLOR_BUFFER_BIT);
//将OpenGL视口和窗口保持相同
glViewport(0, 0, width(), height());
//启动多边形绘制模式
glBegin(GL_POLYGON);
{
//顶点1坐标XYZ(-0.5,0,0)
//颜色为RGB(255,0,0)
glColor3f(1, 0, 0);
glVertex3f( -0.5f, 0.0f, 0.0f);
//顶点2坐标XYZ(-0.25,0.5,0)
glVertex3f( -0.25f, 0.5f, 0.0f);
//顶点3坐标XYZ(0.25,0.5,0)
glVertex3f( 0.25f, 0.5f, 0.0f);
//顶点4标XYZ(0.5,0,0)
glVertex3f( 0.5f, 0.0f, 0.0f);
//顶点5标XYZ(0.25,-0.5,0)
glVertex3f( 0.25f, -0.5f, 0.0f);
//顶点6标XYZ(-0.25,-0.5,0)
glVertex3f( -0.25f, -0.5f, 0.0f);
}
//结束绘制
glEnd();
}
显示效果如下图所示:
绘制图片纹理
图片纹理就是将一个图片按照一定的尺寸对应关系贴到某个特定的图形上面。这里我们将一个图片贴到一个矩形图元上面。在绘制纹理之前,我们需要先对纹理进行初始化操作,对应的实现如下:
void SimpleGLWindow::initialTexture()
{
//初始化图片和纹理
m_texture_image = new QImage(":/background.jpg");
//OpenGL坐标系的y轴和图片坐标系的y轴方向相反,需要镜像操作让图片摆正
m_texture = new QOpenGLTexture(m_texture_image->mirrored());
//添加放大和缩小的时候的滤波器
m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
}
添加完成初始化操作之后,我们再在OpenGL的初始化函数里面调用一下纹理的初始化操作。
void SimpleGLWindow::initializeGL()
{
//初始化OpenGL函数
if (!initializeOpenGLFunctions())
{
qDebug() << "init opengl functions failed";
}
//初始化纹理
initialTexture();
//设置刷新显示的时候的默认颜色为RGB(255,255,255)
glClearColor(1, 1, 1, 0);
}
完成纹理的初始化操作之后,我们就可以在对应的函数中绘制纹理图片了。绘制操作如下所示:
void SimpleGLWindow::paintGL()
{
//清空颜色缓存
glClear(GL_COLOR_BUFFER_BIT);
//将OpenGL视口和窗口保持相同
glViewport(0, 0, width(), height());
//重置变换矩阵
glLoadIdentity();
//绑定纹理
if(m_texture)
{
m_texture->bind();
}
//启动纹理
glEnable(GL_TEXTURE_2D);
//启动矩形绘制模式
glBegin(GL_QUADS);
{
//矩形的四个顶点分别对应图片的四个顶点
//顶点1坐标XYZ(1,1,0)纹理坐标(1,1)
glTexCoord2d(1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, 0.0f);
//顶点2坐标XYZ(-1,1,0)纹理坐标(0,1)
glTexCoord2d(0.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
//顶点3坐标XYZ(-1,-1,0)纹理坐标(0,0)
glTexCoord2d(0.0f, 0.0f);
glVertex3f( -1.0f, -1.0f, 0.0f);
//顶点4标XYZ(1,-1,0)纹理坐标(1,0)
glTexCoord2d(1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
}
//结束绘制
glEnd();
//关闭纹理
glDisable(GL_TEXTURE_2D);
}
绘制的矩形的四个顶点分别对应着图片纹理的四个顶点,所以纹理可以完整的平铺整个四边形,对应的显示效果如下图所示:
纹理绘制的完整代码如下所示:
//simpleglwindow.h
#ifndef SIMPLEGLWINDOW_H
#define SIMPLEGLWINDOW_H
#include <QOpenGLWindow>
#include <QOpenGLFunctions_1_1>
#include <QOpenGLTexture>
//使用的OpenGLFunction的版本是1.1
class SimpleGLWindow : public QOpenGLWindow, protected QOpenGLFunctions_1_1
{
public:
SimpleGLWindow(QWindow *parent = 0);
protected:
//初始化操作
void initializeGL();
//绘制函数
void paintGL();
protected:
//窗口尺寸变化的事件
void resizeGL(int w, int h);
private:
void initialTexture();
//OpenGL纹理
QOpenGLTexture* m_texture = nullptr;
//纹理图片
QImage* m_texture_image = nullptr;
};
#endif // SIMPLEGLWINDOW_H
//simpleglwindow.cpp
#include "simpleglwindow.h"
#include <QDebug>
SimpleGLWindow::SimpleGLWindow(QWindow *parent) :
QOpenGLWindow(NoPartialUpdate, parent)
{
//设置多采样的值
QSurfaceFormat fmt = format();
fmt.setSamples(25);
setFormat(fmt);
}
void SimpleGLWindow::initializeGL()
{
//初始化OpenGL函数
if (!initializeOpenGLFunctions())
{
qDebug() << "init opengl functions failed";
}
initialTexture();
//设置刷新显示的时候的默认颜色为RGB(255,255,255)
glClearColor(1, 1, 1, 0);
}
void SimpleGLWindow::paintGL()
{
//清空颜色缓存
glClear(GL_COLOR_BUFFER_BIT);
//将OpenGL视口和窗口保持相同
glViewport(0, 0, width(), height());
//重置变换矩阵
glLoadIdentity();
if(m_texture)
{
m_texture->bind();
}
//启动纹理
glEnable(GL_TEXTURE_2D);
//启动矩形绘制模式
glBegin(GL_QUADS);
{
//顶点1坐标XYZ(1,1,0)纹理坐标(1,1)
glTexCoord2d(1.0f, 1.0f);
glVertex3f( 1.0f, 1.0f, 0.0f);
//顶点2坐标XYZ(-1,1,0)纹理坐标(0,1)
glTexCoord2d(0.0f, 1.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
//顶点3坐标XYZ(-1,-1,0)纹理坐标(0,0)
glTexCoord2d(0.0f, 0.0f);
glVertex3f( -1.0f, -1.0f, 0.0f);
//顶点4标XYZ(1,0,0)纹理坐标(1,0)
glTexCoord2d(1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
}
//结束绘制
glEnd();
//关闭纹理
glDisable(GL_TEXTURE_2D);
}
void SimpleGLWindow::resizeGL(int w, int h)
{
Q_UNUSED(w);
Q_UNUSED(h);
}
void SimpleGLWindow::initialTexture()
{
//初始化图片和纹理
m_texture_image = new QImage(":/background.jpg");
m_texture = new QOpenGLTexture(m_texture_image->mirrored());
//添加放大和缩小的时候的滤波器
m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
}