2017-01-16 2 views
1

До сих пор, я понимаю, что у нас есть две темы, в QML, наш основной поток приложения, и наш «графа сцены» нить: http://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.htmlКак визуализировать программный элемент vtk в qml?

Я реализовал свой собственный vtkQmlItem с помощью этой ссылки: http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html

и я заметил, что мой vtkscene отображается только тогда, когда сигнал afterrendering испускается потоком qml.

До сих пор все в порядке и прекрасно работает, я вижу свою сцену в vtk и могу даже втягивать ее.

Но я также хотел бы программно отобразить мою сцену vtk, так как я хочу сделать анимацию, перемещая камеру вокруг объекта vtk.

Вызов renderer->render() непосредственно показывает много ошибок vtk и, похоже, не лучший способ сделать это.

Вызов this->window()->update(), кажется, помещает событие в eventLoop, когда я хочу, чтобы он обрабатывался мгновенно. Единственный способ, которым мне удалось заставить его работать мгновенно, - это использовать 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

проблема на самом деле немного сложнее, и если ничего не изменилось за последние несколько месяцев, до сих пор нет поддержки для QtQuick в VTK, что означает отсутствие простого решения нескольких строк. Вы можете найти классы поддержки для QtWidgets в VTK/GUISupport/QtOpenGL/и использовать их в качестве шаблона для получения поддержки qml. Но в основном рекомендую проверить this thread for a discussion about this topic.

Ключевым моментом является то, что QtQuick содержит контекст openGL для окна qml, которое вы пытаетесь преобразовать в выделенный поток, и он не позволит чему-либо еще получить этот контекст. Поэтому для того, чтобы сделать это с VTK, вы должны сделать это в этом потоке. Это означает:

1) Создайте свой собственный vtkRenderWindow, который переопределяет метод Render() таким образом, чтобы он выполнялся в потоке рендеринга 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, назначить участников этому рендереру, вызвать theWindow.Render(), как вам угодно, и все это будет отображаться в 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 в то время как выполнение рендеринга VTK).

Смежные вопросы