【问题标题】:How to render programmatically a vtk item in qml?如何在 qml 中以编程方式呈现 vtk 项?
【发布时间】:2017-05-31 20:25:55
【问题描述】:

到目前为止,我了解到我们在 QML 中有两个线程,我们的主应用程序线程和我们的“场景图”线程:http://doc.qt.io/qt-5/qtquick-visualcanvas-scenegraph.html

借助此链接,我实现了自己的 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(),这是一个我不喜欢的 hack,我会喜欢另一个解决方案。

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

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

【问题讨论】:

    标签: qt opengl events qml vtk


    【解决方案1】:

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

    关键是 QtQuick 将你试图渲染到的 qml 窗口的 openGL 上下文保存在一个专用线程中,它不会让其他任何东西获得该上下文。因此,为了从 VTK 渲染到它,您必须在该线程中进行。这意味着:

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

    2) 使渲染窗口渲染到 qtquick 提供的帧缓冲区对象(QQuickFramebufferObject 的实例)。

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

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

    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 表单中定义一个帧缓冲区的实例,并将其拉伸到您要渲染到的窗口中,例如像这样(假设您在 qml 中将 QVTKFrameBufferObjectItem 注册为 QVTKFrameBuffer,例如像这样qmlRegisterType&lt;QVTKFrameBufferObjectItem&gt;("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,就像您在渲染到 vtk 管理的窗口时使用任何其他 vtkRenderWindow 一样,即您可以将 vtkRenderer 分配给它,将actors 分配给该渲染器,调用 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,因此您可能会以这种方式遇到麻烦(更新 GUI 同时做VTK渲染)。

    【讨论】:

      【解决方案2】:

      对于使用 Qt QuickControls 2 和 VTK 8 寻找解决方案的人,您可以在此存储库 https://github.com/nicanor-romero/QtVtk 中找到一个工作示例,其中包含自述文件中的构建说明。

      【讨论】:

      • 你是如何设法覆盖 qml 和 vtk 的?在添加以下行之前,我的应用程序总是崩溃: _putenv_s("QML_BAD_GUI_RENDER_LOOP", "1");我在你的代码中没有看到
      猜你喜欢
      • 1970-01-01
      • 2021-09-07
      • 2010-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多