首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在qml中以编程方式呈现vtk项?

如何在qml中以编程方式呈现vtk项?
EN

Stack Overflow用户
提问于 2017-01-16 22:14:56
回答 3查看 4K关注 0票数 3

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

我通过以下链接实现了自己的vtkQmlItem:http://doc.qt.io/qt-5/qtquick-scenegraph-openglunderqml-example.html

我注意到,只有当qml流发出afterrendering信号时,我的qml场景才会呈现出来。

到目前为止,一切都很好,我可以看到我的vtk场景,甚至可以与它互动。

但是我也想以编程的方式渲染我的vtk场景,因为我想通过移动摄像机在vtk对象周围做动画。

直接调用renderer->render()会显示大量vtk错误,并且似乎不是这样做的好方法。

调用this->window()->update()似乎将事件放在eventLoop中,而我希望立即处理它。我成功地使它立即工作的唯一方法是使用QApplication::processEvents(),这是一个我不喜欢并且会喜欢另一个解决方案的黑客。

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

代码语言:javascript
复制
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);
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-02-03 12:23:34

这个问题实际上有点复杂,如果在过去几个月中没有任何变化,那么VTK中仍然没有对QtQuick的支持,这意味着没有简单的几行解决方案。您可以在VTK/GUISupport/QtOpenGL/中找到对QtWidgets的支持类,并使用它们作为模板来获得对qml的支持。但主要是我建议检查这篇文章是关于这个话题的讨论

关键是,QtQuick在一个专用线程中保存您要呈现到的qml窗口的openGL上下文,它不会让其他任何内容获得该上下文。因此,为了从VTK呈现到它,您必须在该线程中完成它。这意味着:

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

2)将呈现窗口呈现为由qt腿(QQuickFramebufferObject的实例)提供的框架缓冲区对象。

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

下面是我基于泰勒·布劳恩-琼斯上面链接的模板的实现。它可能不是完美的,但它对我有效(我已经删除了一些特定于我的应用程序的部分,所以它可能不会立即编译,但它应该会让您走上一条通往某个工作解决方案的道路):

qmlVtk.h:

代码语言:javascript
复制
#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:

代码语言:javascript
复制
#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<QVTKFrameBufferObjectItem>("VtkQuick", 1, 0, "QVTKFrameBuffer");):

代码语言:javascript
复制
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()获得的myCppDisplay.framebuffer.GetRenderWindow(),就像在vtk托管窗口中呈现时使用任何其他vtkRenderWindow一样,也就是说,您可以将vtkRenderer分配给它,将参与者分配给该呈现器,按您的意愿调用theWindow.Render(),所有这些都将呈现到分配给框架缓冲区的qml组件中。

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

代码语言:javascript
复制
vtkSmartPointer<vtkTransform> scale = vtkSmartPointer<vtkTransform>::New();
scale->Scale(1, -1, 1);
renderer->GetActiveCamera()->SetUserTransform(scale);

2)当您开始使用多个线程时,事情变得非常棘手--您必须确保不尝试在两个不同的线程中呈现,因为它们将竞争QtQuick的呈现线程。这并不意味着不并行调用renderWindow.Render() --这很容易避免--但您必须认识到,qt线程也用于呈现GUI,因此您可能会因此而陷入麻烦(在进行VTK呈现时更新GUI )。

票数 7
EN

Stack Overflow用户

发布于 2018-07-11 17:13:24

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

票数 8
EN

Stack Overflow用户

发布于 2022-11-16 09:42:05

我尝试实现这个示例,并成功地编译并运行了它,但是我在代码方面面临一些挑战。

我无法确定这些行是在哪里定义的,因为它们会给我提供运行时错误,也不会被识别。

(在qmlVtk.cpp -> SetFramebufferObject()函数中定义)

代码语言:javascript
复制
this->NumberOfFrameBuffers = 1;
this->FrameBufferObject = static_cast<unsigned int>(fbo->handle());
this->DepthRenderBufferObject = 0; 
this->TextureObjects[0] = static_cast<unsigned int>(fbo->texture());
this->OffScreenRendering = 1;
this->OffScreenUseFrameBuffer = 1;

另外,谁能指导我如何将QML与OpenGLWidget集成。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41685872

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档