Qt5.15.2 QML 自定义openGL的QuickItem初步认识
A.渲染过程
官方说明渲染过程->戳
常用的少了一个sceneGraphInvalidated -> 用于清空
常用信号及回调函数
QQuickWindow::beforeSynchronizing
->sync
:在同步场景图状态之前进行 OpenGL 资源的初始化或更新。QQuickWindow::sceneGraphInvalidated
->cleanup
:在场景图无效时清理 OpenGL 资源。QQuickWindow::beforeRendering
->render
:在开始渲染之前执行自定义的 OpenGL 绘制。QQuickWindow::frameSwapped
->onFrameSwapped
:在交换帧缓冲区后准备下一帧的内容。QQuickWindow::openglContextCreated
->onOpenGLContextCreated
:在 OpenGL 上下文创建后进行相关的初始化工作。
回调方式必须是直连 Qt::DirectConnection,因为openGL 本身就是状态机,所以必须直连处理。
信号及其作用和示例
1. QQuickWindow::beforeSynchronizing
-> 回调函数 sync
作用:在同步场景图状态之前发出,用于进行OpenGL资源的初始化或更新。
示例:
void Squircle::handleWindowChanged(QQuickWindow *win)
{
if (win) {
connect(win, &QQuickWindow::beforeSynchronizing, this, &Squircle::sync, Qt::DirectConnection);
connect(win, &QQuickWindow::sceneGraphInvalidated, this, &Squircle::cleanup, Qt::DirectConnection);
connect(win, &QQuickWindow::beforeRendering, this, &Squircle::render, Qt::DirectConnection);
connect(win, &QQuickWindow::frameSwapped, this, &Squircle::onFrameSwapped, Qt::DirectConnection);
connect(win, &QQuickWindow::openglContextCreated, this, &Squircle::onOpenGLContextCreated, Qt::DirectConnection);
win->setColor(Qt::black);
}
}
void Squircle::sync() {
if (!m_initialized) {
initializeOpenGLFunctions();
// 初始化 OpenGL 资源,如 VBO、VAO、着色器等
m_initialized = true;
}
// 更新需要同步的状态
}
2. QQuickWindow::sceneGraphInvalidated
-> 回调函数 cleanup
作用:在场景图无效时发出,用于清理 OpenGL 资源。
示例:
void Squircle::cleanup() {
// 清理 OpenGL 资源,如 VBO、VAO、着色器等
if (m_vbo) {
delete m_vbo;
m_vbo = nullptr;
}
if (m_vao) {
delete m_vao;
m_vao = nullptr;
}
if (m_shaderProgram) {
delete m_shaderProgram;
m_shaderProgram = nullptr;
}
}
3. QQuickWindow::beforeRendering
-> 回调函数 render
作用:在开始渲染之前发出,用于执行自定义的 OpenGL 绘制。
示例:
void Squircle::render() {
if (!m_initialized) return;
// 自定义 OpenGL 绘制
glBindVertexArray(m_vao);
glUseProgram(m_shaderProgram->programId());
// 设置着色器的统一变量
m_shaderProgram->setUniformValue("u_color", QColor(Qt::red));
// 绘制操作
glDrawArrays(GL_TRIANGLES, 0, 3);
}
4. QQuickWindow::frameSwapped
-> 回调函数 onFrameSwapped
作用:在交换帧缓冲区后发出,用于准备下一帧的内容。
示例:
void Squircle::onFrameSwapped() {
// 帧交换完成后的操作,通常用于准备下一帧的内容
// 例如,更新动画状态,处理用户输入等
qDebug() << "Frame swapped!";
}
5. QQuickWindow::openglContextCreated
-> 回调函数 onOpenGLContextCreated
作用:在 OpenGL 上下文创建后发出,用于进行 OpenGL 相关的初始化工作。
示例:
void Squircle::onOpenGLContextCreated(QOpenGLContext *context) {
// OpenGL 上下文创建后的初始化操作
initializeOpenGLResources(context);
qDebug() << "OpenGL context created!";
}
void Squircle::initializeOpenGLResources(QOpenGLContext *context) {
context->makeCurrent(window());
initializeOpenGLFunctions();
// 初始化 OpenGL 资源
context->doneCurrent();
}
B.不同流程的渲染控制方式 QtQuick
0. QQuickPaintedItem
QQuickPaintedItem
是一种快速实现 OpenGL 渲染的方式。通过继承 QQuickPaintedItem
并设置渲染方式为 OpenGL,可以快速创建一个 OpenGL 控件。
使用方法:
- 继承
QQuickPaintedItem
。 - 重写
paint()
方法,在其中进行 OpenGL 渲染。
优点:
- 快捷方便:最简单的 OpenGL 控件实现方式。
- 快速集成:适合快速开发和测试。
缺点:
- 性能较低:适合简单的渲染,不适用于复杂或高性能要求的场景。
1. QQuickFramebufferObject (FBO)
QQuickFramebufferObject
提供了一种在 QML 中嵌入自定义 OpenGL 渲染的简便方式。通过继承 QQuickFramebufferObject
并重载 Renderer
类的 render()
方法,可以实现自定义的 OpenGL 渲染。
控制过程:
- 继承
QQuickFramebufferObject
并重载createRenderer()
方法,返回一个自定义的Renderer
实例。 - 在
Renderer
类中实现render()
方法,进行 OpenGL 渲染。
优点:
- 易于使用:封装了 OpenGL 的复杂细节,提供简洁的接口。
- 直接集成:可以直接在 QML 中使用,适合快速开发。
- 自动资源管理:自动管理 OpenGL 资源的创建和销毁。
- 跨平台一致性:在不同平台上行为一致。
缺点:
- 灵活性较低:相比直接使用 OpenGL,灵活性稍低。
- 性能开销:封装带来一些性能开销,不适用于极高性能要求的场景。
2. QSGNode
通过继承 QQuickItem
并重载 updatePaintNode()
方法,可以直接操作 Qt Quick 的场景图。通过自定义 QSGNode
子类,可以实现更复杂的渲染逻辑。
控制过程:
- 继承
QQuickItem
并重载updatePaintNode(QSGNode *, UpdatePaintNodeData *)
方法。 - 自定义
QSGNode
子类,实现节点的渲染逻辑。
优点:
- 高性能:利用 Qt Quick 的场景图优化,适合高性能渲染。
- 细粒度控制:对渲染过程有更细粒度的控制,适合复杂渲染效果。
- 增量更新:可以单独更新某些节点,节省资源。
- 与 QML 无缝集成:适合在 QML 环境中使用,并能很好地结合其他 QML 元素。
缺点:
- 复杂性较高:需要深入了解 Qt Quick 场景图的工作机制。
- 手动资源管理:需要手动管理 OpenGL 资源,容易出错。
3. QOpenGLFunctions
直接使用 QOpenGLFunctions
提供了对 OpenGL 的完全控制。通过继承 QOpenGLFunctions
并在渲染过程中调用 OpenGL 函数,可以实现任何自定义的 OpenGL 渲染逻辑。
控制过程:
- 继承
QOpenGLFunctions
。 - 在渲染过程中调用 OpenGL 函数,实现自定义渲染逻辑。
优点:
- 最大灵活性:完全控制渲染过程,适合复杂和定制化的渲染效果。
- 高性能潜力:优化得当,能够实现最高性能。
- 无缝集成:可以与现有的 OpenGL 代码库和工具无缝集成。
缺点:
- 复杂性最高:需要手动管理所有 OpenGL 资源,学习曲线陡峭。
- 易出错:直接操作 OpenGL,容易发生资源泄漏和其他错误。
- 集成复杂:与 Qt Quick 的集成需要更多工作以确保一致性和性能。
代码示例
0. QQuickPaintedItem
#include <QQuickPaintedItem>
#include <QPainter>
class MyQuickPaintedItem : public QQuickPaintedItem
{
Q_OBJECT
public:
MyQuickPaintedItem(QQuickItem *parent = nullptr) : QQuickPaintedItem(parent) {}
void paint(QPainter *painter) override
{
// 在这里进行OpenGL渲染
}
};
1. QQuickFramebufferObject【推荐】
#include <QQuickFramebufferObject>
class MyFramebufferObjectRenderer : public QQuickFramebufferObject::Renderer
{
public:
void render() override
{
// 在这里进行OpenGL渲染
}
};
class MyFramebufferObject : public QQuickFramebufferObject
{
Q_OBJECT
public:
QQuickFramebufferObject::Renderer *createRenderer() const override
{
return new MyFramebufferObjectRenderer();
}
};
2. QSGNode【推荐】
#include <QQuickItem>
#include <QSGNode>
class MyQuickItem : public QQuickItem
{
Q_OBJECT
public:
MyQuickItem(QQuickItem *parent = nullptr) : QQuickItem(parent) {}
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override
{
// 在这里进行场景图节点的更新和渲染
return oldNode;
}
};
3. QOpenGLFunctions
#include <QOpenGLFunctions>
#include <QQuickItem>
class MyOpenGLFunctions : public QQuickItem, protected QOpenGLFunctions
{
Q_OBJECT
public:
MyOpenGLFunctions(QQuickItem *parent = nullptr) : QQuickItem(parent)
{
initializeOpenGLFunctions();
}
void paint()
{
// 在这里进行OpenGL渲染
}
};
4. QOpenGLWindow
QOpenGLWindow
是一种用于 OpenGL 渲染的窗口,它继承自 QWindow
并提供了 OpenGL 渲染的接口。
使用方法:
- 继承
QOpenGLWindow
。 - 重写
initializeGL()
、resizeGL()
和paintGL()
方法。
5. QOpenGLWidget
QOpenGLWidget
是 Qt 中用于集成 OpenGL 渲染的一个常用控件,它提供了一个方便的接口来进行 OpenGL 渲染。
使用方法:
- 继承
QOpenGLWidget
。 - 重写
initializeGL()
、resizeGL()
和paintGL()
方法。
PS:
单纯使用QQuickItem+QOpenGLFunctions,QQuickWindow::beforeRendering绑定回调的方式
即,使用opengl原生的方式和回调的方式,有一个问题是只能在qml的最上面或者最下面。不推荐使用!!!
3、图形渲染管线
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
VBO 在GPU上创建的内存大小及地址
1、顶点着色器
2、片段着色器
3、创建着色器程序
4、链接
5、启用
6、属性设置
4、投射矩阵
4.1 正交投影 - 2D
4.2 透视投影 - 3D
Model View Projection - MVP矩阵
Model
View
Projection
5、摄像头
6、绘制基本方法
7、绘制圆
在片段着色器绘制圆,效率更高
uniform highp vec2 u_resolution;
varying lowp vec4 color;
void main() {
// 将像素坐标转换为标准化坐标 (0,1)
vec2 st = gl_FragCoord.xy / u_resolution;
// 初始化背景颜色为透明
vec4 backgroundColor = vec4(0.0, 0.0, 0.0, 0.0);
// 计算当前像素与圆心的距离
float distanceFromCenter = distance(st, vec2(0.5, 0.5));
// 使用 smoothstep 函数来实现平滑的边缘
float circle = smoothstep(0.3, 0.305, distanceFromCenter);
// 计算圆内的颜色,红色
vec3 circleColor = vec3(1.0, 1.0, 0.0);
// 混合圆的颜色和背景颜色
vec3 color = mix(circleColor, backgroundColor.rgb, circle);
// 设置片段颜色,调整透明度
gl_FragColor = vec4(color, 1.0 - circle);
}
8、example
- 绘制三角形
- 绘制图片
- 绘制矩形、三角形、圆点、图片
- 鼠标点击创建一个圆点
- 鼠标点击绘制多边形
- 鼠标控制旋转立方体
- 加载3D物体
- 光照 - 100个立方体,可鼠标点击转换视角
- 光照 - 土星环
- 上帝视角 - 小镇风光
摄像头
gluLookAt,gluPerspective和glOrtho
gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar);
观察的视景体在世界坐标系中的具体大小,一般而言,其中的参数aspect应该与窗口的宽高比大小相同。
fovy,视野角度,用过照相机的话就很好理解了,数值越小,相当于将镜头拉得越近,数值越大,镜头越广,镜头里的东西就越小。
aspect,这个好理解,就是实际窗口的纵横比,即x/y。
zNear,这个呢,表示你近处,的裁面。
zFar表示远处的裁面。
glLoadIdentity();
这个函数类似于一个复位操作:
1.X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。
2.OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。
3.中心左面的坐标值是负值,右面是正值。
移向屏幕顶端是正值,移向屏幕底端是负值。
移入屏幕深处是负值,移出屏幕则是正值。
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
这个函数设置清除屏幕时所用的颜色,色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。最大值也是1.0f,代表特定颜色分量的最亮情况。最后一个参数是Alpha值。
glTranslatef(x, y, z)沿着 X, Y 和 Z 轴移动。根据前面的次序,下面的代码沿着X轴左移1.5个单位,Y轴不动(0.0f),最后移入屏幕6.0f个单位。注意在glTranslatef(x, y, z)中当您移动的时候,您并不是相对屏幕中心移动,而是相对与当前所在的屏幕位置。
glVertex *
glVertex3f 就是确定顶点的函数,三个参数是点 的空间坐标。
void perspective(float verticalAngle, float aspectRatio, float nearPlane, float farPlane);
投影变换和视景体
http://t.csdnimg.cn/Js7Hf
Qt6 有更好的QRHI ,但是目前不成熟,可以切换渲染平台,包括opengl,dirext,vulkan等。
Qt5 的openGL ES用在ios,有版权风险。用在机器人好像也有风险,dll链接也是。
- 绘制圆点的两种方法
https://stackoverflow.com/questions/4197062/using-the-following-function-that-draws-a-filled-circle-in-opengl-how-do-i-make