如何在qml中使用opengl接口进行渲染

在QWidget中,可以使用QOpenglWidget调用opengl接口进行渲染,因为QWidget大部分控件都是依赖于平台的(cpu绘制),所以我们调用opengl的接口时不需要考虑opengl的context共享,所以相对简单。但是qml的渲染策略有所不同,在可以使用硬件加速的环境下,qml是把当前所有可视元素都“堆放”在一起,然后统一使用gpu渲染。所以如果我们想要在qml中使用opengl接口进行绘制,需要考虑调用的时机以及不同窗口之间资源的共享,下面提供一种方法。

想要在qml中使用,我们需要继承QQuickItem,现在我们定义一个MyCube 用来绘制一个立方体。

class CubeRender;
class MyCube : public QQuickItem
{
    Q_OBJECT
public:
    explicit MyCube(QQuickItem* parent = nullptr);

protected slots:
    void sync();

    void cleanUp();

private:
    CubeRender* m_render{nullptr};
signals:

};

其中CubeRender 是对opengl接口的一个封装,稍后再解释。先看看quick的渲染流程:

如何在qml中使用opengl接口进行渲染_第1张图片

这张图截取自官方文档,从中可以很清楚的看到,GUI线程调用updatePolish之后,会把qml的视图元素传入渲染线程,在数据传递过程中,GUI线程会被lock,当传递完毕,渲染线程会发射beforeRendering信号,解锁GUI线程,然后GUI线程去做别的工作,渲染线程则进行渲染工作。

一般opengl不支持跨线程调用,所以我们想创建opengl资源和使用相关的api都必须在渲染线程中用同步的方式调用。

所以,我们需要在渲染线程发出beforeSynchronizing的信号时创建我们的opengl资源,在渲染线程发出beforeRendering的时候调用绘制接口,需要注意的是,由于渲染线程和gui线程并非一个线程,所以在连接信号槽的时候,我们需要使用Qt::DirectConnection来强制同步。

下面我们来看一下中CubeRender:

class CubeRender: protected QOpenGLFunctions_4_5_Core
{
public:
   CubeRender();

  void initializeGL();

  void resizeGl(int w, int h);

  void render();

protected:


  QOpenGLShaderProgram m_shaderProgram;
};

我这边是仿造QOpengWidget的形式对opengl接口进行了封装,主要有三个接口:

void initializeGL(); 用来初始化opengl资源,包括创建VBO,VAO以及链接shader。

void resizeGl(int w, int h);当窗口大小发生改变的时候调用,用来修改视口的大小以及重新生成相关的视图矩阵。

void render(); 进行绘制。值得注意的是,由于qml大部分控件都是调用opengl绘制的,而且opengl本质是一个状态机,所以在绘制前需要对VBO这些对象重新进行bind。

(可以参考opengl相关教程来理解这些操作。)


下面就比较简单了,我们在收到beforeSynchronizing信号的时候初始化opengl资源。

 connect(this,&QQuickItem::windowChanged,this,[this](QQuickWindow* window){
       if(window)
       {
           connect(window,&QQuickWindow::beforeSynchronizing,this,&MyCube::sync,
Qt::DirectConnection);

       }
   },Qt::DirectConnection);


void MyCube::sync()
{
   if(!m_render)
   {
       m_render = new CubeRender;
       m_render->initializeGL();
       m_render->resizeGl(width(),height());
}

在收到beforeRendering的时候进行绘制:

       connect(window(), &QQuickWindow::beforeRendering, this, [this]()
       {
           m_render->render();
       }, Qt::DirectConnection);

当窗口大小变化的重新调整视口大小:

           connect(this, &QQuickItem::widthChanged, this, [this]()
       {
           m_render->resizeGl(this->width(), this->height());
       });
       connect(this, &QQuickItem::heightChanged, this, [this]()
       {
           m_render->resizeGl(this->width(), this->height());
       });

接下来把MyCube注册成QML元素就可以使用了。

qmlRegisterType("cube.utility", 1, 0, "MyCube");

此外,还有一种方法是继承QQuickFramebufferObject,来进行opengl渲染。

你可能感兴趣的:(QT学习之路,Opengl,qt,c++)