2017-01-16 65 views
1

到目前为止,据我所知,我们在QML,我们的主应用程序线程,而我们的“场景图”线程两个线程:http://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html如何以编程方式呈现qml中的vtk项目?

我实现我自己的vtkQmlItem此链接的帮助:http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html

我注意到我的vtkscene仅在qml流发出afterrendering信号时才会呈现。

到目前为止,一切都很好,完美地工作,我可以看到我的vtk场景,甚至可以与它进行交互。

但我想也编程使我vtk的场景为好,因为我想围绕一个VTK对象移动摄像机做一个动画。

调用renderer->render()直接显示了很多VTK错误的,似乎并没有要做到这一点的好办法。

调用this->window()->update()似乎把事件的事件循环,当我希望它可以立即处理。我设法使其立即工作的唯一方法是使用QApplication :: processEvents(),这是一种我不喜欢的破解方法,并且会喜欢另一种解决方案。

所以,我不喜欢工作方案的伪代码如下:

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); 
} 

回答

2

的问题实际上是一个有点复杂,如果没有在过去的几个月里改变,但仍然没有支持对于VTK中的QtQuick来说,这意味着没有简单的几行解决方案。您可以在VTK/GUISupport/QtOpenGL /中找到QtWidgets的支持类,并将它们用作模板以获得对qml的支持。但主要是我建议检查this thread for a discussion about this topic

关键的一点是,QtQuick适用于你试图渲染成一个专门的线程的QML窗口的OpenGL上下文,它不会让任何东西得到这方面。所以为了从VTK渲染它,你必须在该线程内完成。这意味着:

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

2)作出这样的渲染窗口渲染到由qtquick(QQuickFramebufferObject的实例)设置在帧缓冲区对象。 3)将vtk的渲染信号与qt的渲染方法互连 - >例如,当vtk渲染窗口调用makeCurrent时,qt的渲染线程会“唤醒”。

这里是我基于泰勒布劳恩琼斯模板链接上面的实现。它可能不是完美的,但它适用于我(我已经删除了特定于我的应用程序的某些部件,因此它可能无法立即编译,但它应该将您置于某种工作解决方案的路径上):

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:

#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作为QVTKFrameBuffer在QML比如像这样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 
    } 
} 

您再使用vtkRenderWindow您myCppDisplay.framebuffer.GetRenderWindow()让你可以使用任何其他vtkRenderWindow如果你被渲染成以同样的方式vtk托管的窗口,也就是说,你可以为它分配vtkRenderer,给这个渲染器分配角色,根据你的需要调用Windows.Render(),它将全部渲染到你为其分配framebuffer的qml组件。

两个注意事项:1)vtk和qt使用不同的坐标系,你需要翻转y坐标......我通过给摄像机分配一个比例变换来实现,但还有很多其他的方法做到这一点:

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

2)事情变得相当棘手,一旦你开始使用多线程 - 你必须确保你没有尝试在两个不同的线程来呈现,因为他们会为一个QtQuick的渲染线程竞争。这并不意味着只能并行调用renderWindow.Render() - 这很容易避免 - 但您必须认识到,该qt线程也用于呈现GUI,因此您可能会以这种方式陷入困境(更新GUI while做VTK渲染)。

相关问题