Qt Quick OpenGL学习笔记:通过FBO帧缓冲绘制一个三角

本文深入探讨QtQuick与OpenGL结合的实践,重点介绍如何利用QQuickFramebufferObject(FBOItem)在QtQuick应用中进行OpenGL渲染。文章详细解释了FBOItem及其Renderer子类的使用方法,包括着色器程序的编写、顶点数据的设置及FBO的创建过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0.前言

Qt Creator 上提供了一些在 Qt Quick 程序中使用 OpenGL 的示例,本文主要参考示例 "fboitem"。搜索示例 "fbo",有两个 QQuickFramebufferObject 相关的 demo,演示了如何运用 Qt 帧缓冲对象渲染 QML 小部件。

 

(之前学习这个 demo 时很多东西还没理解,现在把代码重新整理了下)

1.知识点

(主要翻译自 Qt 文档)

QQuickFramebufferObject 继承自 QQuickItem,该类型使得我们可以在帧缓冲对象(FBO)上渲染我们的小部件,Qt Quick SceneGraph 框架会将这个 FBO 渲染到屏幕上。要注意的是,该类仅在 Qt Quick 通过 OpenGL 渲染时才起作用。可以把 QQuickFramebufferObject 类当作是 OpenGL 版的 QQuickPaintedItem,其使用 QPainter 绘制,也是 QQuickItem 的子类。

在大多数平台上,渲染将在专用线程上进行。因此,QQuickFramebufferObject 类在 QML Item 实现和 FBO 呈现之间强制执行严格的分离。QML 所需的所有 Item 逻辑,例如属性和与 UI 相关的辅助函数,都应该位于 QQuickFramebufferObject 类的子类中。与渲染相关的所有内容都必须位于QQuickFramebufferObject::Renderer 类中。

为了避免 Item 和 Render 两个线程的读写竞争,应避免使用共享变量。Item 和 Render 之间的通信应该通过 QQuickFramebufferObject::renderer::synchronize() 函数进行。当 GUI 线程被阻塞时,将在渲染线程上调用此函数。或者使用队列连接的信号槽或者事件。

Renderer 和 FBO 对象都是 Qt 内部管理内存,不用我们主动释放。

要渲染到 FBO 中,我们应该继承 Renderer 类,并重新实现 Renderer::render() 函数(相当于 QOpenGLWidget 的 paintGL() 函数)。Renderer 子类实例是从 createRenderer() 返回的。

从 Qt5.4 开始,QQuickFramebufferObject 类是一个纹理提供者(texture provider),可以直接用于 ShaderEffects 和其他使用纹理提供者(texture provider)的类。

这里之前还遇到个问题,创建的 QOpenGLFramebufferObject 帧缓冲对象附加了深度缓冲,但是 render 的时候没清除深度缓冲,导致顶点没有被渲染出来。

2.代码实现

GitHub项目链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20200128_FBO

(注意:我使用的Qt版本为5.12.6或5.15.2,编译器 MSVC2019 )

主要代码:

QQuickFramebufferObject 子类

#pragma ocne

#include <QtQuick/QQuickFramebufferObject>

//QML UI 相关逻辑放到 QQuickFramebufferObject 子类
//渲染相关放到 QQuickFramebufferObject::Renderer 子类
//该类仅在 Qt Quick 通过 OpenGL 渲染时才起作用
class FBOItem : public QQuickFramebufferObject
{
    Q_OBJECT
public:
    FBOItem(QQuickItem *parent = nullptr);

    //Renderer 实例是从 createRenderer() 返回的
    QQuickFramebufferObject::Renderer *createRenderer() const override;
};
#include "FBOItem.h"

#include "FBORenderer.h"
#include "FBORenderer2.h"

FBOItem::FBOItem(QQuickItem *parent)
    : QQuickFramebufferObject(parent)
{
    //上下翻转,这样就和OpenGL的坐标系一致了
    setMirrorVertically(true);
}

QQuickFramebufferObject::Renderer *FBOItem::createRenderer() const
{
    //Renderer 和 FBO 都是内部管理的内存
    return new FBORenderer();
}

 QQuickFramebufferObject::Renderer 子类

#pragma once

#include <QtQuick/QQuickFramebufferObject>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLShaderProgram>
//#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLFunctions_3_3_Core>

//渲染相关放到 QQuickFramebufferObject::Renderer 子类
class FBORenderer : public QQuickFramebufferObject::Renderer,
        protected QOpenGLFunctions_3_3_Core
{
public:
    FBORenderer();
    //要渲染到 FBO,需要继承 Renderer 类并重新实现其 render() 函数
    void render() override;
    //创建新的 FBO 时调用,如 resize 时
    QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override;

private:
    //着色器程序
    QOpenGLShaderProgram program;
};
#include "FBORenderer.h"

#include <QDebug>

FBORenderer::FBORenderer()
{
    //为当前上下文初始化OpenGL函数解析
    initializeOpenGLFunctions();

    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    const char *vertex_str=R"(#version 330 core
                           layout (location = 0) in vec3 vertices;
                           void main() {
                           gl_Position = vec4(vertices,1.0);
                           })";
    const char *fragment_str=R"(#version 330 core
                             uniform vec3 myColor;
                             void main() {
                             gl_FragColor = vec4(myColor,1.0);
                             })";

    //将source编译为指定类型的着色器,并添加到此着色器程序
    if(!program.addCacheableShaderFromSourceCode(
                QOpenGLShader::Vertex,vertex_str)){
        qDebug()<<"compiler vertex error";
        exit(0);
    }
    if(!program.addCacheableShaderFromSourceCode(
                QOpenGLShader::Fragment,fragment_str)){
        qDebug()<<"compiler fragment error";
        exit(0);
    }
    //使用addShader()将添加到该程序的着色器链接在一起。
    program.link();

    //将属性名称绑定到指定位置(这里location=0)
    program.bindAttributeLocation("vertices", 0);
}

void FBORenderer::render()
{
    glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
    //create FBO 时附加了深度缓冲
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //将此着色器程序绑定到活动的QOpenGLContext,并使其成为当前的着色器程序
    //同于调用glUseProgram(programid)
    program.bind();
    //传递值
    program.setUniformValue("myColor", QVector3D(0,1,0));

    //Qt默认是和OpenGL里颠倒过来的,上负下正,
    //但是可以在QQuickFramebufferObject设置setMirrorVertically(true);
    float vertices[] = {
        0.5f, -0.5f, 0.0f,  // bottom right
        -0.5f,-0.5f, 0.0f,  // bottom left
        0.0f,  0.5f, 0.0f   // top
    };
    //在此着色器程序中的位置处启用顶点数组,
    //以便着色器程序将使用在位置上由setAttributeArray()设置的值。
    program.enableAttributeArray(0);
    //给对应位置设置顶点数组
    program.setAttributeArray(0, GL_FLOAT, vertices, 3);
    //从数组数据渲染图元(render primitives from array data)
    glDrawArrays(GL_TRIANGLES, 0, 3);
    program.disableAttributeArray(0);

    //从当前QOpenGLContext释放活动的着色器程序
    //相当于调用glUseProgram(0)
    program.release();
}

QOpenGLFramebufferObject *FBORenderer::createFramebufferObject(const QSize &size)
{
    QOpenGLFramebufferObjectFormat format;
    //attach后需要开启GL_DEPTH_TEST
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    format.setSamples(4);
    return new QOpenGLFramebufferObject(size, format);
}

3.参考

Qt示例:路径如 E:\Qt\Qt5.12.6\Examples\Qt-5.12.6\quick\scenegraph\textureinsgnode\textureinsgnode.pro,或QtCreater示例搜索 "FBO"

Qt文档:https://doc.qt.io/qt-5/qquickframebufferobject.html

LearnOpenGL三角:你好,三角形 - LearnOpenGL CN

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值