在 OpenSceneGraph 场景中显示 Qt Quick 内容

Displaying Qt Quick content inside OpenSceneGraph scene

我想在我的 OpenSceneGraph 场景中的虚拟屏幕上显示我的 Qt Quick 内容。

我现在使用的方法非常低效:

  1. 使用 FBO (FrameBufferObject) 将 Qt Quick 渲染到屏幕外表面
  2. 使用 QOpenGLFramebufferObject::toImage()
  3. 下载像素
  4. 上传像素到 OSG

所以是GPU-CPU-GPU传输。 Source code

正确的解决方案应该以某种方式利用现有的 FBO 并能够仅在 GPU 内部传输数据。

有两种选择:

  1. 在Qt端创建FBO,在OSG端使用其纹理
  2. 在 OSG 端创建 FBO 并将其提供给 Qt Quick 渲染器

Qt部分没问题。我完全迷失了 OSG。谁能给我一些指点?

终于成功了。

总体思路:

  1. 使用 FBO 将 QtQuick 渲染为纹理 - 互联网上有一些 examples 可用。

  2. 在 OpenSceneGraph 中使用此纹理

整个事情包括几个技巧。

上下文共享

要执行某些图形操作,我们必须初始化 OpenGL 全局状态,也称为上下文。

创建纹理时,上下文会存储它的 ID。 ID 不是全局唯一的,因此当在另一个上下文中创建另一个纹理时,它可能会获得相同的 ID,但背后有不同的资源。

如果您只是将纹理的 ID 传递给另一个渲染器(在不同的上下文中运行),期望它显示您的纹理,您最终会显示另一个纹理或黑屏或崩溃。

补救措施是上下文共享,这实际上意味着共享 ID。

OpenSceneGraph 和Qt 抽象不兼容,所以你需要告诉OSG 不要使用它自己的上下文抽象。这是通过调用 setUpViewerAsEmbeddedInWindow

完成的

代码:

OsgWidget::OsgWidget(QWidget* parent, Qt::WindowFlags flags)
    : QOpenGLWidget(parent, flags)
    , m_osgViewer(new osgViewer::Viewer)
{
    setFormat(defaultGraphicsSettings());

    // ...

    m_osgGraphicsContext = m_osgViewer->setUpViewerAsEmbeddedInWindow(x(), y(), width(), height());
}

// osg::ref_ptr<osgViewer::GraphicsWindowEmbedded> m_osgGraphicsContext;

从现在开始,现有的 QOpenGLContext 实例将用作 OSG 渲染的 OpenGL 上下文。

您需要为 QtQuick 渲染创建另一个上下文并将它们设置为共享:

void Widget::initializeGL()
{
    QOpenGLContext* qmlGLContext = new QOpenGLContext(this);
    // ...
    qmlGLContext->setShareContext(context());
    qmlGLContext->create();
}

记住,一次只能有一个活动上下文。

您的方案是:

0. create osg::Texture out of QOpenGLFrameBufferObject::texture()
1. make QtQuick context active
2. render QtQuick to texture
3. make primary (OSG) context active
4. render OSG
5. goto 1

制作一个合适的osg::Texture

由于 OSG 和 Qt API 不兼容,你几乎不能 link QOpenGLFrameBufferObjectosg::Texture2D

QOpenGLFrameBufferObject 具有 QOpenGLFrameBufferObject::texture() 方法,其中 returns opengl 纹理 ID,但 osg::Texture 自行管理所有 openGL 内容。

osg::Texture2D(uint textureId); 这样的东西可以帮助我们,但它不存在。

我们自己做一个吧

osg::Textureosg::TextureObject 支持,后者存储 OpenGL 纹理 ID 和一些其他数据。如果我们用给定的纹理 id 构造 osg::TextureObject 并将其传递给 osg::Texture,后者将使用它。

代码:

void Widget::createOsgTextureFromId(osg::Texture2D* texture, int textureId)
{
    osg::Texture::TextureObject* textureObject = new osg::Texture::TextureObject(texture, textureId, GL_TEXTURE_2D);
    textureObject->setAllocated();

    osg::State* state = m_osgGraphicsContext->getState();
    texture->setTextureObject(state->getContextID(), textureObject);
}

完成演示项目here