3. Qt OpenGL¶
OpenGL(Open Graphics Library,开源图形库)是用于渲染2D和3D计算机图形的广泛应用的行业标准。 它是Mac OS X、Linux和大多数嵌入式平台上硬件加速图形操作的标准。
OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。 至于内部具体每个函数是如何实现的,将由编写OpenGL库的人自行决定,实际通常是GPU的生产商。
3.1. 鲁班猫RK系列板卡¶
鲁班猫RK系列的lubancatZ/W和lubancat1/2板卡,是使用瑞芯微的rk356x处理器,3D图像渲染的支持:
lubancat-4板卡,是使用瑞芯微的rk3588S处理器,3D图像渲染的支持:
rk356x处理器GPU是Mail-G52 GPU,rk3588X处理器GPU是Mali-G610,都支持OpenGL ES 1.1,2.0和3.2。
OpenGL ES(OpenGL for Embedded Systems)是OpenGL规范的一种形式,适用于嵌入式设备。 OpenGL ES 1.x版本、2.x 版本和 3.x 版本均可提供高性能图形界面,用于创建3D、可视化图表和界面等。
其中1.x版本支持固定管线(立即渲染模式)等,2.x版本支持可编程管线等,3.x支持mrt、纹理压缩、compute shader等, 2.x和3.x的图形编程基本相似,不同之处在于版本3.x表示2.x与其他功能的超集。
下面测试主要使用OpenGL ES 3.2(或者使用OpenGL ES 2.0)。
3.2. Qt中OpenGL相关类¶
Qt GUI 核心模块集成了OpenGL与OpenGL ES接口。
3.2.1. QWindow¶
Qt GUI中最重要的类是QGuiApplication和 QWindow ,Qt应用程序将需要利用这两个类将内容显示在屏幕上。 其中 QWindow 类表示在底层窗口系统的窗口,它提供了许多虚拟功能来处理来自窗口系统的事件(QEvent), 例如触摸输入,曝光,焦点,按键和几何形状更改。
3.2.2. QGLWidget¶
QGLWidget 类是用于渲染OpenGL图形的窗口, 提供了显示集成到Qt应用程序中的OpenGL图形的功能。使用方式是通过子类继承,就像其他任何QWidget一样,可以选择使用QPainter或者标准OpenGL渲染命令。
注意:这个类是传统QtOpenGL模块的一部分,与其他Qt OpenGL类一样,应该在新的应用程序中避免使用。从Qt5.4开始,Qt推荐使用QOpenGLWidget和QOpenGL类。
3.2.3. QOpenGLWidget¶
QOpenGLWidget 是Qt库提供的一个类,提供了显示集成到Qt应用程序中的OpenGL图形的功能。
它是QWidget类的子类,提供了基本的显示OpenGL图形功能和一些额外的功能, 比如用于与OpenGL上下文交互,例如基于事件的机制处理键盘和鼠标输入,以及在多个窗口部件之间共享OpenGL资源的能力。
使用QOpenGLWidget的主要优点是它允许您轻松地将OpenGL图形集成到基于Qt的应用程序中,并提供了一种简单一致的方式来处理OpenGL上下文和事件。 QOpenGLWidget是为了更现代,更高效地替代QGLWidget而设计的,并且推荐用于新的开发。它支持例如使用现代OpenGL核心配置文件和在多线程中使用OpenGL上下文等功能。
QOpenGLWidget的使用非常简单:创建子类类继承它,并像任何其他QWidget一样使用子类,可以在使用QPainter和标准OpenGL渲染命令之间进行选择。
QOpenGLWidget提供了三个方便的虚拟函数,可以方便的在子类中重新实现这些函数来执行OpenGL:
1、initializeGL()执行OpenGL资源初始化,在第一次调用resizeGL()或paintGL()之前调用一次;
2、resizeGL()用于设置转换矩阵和其他依赖于窗口大小的资源等,每当调整Widget的大小时调用(或者窗口自动获得调整大小事件而首次显示时);
3、paintGL()渲染OpenGL场景使用QPaint绘制,更新窗口时就会调用。
新的应用中,一般建议使用QOpenGLWidge。
3.2.4. QOpenGLWindow¶
QOpenGLWindow 是Qt中用来提供更底层的OpenGL渲染支持的类, 使用与QOpenGLWidget兼容的API轻松创建执行OpenGL渲染的窗口,并且类似于传统的QGLWidget, 与QOpenGLWidget不同,QOpenGLWindow不依赖于widgets模块,并且提供了更好的性能。
QOpenGLWindow是 QWindow 的子类,扩展了 QWindow的功能,提供了基本的OpenGL渲染功能。
QOpenGLWindow提供了一组虚拟函数,用于在窗口渲染时调用,如initializeGL()和paintGL();可以在这些函数中使用OpenGL函数来绘制图形; 也提供了一些额外的功能,用于在多线程中使用OpenGL上下文,并支持使用现代OpenGL特性。
和QOpenGLWidget使用方式一样,继承QOpenGLWindow,并重新实现以下虚拟功能:
1、initializeGL()执行OpenGL资源初始化;
2、resizeGL()来设置转换矩阵和其他依赖于窗口大小的资源;
3、paintGL()发出OpenGL命令或使用QPainter绘制;
更多描述参考下https://doc.qt.io/qt-5/qopenglwindow.html。
3.2.5. QOpenGLFunctions¶
QOpenGLFunctions 类提供对 OpenGL ES 2.0 API的跨平台访问。
使用QOpenGLFunctions的推荐方法是直接继承,同时在初始化函数中void initializeGL() 调用此接口initializeOpenGLFunctions() 进行初始化。
3.2.6. QOpenGLExtraFunctions¶
QOpenGLExtraFunctions 类提供对OpenGL ES 3.0、3.1和3.2 API的跨平台访问,继承自QOpenGLFunctions。
还有更多的相关类参考下:https://doc.qt.io/qt-5/qtgui-index.html#opengl-and-opengl-es-integration
3.3. QT中使用OpenGL¶
Qt对OpenGL有很好的支持,无论固定管线版本,还是可编程管线版本。 可以使用适用于Qt自身的QOpenGLShader与QOpenGLShaderProgram等类,或者使用忠实于原头文件glfw.h的QOpenGLFunction_x_x_Core类。
QOpenGLFunction_x_x_Core类提供了对应OpenGL版本和配置文件的所有函数包装。例如QOpenGLFunctions_3_2_Core, 提供OpenGL 3.2核心配置文件的所有功能。
3.3.1. 创建一个窗口¶
下面我们将在Qt中简单使用OpenGL ES,并在鲁班猫板卡上上运行, 先创建一个工程,基于QMainWindow,工程名称为MyTriangle。 然后往该项目中添加一个Triangle类,继承于QOpenGLWidget和QOpenGLFunctions:
1 2 3 4 5 6 7 8 9 10 11 12 | class Triangle : public QOpenGLWidget, protected QOpenGLFunctions
{
public:
explicit Triangle(QWidget *parent = nullptr);
~Triangle();
protected:
// 继承QOpenGLWidget后重写这三个虚函数
virtual void initializeGL() override;
virtual void resizeGL(int w, int h) override;
virtual void paintGL() override;
};
|
在Triangle类中,将重新实现initializeGL()、paintGL()和resizeGL()函数,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include "triangle.h"
Triangle::Triangle(QWidget *parent) : QOpenGLWidget(parent)
{
}
Triangle::~Triangle(){
}
void Triangle::initializeGL()
{
// 初始化OpenGL函数,如果继承QOpenGlFunctions,必须使用这个初始化函数
initializeOpenGLFunctions();
}
void Triangle::resizeGL(int w, int h)
{
// OpenGL渲染窗口的尺寸大小,glViewport可以设置位置和宽高
glViewport(0, 0, w, h);
}
void Triangle::paintGL()
{
// 设置清屏颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// 清空屏幕的颜色缓冲区,填充glClearColor设置的颜色
glClear(GL_COLOR_BUFFER_BIT);
}
|
Qt Designer中,在主窗口添加一个OOpenGL Widget窗口,右击窗口该窗口,点击 提升为...
,
添加前面的Triangle类,提升为Triangle:
在MainWindow.cpp中将新创建的openGLWidget设置为centralWidget,编译,然后复制可执行程序到鲁班猫板运行(或者远程连接部署):
接下来我们将绘制一个三角形,这需要很多了解很多OpenGL概念以及图形学相关知识,可以参考 https://learnopengl-cn.github.io
3.3.2. 绘制一个三角形¶
在OpenGL中,可以使用三维空间中的坐标定义绘制的对象,要绘制一个三角形,必须先定义其坐标,OpenGL中一般是定义浮点数的顶点数组坐标,然后存储在一个缓冲区中。
OpenGL中所有的事物都是在3D空间中,但屏幕和窗口时2D像素数组,这导致OpenGL的大部分工作是把3D坐标转变为适应你屏幕的2D像素, 而3D坐标转为2D坐标的处理过程是由OpenGL的 图形渲染管线 (Graphics Pipeline)管理。
GPU渲染管线部分多个阶段,可以参考下 这里。
图形渲染管线的各个阶段分为可编程的模块,可配置的部分,固定的部分。 在现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点/片段着色器)。
顶点着色器 主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
片段着色器 的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。 通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
示例程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | static const char *vertexShaderSource =
"#version 320 es\n"
"layout (location = 0) in vec4 posAttr;\n"
"layout (location = 1) in vec3 colAttr;\n"
"uniform mat4 matrix;\n"
"out lowp vec3 col;\n"
"void main() {\n"
" col = colAttr;\n"
" gl_Position = matrix * posAttr;\n"
"}\n";
static const char *fragmentShaderSource =
"#version 320 es\n"
"in highp vec3 col;\n"
"out highp vec4 FragColor;\n"
"void main() {\n"
" FragColor = vec4(col, 1.0);\n"
"}\n";
void Triangle::initializeGL()
{
// 初始化OpenGL函数,如果继承QOpenGlFunctions,必须使用这个初始化函数
initializeOpenGLFunctions();
//着色器
m_program = new QOpenGLShaderProgram(this);
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
//链接
if(!m_program->link()){
qDebug() << "link failed!\n";
}
// 使用attributeLocation函数来获取xxx属性的位置
m_posAttr = m_program->attributeLocation("posAttr"); //顶点属性
m_colAttr = m_program->attributeLocation("colAttr"); //颜色属性
m_matrixUniform = m_program->uniformLocation("matrix"); //全局变量
m_program->bind();
//三个顶点
static const GLfloat vertices[] = {
0.0f, 0.707f,
-0.5f, -0.5f,
0.5f, -0.5f
};
//颜色,在OpenGL或GLSL中强制归一化到[0.0,1.0]之间的
static const GLfloat colors[] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
};
//链接顶点、颜色属性,告诉OpenGL该如何解析顶点数据,颜色属性
glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors);
//启用顶点、颜色属性
glEnableVertexAttribArray(m_posAttr);
glEnableVertexAttribArray(m_colAttr);
}
void Triangle::resizeGL(int w, int h)
{
// OpenGL渲染窗口的尺寸大小,glViewport可以设置位置和宽高
// glViewport(0, 0, w, h);
Q_UNUSED(w);
Q_UNUSED(h);
const qreal retinaScale = devicePixelRatio();
glViewport(0, 0, width() * retinaScale, height() * retinaScale); //视口的宽度和高度将根据设备的像素比例进行缩放
}
void Triangle::paintGL()
{
// 设置清屏颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// 清空屏幕的颜色缓冲区,填充glClearColor设置的颜色
glClear(GL_COLOR_BUFFER_BIT);
m_program->bind();
QMatrix4x4 matrix; //4x4的单位矩阵
matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f); //透视矩阵变换
matrix.translate(0, 0, -2); //平移变换
matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0); //旋转
m_program->setUniformValue(m_matrixUniform, matrix);
glDrawArrays(GL_TRIANGLES, 0, 3); //传递图元,绘制三角
//关闭启用顶点、颜色数组等
//......
++m_frame;
}
|
整体的程序如上,我们创建的Triangle类是继承QOpenGLFunctions,使用OpenGL ES 2.0 API,例程中使用Qt的封装的OpenGL接口(例程主函数中设置使用3.2版本,向后兼容2.0)。
我们重新实现initializeGL虚函数,OpenGL资源和状态。 先initializeOpenGLFunctions()函数为当前上下文初始化opengl函数解析,然后创建着色器对象(一个片段着色器和一个顶点着色器), 着色器是着色器语言GLSL(OpenGL Shading Language)编写,并暂时以字符串的形式进行保存:
1 2 3 4 5 6 7 8 9 10 | // 顶点着色器
#version 320 es //版本号
layout (location = 0) in vec4 posAttr; //输入变量名为posAttr,位置属性0
layout (location = 1) in vec3 colAttr; //输入变量名为colAttr,颜色属性,位置属性1
uniform mat4 matrix; //uniform 全局变量,一个四维矩阵,用来变换
out lowp vec3 col;
void main() {
col = colAttr; //传递颜色属性
gl_Position = matrix * posAttr; //gl_Position内置关键字,为顶点着色器的输出的顶点位置数据
};
|
1 2 3 4 5 6 7 | // 片段着色器
#version 320 es //版本号
in highp vec3 col; //输入变量,从顶点着色器传来的输入变量
out highp vec4 FragColor;
void main() {
FragColor = vec4(col, 1.0); //输出变量
};
|
片段着色器需要生成一个最终输出的颜色,可以通过从一个着色器向另一个着色器发送数据,这必须在发送方着色器中声明一个输出(即前面顶点着色器的col变量), 在接收方着色器中声明一个类似的输入(即前面片段着色器的输入变量col)。当类型和名字都一样的时候, OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。
在resizeGL()中,设置OpenGL渲染窗口的尺寸大小,即视口(Viewport)的大小。
最后实现旋转效果,需要添加一个QTimer,定时更新窗口:
1 2 3 4 5 6 7 8 9 10 11 | Triangle::Triangle(QWidget *parent) : QOpenGLWidget(parent)
{
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &Triangle::animation);
timer->start(20);
}
void Triangle::animation()
{
update();
}
|
编译,然后在鲁班猫上测试,就会绘制一个彩色三角形,并且在绕y轴旋转:
以上就是一个简单的示例,更多的参考下Qt帮助手册和OpenGL API参考手册。