QtQuick中集成OSG渲染

从目前网上介绍OSG与Qml集成有两种方式:

  1. QtQuick2OSGItem通过继承抽象类QQuickFramebufferObject::Renderer来重写渲染接口,在QQuickFramebufferObject渲染成Frame Buffer Object(FBO)在目标Item显示
  2. 继承QQuickItem,Osg先初始化,渲染成FBO,通过接口updatePaintNode()来更新FBO数据.

第一种方法实现起来比较优雅方便,但是不支持多pass渲染,本文主要介绍第二种。在本文用EventAdapter继承QQuickItem作为连接QtQuick与Osg的一个基类,后面只要继承这个类并注册到qml就可以了。

  • QQuickItem继承自QObject和QQmlParserStatus,前者是Qt的基类,可以说在Qt中玩物起源于QObject,但这个类和Osg没有直接的关系,而QQmlParserStatus描述的是在Qml中的状态关系,所以从QQuickItem需要一个实体来描述这个窗体,从QQuickItem的接口看来,这个窗体的创建就是QQuickWindow,所以我们可以通过QQuickWindow将Osg创建出来的FrameBuffer绑定到QQuickWindow上,在QQuickItem的信号中,我们可以通过windowChanged()来判断窗体的产生,从而初始化OpenGL的上下文(也即初始化osg)
  • 要向Osg传递鼠标和键盘之间,需要重写QQuickItem的鼠标接口和按键接口,这个我们在处理特定的鼠标或者按键事件的原理是一样的
  • 在本文中将fbo渲染到textureNode中,所以在重载接口updatePaintNode()需要判断old是否更新,如果更新,将器删除。
  • 如果窗口大小发生了改变,要在geometryChanged()去更新fbo
  • 利用QQuickWindow在每一帧渲染前(beforeRendering()),渲染每一帧的fbo

最后看代码

#pragma once

#include <QtQuick/QQuickItem>


#include <QtQuick/QQuickWindow>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtQuick/QSGSimpleTextureNode>
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtQuick/QSGSimpleTextureNode>
#include <QtQuick/QQuickWindow>
#include <QtQuick/QQuickItem>

#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLFunctions>

#include <osgViewer/CompositeViewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgGA/TrackballManipulator>
#include <osgDB/ReadFile>

class  EventAdapter : public QQuickItem
{
    Q_OBJECT
public:
    explicit EventAdapter(QQuickItem *parent = nullptr);
    osgViewer::Viewer* getViewer();
public slots:

protected:
    void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void wheelEvent(QWheelEvent *event);
    void keyPressEvent(QKeyEvent *event);
    QSGNode* updatePaintNode(QSGNode *oldNode,
                             UpdatePaintNodeData *updatePaintNodeData);

public:
    inline int mouseButton(QMouseEvent *event)
    {
        int button = 0;
        switch (event->button())
        {
            case Qt::LeftButton: button = 1; break;
            case Qt::MidButton: button = 2; break;
            case Qt::RightButton: button = 3; break;
            case Qt::NoButton: button = 0; break;
            default: button = 0; break;
        }
        return button;
    }

public:
    osg::ref_ptr<osgViewer::Viewer> viewer;
    QOpenGLFramebufferObject *fbo;
    QSGTexture *texture;
    QSGSimpleTextureNode *textureNode;
public slots:
    void updateViewport();
private slots:
    void onWindowChanged(QQuickWindow *window);
    void frame();

private:
    void initOSG();
    void initFBO();
    void updateFBO();
};
#include "EventAdapter.h"
#include<osgGA/StateSetManipulator>

EventAdapter::EventAdapter(QQuickItem *parent) :
    QQuickItem(parent),
    fbo(nullptr),
    texture(nullptr),
    textureNode(nullptr)
{
    initOSG();
    connect(this, SIGNAL(windowChanged(QQuickWindow*)),
            this, SLOT(onWindowChanged(QQuickWindow*)));

    setAcceptHoverEvents(true);
    setAcceptedMouseButtons(Qt::AllButtons);
}

void EventAdapter::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    if (window())
    {
        updateViewport();
    }

    QQuickItem::geometryChanged(newGeometry, oldGeometry);
}

void EventAdapter::mousePressEvent(QMouseEvent *event)
{
    int button = mouseButton(event);
    viewer->getEventQueue()->mouseButtonPress(event->x(), event->y(), button);
}

void EventAdapter::mouseMoveEvent(QMouseEvent *event)
{
    viewer->getEventQueue()->mouseMotion(event->x(), event->y());
}

void EventAdapter::mouseReleaseEvent(QMouseEvent *event)
{
    int button = mouseButton(event);
    viewer->getEventQueue()->mouseButtonRelease(event->x(), event->y(), button);
}

void EventAdapter::keyPressEvent(QKeyEvent *event)
{
    viewer->getEventQueue()->keyPress(static_cast<int>(*(event->text().toLatin1().data())));
}

void EventAdapter::wheelEvent(QWheelEvent *event)
{
    if (event->delta() > 0)
        viewer->getEventQueue()->mouseScroll(osgGA::GUIEventAdapter::SCROLL_UP);
    else
        viewer->getEventQueue()->mouseScroll(osgGA::GUIEventAdapter::SCROLL_DOWN);

}

QSGNode *EventAdapter::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
{
    if (oldNode && oldNode != textureNode) {
        delete oldNode;
    }
    Q_UNUSED(updatePaintNodeData)
    return textureNode;
}

void EventAdapter::updateViewport()
{
    if (!this->window())
        return;

    QSize size(this->boundingRect().size().toSize());
    viewer->getCamera()->getGraphicsContext()->resizedImplementation(0, 0, size.width(), size.height());
    osgGA::GUIEventAdapter *ea = viewer->getEventQueue()->getCurrentEventState();
    ea->setXmin(0);
    ea->setXmax(size.width());
    ea->setYmin(0);
    ea->setYmax(size.height());
    viewer->getCamera()->setViewport(0, 0, size.width(), size.height());
    viewer->getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast<double>(size.width()) / static_cast<double>(size.height()), 0.1f, 10000.0f);
    //              view->getCamera()->getGraphicsContext()->getState()->setUseModelViewAndProjectionUniforms(true);
    //              view->getCamera()->getGraphicsContext()->getState()->setUseVertexAttributeAliasing(true);
    if (texture && texture->textureSize() != size) {
        updateFBO();
    }

}


void EventAdapter::initOSG() {
    viewer = new osgViewer::Viewer();
    viewer->addEventHandler( new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()) );
    viewer->addEventHandler(new osgViewer::StatsHandler);
    //viewer->addEventHandler(new osgViewer::WindowSizeHandler);
    //viewer->getEventQueue()->getCurrentEventState()
}

void EventAdapter::initFBO() {
    QRectF rect = this->boundingRect();
    QOpenGLFramebufferObjectFormat format;
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    QSize size(rect.size().toSize());
    fbo = new QOpenGLFramebufferObject(size, format);
    texture = this->window()->createTextureFromId(fbo->texture(), size);
    textureNode = new QSGSimpleTextureNode();
    textureNode->setRect(0, this->height(), this->width(), -height());
    textureNode->setTexture(texture);
    this->setFlag(QQuickItem::ItemHasContents, true);
    updateViewport();
    this->update();
}

void EventAdapter::updateFBO() {
    if (fbo)
        delete fbo;
    QRectF rect = this->mapRectToItem(nullptr, boundingRect());
    QOpenGLFramebufferObjectFormat format;
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    QSize size(rect.size().toSize());
    fbo = new QOpenGLFramebufferObject(size, format);
    if (texture)
        delete texture;
    texture = window()->createTextureFromId(fbo->texture(), size);
    textureNode = new QSGSimpleTextureNode();
    textureNode->setRect(0, height(), width(), -this->height());
    textureNode->setTexture(texture);
    this->update();
}

void EventAdapter::onWindowChanged(QQuickWindow *window)
{
    if (!window)
    {
        return;
    }
    //std::cout << "onWindowChanged" << std::endl;
    connect(window, SIGNAL(beforeRendering()),
            this, SLOT(frame()), Qt::DirectConnection);
    window->setClearBeforeRendering(false);

    osg::ref_ptr<osgViewer::GraphicsWindowEmbedded> graphicsWindow
        = new osgViewer::GraphicsWindowEmbedded(0, 0, window->width(), window->height());
    graphicsWindow->setClearColor(osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f));
    viewer->getCamera()->setGraphicsContext(graphicsWindow);
    updateViewport();
    connect(window, SIGNAL(widthChanged(int)),
            this, SLOT(updateViewport()));
    connect(window, SIGNAL(heightChanged(int)),
            this, SLOT(updateViewport()));
}

void EventAdapter::frame()
{
    window()->update();
    if(!fbo)
        initFBO();
    if(fbo)
    {
        fbo->bind();
    }
    QOpenGLContext::currentContext()->functions()->glUseProgram(0);
    viewer->frame();
}

osgViewer::Viewer* EventAdapter::getViewer()
{
    return viewer.get();
}

注:

  • 由于Qml默认QSG_RENDER_LOOP是threaded,所以需要在环境变量设置QSG_RENDER_LOOP为basic或者window,要不然会有崩溃现象,关于QtQuick的渲染机制,具体可以参考QtQuick基础教程(四)---场景渲染(Scene Graph)

  • 需要将EventAdapter或者其子类注册到Qml中

qmlRegisterType<SceneItem>("Hohai.Controls", 1, 0, "EventAdapter");
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335