2017-01-16 64 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渲染)。

相關問題