在 QML 上使用 Q/C++ OpenGL 渲染界面

Qt Quick OpenGL

在 Qt 中使用 OpenGL API 渲染 FrameBufferObject,放到 QML 之上展示。

QQuickFramebufferObject

在大多数的平台上,渲染会使用专用的线程。出于这个原因,QQuickFramebuffer 在 item 的实现和 FBO 渲染之间执行强制的分离。所有的 item 逻辑,比如 QML 所需的属性和 UI 相关的辅助函数都在 QQuickFramebufferObject 类及其子类之中。所有与渲染相关的都必须被定位于 QQuickFramebufferObject::Renderer 类。

避免两个线程之间的竞争冲突和读写问题的重点是渲染和 item 实现从不读写共享的变量。二者之间的交流应当首要通过 QQuickFramebufferObject::Renderer::synchronize()函数,当 GUI 线程阻塞时,渲染线程会调用这个函数。
使用信号和槽的队列链接或者事件触发的方式也可以进行两个线程之间的交流。
Renderer 和 FBO 的内存由内部独立管理。

如下图提示,所有和 UI 相关的内容都在 QQuickFramebufferObject 实现,而所有渲染以及 OpenGL 相关的工作都在 QQuickFramebufferObject::Renderer 实现。

实现方法

根据 Qt 官方的提供的 example,做了最小版本的 QQuickFramebufferObject 实现方式。分为三个类,它们之间的关系如图所示。用户的 UserRenderer 中会调用 QOpenGLFunctions 提供的 OpenGL API,做一些 OpenGL 的渲染工作,包括初始化和着色器以及渲染。该类会被 QQuickFramebufferObject::Renderer实现并调用,担任 QQuickFramebufferObject 的渲染。在 QQuickFramebufferObject 子类中进行 Qt 和 QML 的界面逻辑。

之后在 QML 中调用👇

import QtQuick
import qquickfbo

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    UserFramebufferObject {
        anchors.fill: parent
    }
}

官网对 QQuickFramebufferObject 的实现说明👇

为了渲染 FBO,用户可以继承 Renderer 类并重载 Renderer::render()函数,Renderer 的子类通过 createRenderer() 获得。
FBO 的尺寸默认自适应 item 的大小,配置 textureFollowItemSizefalse,并通过 QQuickFramebufferObject::Renderer::createFreambufferObject() 返回一个纹理。
自 Qt 5.4 开始,QQuickFramebufferObject 具有提供纹理的功能,并且能够直接被用于 ShaderEffects 和其他需要 texture provider 的类。

完整的带有片段着色器的 QQuickFramebufferObject 示例代码 myfbo.h
平台:Ubuntu22,Qt 6.5

#ifndef MYFBO_H
#define MYFBO_H

#include <qopenglfunctions.h>
#include <qopenglshaderprogram.h>
#include <QQuickFramebufferObject>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFramebufferObjectFormat>
#include <qsgsimpletexturenode.h>


class UserRenderer : protected QOpenGLFunctions
{
public:
    UserRenderer() {

    }
    UserRenderer(const UserRenderer& ) = delete;
    const UserRenderer operator=(const UserRenderer&) = delete;

    void render() {
        glDepthMask(true);

        glClearColor(0.0f, 0.0f, 0.0f ,1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        shaderprogram.bind();
        vertexAttr = shaderprogram.attributeLocation("vertex");
        shaderprogram.enableAttributeArray(vertexAttr);
        shaderprogram.setAttributeArray(vertexAttr, vertices.constData());
        glDrawArrays(GL_POINTS, 0, vertices.size());
        shaderprogram.disableAttributeArray(vertexAttr);

        shaderprogram.release();

        glDisable(GL_DEPTH_TEST);

    }
    void initialize() {
        initializeOpenGLFunctions();

        glClearColor(0.1f, 0.1f, 0.2f, 1.0f);

        const char* vscr =
            "#version 330\n"
            "in vec4 vertex;\n"
            "void main(void)\n"
            "{\n"
            "   gl_Position = vertex;\n"
            "}\n";
        const char* gscr =
            "#version 330\n"
            "layout (points) in;\n"
            "layout (triangle_strip, max_vertices = 3) out;\n"
            "void main(void)\n"
            "{\n"
            "   gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);\n"
            "   EmitVertex();\n"
            "   gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);\n"
            "   EmitVertex();\n"
            "   gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.3, 0.0, 0.0);\n"
            "   EmitVertex();\n"
            "   EndPrimitive();\n"
            "}\n";
        const char* fscr =
            "#version 330\n"
            "out highp vec4 FragColor;\n"
            "void main(void)\n"
            "{\n"
            "    FragColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
            "}\n";

        shaderprogram.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vscr);
        shaderprogram.addCacheableShaderFromSourceCode(QOpenGLShader::Geometry, gscr);
        shaderprogram.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fscr);
        shaderprogram.link();
    }
private:
    QOpenGLShaderProgram shaderprogram;
    int vertexAttr;
    int normalAttr;
    int matrixUniform;

    QList<QVector3D> vertices{
        QVector3D(-0.5f,  0.5f, 0.0f), // top-left
        QVector3D(0.5f,  0.5f, 0.0f),  // top-right
        QVector3D(0.5f, -0.8f, 0.0f),  // bottom-right
    };
};


class UserFboRenderer : public QQuickFramebufferObject::Renderer
{
public:
    UserFboRenderer() {
        glrenderer.initialize();
    }
    UserFboRenderer(const UserFboRenderer&) = delete;
    const UserFboRenderer operator=(const UserFboRenderer&) = delete;


    void render() override {
        glrenderer.render();
        update();
    }

    QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override {
        QOpenGLFramebufferObjectFormat format;
        format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
        format.setSamples(4);
        return new QOpenGLFramebufferObject(size, format);
    }

    QQuickFramebufferObject::Renderer* createRenderer() const {
        return new UserFboRenderer();
    }
private:
    UserRenderer glrenderer;
};

class UserFramebufferObject : public QQuickFramebufferObject
{
    Q_OBJECT
    QML_ELEMENT
public:
    UserFramebufferObject() {

    }
    UserFramebufferObject(const UserFramebufferObject *) = delete;
    const UserFramebufferObject operator=(const UserFramebufferObject&) = delete;

    Renderer *createRenderer() const override {
        renderer = new UserFboRenderer();
        return renderer;
    }
private:
    mutable UserFboRenderer* renderer = nullptr;
};



#endif // MYFBO_H

posted @ 2024-11-21 21:27  KKKKevin  阅读(1108)  评论(0)    收藏  举报