如何以编程方式在QML中渲染VTK项目? [英] How to render programmatically a vtk item in qml?

查看:708
本文介绍了如何以编程方式在QML中渲染VTK项目?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

到目前为止,我了解到我们在QML中有两个线程,即主应用程序线程和场景图"线程:

So far, I understand that we have two threads in QML, our main application thread, and our "scene graph" thread : http://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html

借助此链接,我已经实现了自己的vtkQmlItem:

I've implemented my own vtkQmlItem with the help of this link : http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html

并且我注意到我的vtkscene仅在qml流发出afterrendering信号时渲染.

and I've noticed that my vtkscene is only rendered when the afterrendering signal is emitted by the qml flow.

到目前为止,一切正常,并且运行良好,我可以看到我的vtk场景,甚至可以与它进行交互.

So far, everything is ok and works perfectly, I can see my vtk scene and can even interract with it.

但是我也想以编程方式渲染我的vtk场景,因为我想通过在vtk对象周围移动相机来制作动画.

But I would like to also programmatically render my vtk scene as well, since I want to do an animation by moving the camera around a vtk object.

直接调用renderer->render()会显示很多vtk错误,而且似乎不是执行此操作的好方法.

Calling renderer->render() directly shows a lot of vtk error, and does not seem to be the good way to do this.

当我希望立即处理该事件时,调用this->window()->update()似乎会将事件放入eventLoop中.我设法使其立即生效的唯一方法是使用QApplication :: processEvents(),这是我不喜欢的hack,并且会喜欢其他解决方案.

Calling this->window()->update() seems to put the event in the eventLoop, when I want it to be handled instantly. The only way I've managed to make it work instantly is by using QApplication::processEvents(), which is a hack I don't like and would love another solution.

因此,我不喜欢的工作解决方案的伪代码如下:

So the pseudocode of the working solution that I don't like is the following :

for (int i = 0; i < 50; i++)
{
   ChangeCameraPosition(i); // Change the position and orientation of the vtk camera
   this->window()->update();
   QApplication::processEvents(); // The hack I don't like
   QThread::msleep(500);
}

推荐答案

问题实际上有点复杂,如果过去几个月中没有发生任何变化,则VTK中仍不支持QtQuick,这意味着没有几行代码解决方案.您可以在VTK/GUISupport/QtOpenGL/中找到QtWidgets的支持类,并将它们用作导出对qml的支持的模板.但主要是,我建议您检查此主题中有关此主题的讨论.

the problem is actually a bit complicated and if nothing changed in the past few months, there is still no support for QtQuick in VTK, which means no simple few lines solution. You can find support classes for QtWidgets in VTK/GUISupport/QtOpenGL/ and use them as a template to derive support for qml. But mainly I recommend checking this thread for a discussion about this topic.

关键点是QtQuick在专用线程中保留了您要渲染到的qml窗口的openGL上下文,并且不会让其他任何东西获取该上下文.因此,为了从VTK渲染到其中,您必须在该线程中进行处理.这意味着:

The key point is that QtQuick holds the openGL context for the qml window you are trying to render into in a dedicated thread and it won't let anything else get that context. So in order to render into it from VTK, you have to do it within that thread. This means:

1)创建一个自己的vtkRenderWindow,该vtkRenderWindow覆盖Render()方法,以确保它发生在qml的渲染线程中.

1) Create your own vtkRenderWindow that overrides the Render() method such that it is ensured it happens in the qml's render thread.

2)使该渲染窗口渲染到由qtquick提供的帧缓冲对象(QQuickFramebufferObject的实例)中.

2) Make that render window render into a framebuffer object provided by the qtquick (instance of QQuickFramebufferObject).

3)将vtk的渲染信号与qt的渲染方法互连->例如当vtk渲染窗口调用makeCurrent时,qt的渲染线程醒来".

3) Interconnect vtk's rendering signals with the qt's rendering methods -> e.g. when the vtk render window calls makeCurrent, the qt's rendering thread "wakes up".

这是我基于上面链接的Taylor Braun-Jones模板的实现.它可能并不完美,但是对我有用(我已经删除了一些特定于我的应用程序的部分,因此它可能不会立即编译,但应该使您可以找到一些可行的解决方案):

Here is my implementation based on Taylor Braun-Jones' template linked above. It might not be perfect, but it works for me (I have removed some parts specific to my app so it might not compile straight away, but it should put you on a path to some working solution):

qmlVtk.h:

#include <vtkEventQtSlotConnect.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkRenderer.h>

#include <QtQuick/QQuickFramebufferObject>
// Use the OpenGL API abstraction from Qt instead of from VTK because vtkgl.h
// and other Qt OpenGL-related headers do not play nice when included in the
// same compilation unit
#include <QOpenGLFunctions>

#include <qqmlapplicationengine.h>

class QVTKFramebufferObjectRenderer;
class QVTKInteractorAdapter;
class vtkInternalOpenGLRenderWindow;
class QVTKFramebufferObjectRenderer;


class QVTKFrameBufferObjectItem : public QQuickFramebufferObject
{
    Q_OBJECT

public:
    QVTKFrameBufferObjectItem(QQuickItem *parent = 0);
    ~QVTKFrameBufferObjectItem();
    Renderer *createRenderer() const;
    vtkSmartPointer<vtkInternalOpenGLRenderWindow> GetRenderWindow() const;

protected:
    // Called once before the FBO is created for the first time. This method is
    // called from render thread while the GUI thread is blocked.
    virtual void init();

    vtkSmartPointer<vtkInternalOpenGLRenderWindow> m_win;
    QVTKInteractorAdapter* m_irenAdapter;
    vtkSmartPointer<vtkEventQtSlotConnect> mConnect;

    friend class QVTKFramebufferObjectRenderer;

    // Convert the position of the event from openGL coordinate to native coordinate
    QMouseEvent openGLToNative(QMouseEvent const& event);

    virtual void mouseMoveEvent(QMouseEvent * event);
    virtual void mousePressEvent(QMouseEvent * event);
    virtual void mouseReleaseEvent(QMouseEvent * event);
    virtual void mouseDoubleClickEvent(QMouseEvent * event);
    virtual void wheelEvent(QWheelEvent *event);
    virtual void keyPressEvent(QKeyEvent* event);
    virtual void keyReleaseEvent(QKeyEvent* event);
    virtual void focusInEvent(QFocusEvent * event);
    virtual void focusOutEvent(QFocusEvent * event);


    protected Q_SLOTS:
    // slot to make this vtk render window current
    virtual void MakeCurrent();
    // slot called when vtk wants to know if the context is current
    virtual void IsCurrent(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data);
    // slot called when vtk wants to start the render
    virtual void Start();
    // slot called when vtk wants to end the render
    virtual void End();
    // slot called when vtk wants to know if a window is direct
    virtual void IsDirect(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data);
    // slot called when vtk wants to know if a window supports OpenGL
    virtual void SupportsOpenGL(vtkObject* caller, unsigned long vtk_event, void* client_data, void* call_data);
};

/// <summary>
/// An extension of vktGenericOpenGLRenderWindow to work with Qt.
/// Serves to write VTK-generated render calls to a framebuffer provided and maintained by Qt. It is meant to be used within Qt render loop, i.e. using Qt's render thread.
/// </summary>
/// <seealso cref="vtkGenericOpenGLRenderWindow" />
/// <seealso cref="QOpenGLFunctions" />
class vtkInternalOpenGLRenderWindow : public vtkGenericOpenGLRenderWindow, protected QOpenGLFunctions
{

public:
    static vtkInternalOpenGLRenderWindow* New();
    vtkTypeMacro(vtkInternalOpenGLRenderWindow, vtkGenericOpenGLRenderWindow)

    virtual void OpenGLInitState();

    // Override to use deferred rendering - Tell the QSG that we need to
    // be rendered which will then, at the appropriate time, call
    // InternalRender to do the actual OpenGL rendering.
    virtual void Render();

    // Do the actual OpenGL rendering
    void InternalRender();

    // Provides a convenient way to set the protected FBO ivars from an existing
    // FBO that was created and owned by Qt's FBO abstraction class
    // QOpenGLFramebufferObject
    void SetFramebufferObject(QOpenGLFramebufferObject *fbo);

    QVTKFramebufferObjectRenderer *QtParentRenderer;

protected:
    vtkInternalOpenGLRenderWindow(); 

    ~vtkInternalOpenGLRenderWindow()
    {
        // Prevent superclass destructors from destroying the framebuffer object.
        // QOpenGLFramebufferObject owns the FBO and manages it's lifecyle.
        this->OffScreenRendering = 0;
    }
};

qmlVtk.cpp:

qmlVtk.cpp:

#include "QVTKFramebufferObjectItem.h"

#include <QQuickFramebufferObject>
#include <QQuickWindow>
#include <QOpenGLFramebufferObject>
#include <QVTKInteractorAdapter.h>

#include <vtkRenderWindowInteractor.h>
#include <vtkObjectFactory.h>

#include <vtkSmartPointer.h>
#include <vtkCamera.h>
#include <vtkProperty.h>

#include <qglfunctions.h>


class QVTKFramebufferObjectRenderer : public QQuickFramebufferObject::Renderer
{
    friend class vtkInternalOpenGLRenderWindow;

public:
    QVTKFramebufferObjectRenderer(vtkSmartPointer<vtkInternalOpenGLRenderWindow> rw) :
        m_framebufferObject(0)
    {
        m_vtkRenderWindow = rw;

        m_vtkRenderWindow->QtParentRenderer = this;
    }

    ~QVTKFramebufferObjectRenderer()
    {
        m_vtkRenderWindow->QtParentRenderer = 0;
        glFrontFace(GL_CCW); // restore default settings
    }

    virtual void synchronize(QQuickFramebufferObject * item)
    {
        // the first synchronize call - right before the the framebufferObject
        // is created for the first time
        if (!m_framebufferObject)
        {
            QVTKFrameBufferObjectItem *vtkItem = static_cast<QVTKFrameBufferObjectItem*>(item);
            vtkItem->init();
        }
    }

    virtual void render()
    {
        m_vtkRenderWindow->InternalRender(); // vtkXOpenGLRenderWindow renders the scene to the FBO
    }

    QOpenGLFramebufferObject *createFramebufferObject(const QSize &size)
    {
        QOpenGLFramebufferObjectFormat format;
        format.setAttachment(QOpenGLFramebufferObject::Depth);
        m_framebufferObject = new QOpenGLFramebufferObject(size, format);

        m_vtkRenderWindow->SetFramebufferObject(m_framebufferObject);

        return m_framebufferObject;
    }

    vtkSmartPointer<vtkInternalOpenGLRenderWindow> m_vtkRenderWindow;
    QOpenGLFramebufferObject *m_framebufferObject;
};

vtkStandardNewMacro(vtkInternalOpenGLRenderWindow);

vtkInternalOpenGLRenderWindow::vtkInternalOpenGLRenderWindow() :
QtParentRenderer(0)
{
    vtkOpenGLRenderWindow::OpenGLInitContext();
}

void vtkInternalOpenGLRenderWindow::OpenGLInitState()
{
    this->MakeCurrent();
    vtkOpenGLRenderWindow::OpenGLInitState();
    // Before any of the gl* functions in QOpenGLFunctions are called for a
    // given OpenGL context, an initialization must be run within that context
    initializeOpenGLFunctions();
    glFrontFace(GL_CW); // to compensate for the switched Y axis
}

void vtkInternalOpenGLRenderWindow::InternalRender()
{
    vtkOpenGLRenderWindow::Render();
}

//
// vtkInternalOpenGLRenderWindow Definitions
//

void vtkInternalOpenGLRenderWindow::Render()
{
    this->QtParentRenderer->update();
}

void vtkInternalOpenGLRenderWindow::SetFramebufferObject(QOpenGLFramebufferObject *fbo)
{
    // QOpenGLFramebufferObject documentation states that "The color render
    // buffer or texture will have the specified internal format, and will
    // be bound to the GL_COLOR_ATTACHMENT0 attachment in the framebuffer
    // object"
    this->BackLeftBuffer = this->FrontLeftBuffer = this->BackBuffer = this->FrontBuffer =
        static_cast<unsigned int>(GL_COLOR_ATTACHMENT0);

    // Save GL objects by static casting to standard C types. GL* types
    // are not allowed in VTK header files.
    QSize fboSize = fbo->size();
    this->Size[0] = fboSize.width();
    this->Size[1] = fboSize.height();
    this->NumberOfFrameBuffers = 1;
    this->FrameBufferObject = static_cast<unsigned int>(fbo->handle());
    this->DepthRenderBufferObject = 0; // static_cast<unsigned int>(depthRenderBufferObject);
    this->TextureObjects[0] = static_cast<unsigned int>(fbo->texture());
    this->OffScreenRendering = 1;
    this->OffScreenUseFrameBuffer = 1;
    this->Modified();
}

void QVTKFrameBufferObjectItem::Start()
{
    m_win->OpenGLInitState();
}

void QVTKFrameBufferObjectItem::End()
{
}


void QVTKFrameBufferObjectItem::MakeCurrent()
{
    this->window()->openglContext()->makeCurrent(this->window());
}

void QVTKFrameBufferObjectItem::IsCurrent(vtkObject*, unsigned long, void*, void* call_data)
{
    bool* ptr = reinterpret_cast<bool*>(call_data);
    *ptr = this->window()->openglContext();
}

void QVTKFrameBufferObjectItem::IsDirect(vtkObject*, unsigned long, void*, void* call_data)
{
    int* ptr = reinterpret_cast<int*>(call_data);
    *ptr = QGLFormat::fromSurfaceFormat(this->window()->openglContext()->format()).directRendering();
}

void QVTKFrameBufferObjectItem::SupportsOpenGL(vtkObject*, unsigned long, void*, void* call_data)
{
    int* ptr = reinterpret_cast<int*>(call_data);
    *ptr = QGLFormat::hasOpenGL();
}


QVTKFrameBufferObjectItem::QVTKFrameBufferObjectItem(QQuickItem *parent) : QQuickFramebufferObject(parent)
{
    setAcceptedMouseButtons(Qt::AllButtons);

    m_irenAdapter = new QVTKInteractorAdapter(this);
    m_win = vtkSmartPointer<vtkInternalOpenGLRenderWindow>::New();

    // make a connection between the vtk signals and qt slots so that an initialized and madeCurrent opengl context is given to the vtk
    // we probably need only the Start(), MakeCurrent() and End() one, but just to be sure...
    mConnect = vtkSmartPointer<vtkEventQtSlotConnect>::New();
    mConnect->Connect(m_win, vtkCommand::WindowMakeCurrentEvent, this, SLOT(MakeCurrent()));
    mConnect->Connect(m_win, vtkCommand::WindowIsCurrentEvent, this, SLOT(IsCurrent(vtkObject*, unsigned long, void*, void*)));
    mConnect->Connect(m_win, vtkCommand::StartEvent, this, SLOT(Start()));
    mConnect->Connect(m_win, vtkCommand::EndEvent, this, SLOT(End()));
    mConnect->Connect(m_win, vtkCommand::WindowIsDirectEvent, this, SLOT(IsDirect(vtkObject*, unsigned long, void*, void*)));
    mConnect->Connect(m_win, vtkCommand::WindowSupportsOpenGLEvent, this, SLOT(SupportsOpenGL(vtkObject*, unsigned long, void*, void*)));
}

QVTKFrameBufferObjectItem::~QVTKFrameBufferObjectItem()
{
    mConnect->Disconnect(); // disconnect all slots
    if (m_irenAdapter)
        delete m_irenAdapter;
}

QQuickFramebufferObject::Renderer *QVTKFrameBufferObjectItem::createRenderer() const
{
   return new QVTKFramebufferObjectRenderer(m_win);
}

vtkSmartPointer<vtkInternalOpenGLRenderWindow> QVTKFrameBufferObjectItem::GetRenderWindow() const
{
   return m_win;
}

void QVTKFrameBufferObjectItem::init()
{
}

// theoretically not needed now - the Y is being flipped in render and devicePixelRatio will almost always be = 1 on a PC anyway...but lets keep it to be sure
QMouseEvent QVTKFrameBufferObjectItem::openGLToNative(QMouseEvent const& event)
{
    QPointF localPos(event.localPos());
    localPos.setX(localPos.x() * window()->devicePixelRatio());
    localPos.setY(localPos.y() * window()->devicePixelRatio());
    QMouseEvent nativeEvent(event.type(), localPos, event.button(), event.buttons(), event.modifiers());
    return nativeEvent;
}

void QVTKFrameBufferObjectItem::mouseMoveEvent(QMouseEvent * event)
{
    m_win->GetInteractor()->SetSize(this->width(), this->height());
    QMouseEvent nativeEvent = openGLToNative(*event);
    m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor());
}

void QVTKFrameBufferObjectItem::mousePressEvent(QMouseEvent * event)
{
    m_win->GetInteractor()->SetSize(this->width(), this->height());
    QMouseEvent nativeEvent = openGLToNative(*event);
    m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor());
}

void QVTKFrameBufferObjectItem::mouseReleaseEvent(QMouseEvent * event)
{
    m_win->GetInteractor()->SetSize(this->width(), this->height());
    QMouseEvent nativeEvent = openGLToNative(*event);
    m_irenAdapter->ProcessEvent(&nativeEvent, this->m_win->GetInteractor());
}

void QVTKFrameBufferObjectItem::wheelEvent(QWheelEvent *event)
{
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}


void QVTKFrameBufferObjectItem::keyPressEvent(QKeyEvent* event)
{
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}

void QVTKFrameBufferObjectItem::keyReleaseEvent(QKeyEvent* event)
{
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}
void QVTKFrameBufferObjectItem::focusInEvent(QFocusEvent * event)
{
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}

void QVTKFrameBufferObjectItem::focusOutEvent(QFocusEvent * event)
{
    m_irenAdapter->ProcessEvent(event, this->m_win->GetInteractor());
}

要使用它,请以qml格式定义帧缓冲区的实例,并将其拉伸到要渲染到的窗口中,例如像这样(假设您将QVTKFrameBufferObjectItem注册为qml中的QVTKFrameBuffer,例如这样qmlRegisterType<QVTKFrameBufferObjectItem>("VtkQuick", 1, 0, "QVTKFrameBuffer");):

To use it, define an instance of the framebuffer in your qml form and stretch it across the window you want to render into, e.g. like this (assuming you registered the QVTKFrameBufferObjectItem as a QVTKFrameBuffer in qml e.g. like this qmlRegisterType<QVTKFrameBufferObjectItem>("VtkQuick", 1, 0, "QVTKFrameBuffer");):

import VtkQuick 1.0
QVTKFrameBuffer
{
    id: renderBuffer
    anchors.fill : parent
    Component.onCompleted :
    {
        myCppDisplay.framebuffer = renderBuffer // tell the c++ side of your app that this is the framebuffer into which it should render
    }
}

然后,您将使用通过myCppDisplay.framebuffer.GetRenderWindow()获得的vtkRenderWindow,与将任何其他vtkRenderWindow渲染到由vtk管理的窗口时所使用的方式相同,即,您可以为其分配vtkRenderer,将actor分配给该渲染器,调用TheWindow .Render()随心所欲,它将全部呈现到分配了帧缓冲区的qml组件中.

You then use the vtkRenderWindow you get by myCppDisplay.framebuffer.GetRenderWindow() the same way you would use any other vtkRenderWindow if you were rendering into a vtk-managed window, i.e. you can assign vtkRenderer to it, assign actors to that renderer, call theWindow.Render() as you wish and it will all be rendered into the qml component to which you assigned the framebuffer.

两个注意事项:1)vtk和qt使用不同的坐标系,您需要翻转y坐标...我正在通过为相机分配比例转换来做到这一点,但是还有很多其他方法可以执行它:

Two notes: 1) the vtk and qt use different coordinate system, you need to flip the y-coordinate...I am doing it by assigning a scale transformation to the camera, but there is plenty of other ways to do it:

vtkSmartPointer<vtkTransform> scale = vtkSmartPointer<vtkTransform>::New();
scale->Scale(1, -1, 1);
renderer->GetActiveCamera()->SetUserTransform(scale);

2)一旦开始使用多个线程,事情就会变得非常棘手-您必须确保不要尝试在两个不同的线程中进行渲染,因为它们会争用一个QtQuick的渲染线程.这并不意味着不仅不并行调用renderWindow.Render(),这很容易避免,但是您必须意识到qt线程也用于呈现GUI,因此您可能会遇到这种麻烦(在更新GUI的同时做VTK渲染).

2) things get quite tricky once you start using multiple threads - you have to make sure that you are not trying to render in two different threads, because they would compete for that one QtQuick's rendering thread. This does not mean only not calling renderWindow.Render() in parallel - that is easy to avoid - but you have to realize that that qt thread is used also for rendering the GUI, so you might get into trouble this way (updating GUI while doing VTK rendering).

这篇关于如何以编程方式在QML中渲染VTK项目?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆