场景图 - 自定义 QSGRenderNode
演示如何使用QSGRenderNode 在Qt Quick 场景图中实现自定义渲染。
自定义渲染节点示例展示了如何实现QQuickItem 子类,该子类由源自QSGRenderNode 的场景图节点支持,提供自己的基于QRhi 的渲染。
注: 该示例演示了执行可移植、跨平台 3D 渲染的高级底层功能,同时依赖于 Qt GUI 模块中兼容性保证有限的 API。要使用QRhi API,应用程序需要链接到Qt::GuiPrivate
并包含<rhi/qrhi.h>
。
QSGRenderNode 这样就可以直接访问场景图中的渲染硬件接口(RHI)。本示例演示了如何创建基于 的渲染节点,并使用自定义项目对其进行管理。渲染节点创建一个 RHI 管道,更新顶点和统一缓冲区,并渲染到 RHI 命令缓冲区。QSGRenderNode
实际上,这是一种可移植的跨平台方法,可与场景图自身的渲染联机执行自定义渲染,而无需使用 OpenGL、Metal 或 Vulkan 等本地 3D API。相反,应用程序使用 Qt 的图形和着色器抽象层。
QSGRenderNode Qt Graphics 是将自定义 2D/3D 渲染集成到 场景的三种方法之一。其他两种方法是:在 或 场景中执行渲染,或针对专用渲染目标(纹理)生成一个独立的渲染传递,然后让场景中的一个项目显示该纹理。基于 的方法与前者类似,不涉及额外的渲染传递或渲染目标,并允许在 场景自身的渲染中 "内联 "注入自定义渲染命令。Qt Quick before
after
Qt Quick QSGRenderNode Qt Quick
有关这三种方法,请参阅下面的示例:
- Scene Graph - RHI Under QML- 演示基于QQuickWindow::beforeRendering() 信号的 "underlay "方法。不需要额外的渲染传递和资源,但与Qt Quick 场景其他部分的合成和混合非常有限。在Qt Quick 场景的 "下方 "或 "上方 "进行渲染是最简单的方法。
- 场景图 - RHI 纹理项- 演示创建一个自定义的QQuickItem ,将其渲染为纹理,并显示用生成的内容制作的四边形纹理。这种方法非常灵活,可以将生成的二维图像与Qt Quick 场景的其余部分完全混合和合成。但这需要额外的渲染通道和渲染目标。
- 本示例演示了 "内联 "方法,即Qt Quick 场景图在主渲染过程中调用自定义项和节点实现。这种方法可以提高性能(不涉及额外的渲染传递、贴图和混合),但也有潜在的缺陷,而且是最复杂的方法。
自定义项源于QQuickItem 。最重要的是,它重新实现了updatePaintNode() 。
class CustomRender : public QQuickItem { Q_OBJECT Q_PROPERTY(QList<QVector2D> vertices READ vertices WRITE setVertices NOTIFY verticesChanged) QML_ELEMENT public: explicit CustomRender(QQuickItem *parent = nullptr); QList<QVector2D> vertices() const; void setVertices(const QList<QVector2D> &newVertices); signals: void verticesChanged(); protected: QSGNode *updatePaintNode(QSGNode *old, UpdatePaintNodeData *) override; private: QList<QVector2D> m_vertices; };
构造函数设置了ItemHasContents 标志,表明这是一个可视化项目。
CustomRender::CustomRender(QQuickItem *parent) : QQuickItem(parent) { setFlag(ItemHasContents, true); connect(this, &CustomRender::verticesChanged, this, &CustomRender::update); }
updatePaintNode() 实现会创建一个自定义场景图节点实例(如果尚未完成)。此项目的后备QSGNode 树包含一个节点,即QSGRenderNode 派生类的实例。在使用Qt Quick 的线程渲染模型时,该函数在渲染线程上调用,主线程被阻塞。因此,访问主线程数据(如存储在 QQuickItems 中的数据)是安全的。节点,即QSGRenderNode 子类的实例,将 "生活 "在呈现线程上。
QSGNode *CustomRender::updatePaintNode(QSGNode *old, UpdatePaintNodeData *) { CustomRenderNode *node = static_cast<CustomRenderNode *>(old); if (!node) node = new CustomRenderNode(window()); node->setVertices(m_vertices); return node; }
CustomRenderNode
类派生自QSGRenderNode ,重新实现了许多虚拟函数。为了管理QRhi 资源(缓冲区、管道等),智能指针在这种情况下非常有用,因为当QRhi 仍然可用时,场景图会将节点与渲染线程(如果有的话)上的其他场景一起销毁,因此通过析构函数或智能指针释放资源是合法和安全的。
class CustomRenderNode : public QSGRenderNode { public: CustomRenderNode(QQuickWindow *window); void setVertices(const QList<QVector2D> &vertices); void prepare() override; void render(const RenderState *state) override; void releaseResources() override; RenderingFlags flags() const override; QSGRenderNode::StateFlags changedStates() const override; protected: QQuickWindow *m_window; std::unique_ptr<QRhiBuffer> m_vertexBuffer; std::unique_ptr<QRhiBuffer> m_uniformBuffer; std::unique_ptr<QRhiShaderResourceBindings> m_resourceBindings; std::unique_ptr<QRhiGraphicsPipeline> m_pipeline; QList<QRhiShaderStage> m_shaders; bool m_verticesDirty = true; QList<QVector2D> m_vertices; };
表现良好的QSGRenderNode 子类也会重新实现releaseResources(),在这种情况下,它可以是一组简单的 reset() 调用。
void CustomRenderNode::releaseResources() { m_vertexBuffer.reset(); m_uniformBuffer.reset(); m_pipeline.reset(); m_resourceBindings.reset(); }
QSGRenderNode 是通过QRhi API(而不是直接通过 OpenGL、Vulkan、Metal 等)进行渲染的,它会将项目变换考虑在内(因为它实际上只进行 2D 渲染)。因此,指定适当的标记可能会带来微小的性能提升。
QSGRenderNode::RenderingFlags CustomRenderNode::flags() const { // We are rendering 2D content directly into the scene graph using QRhi, no // direct usage of a 3D API. Hence NoExternalRendering. This is a minor // optimization. // Additionally, the node takes the item transform into account by relying // on projectionMatrix() and matrix() (see prepare()) and never rendering at // other Z coordinates. Hence DepthAwareRendering. This is a potentially // bigger optimization. return QSGRenderNode::NoExternalRendering | QSGRenderNode::DepthAwareRendering; }
每次Qt Quick 场景渲染时,都会调用 prepare() 和 render() 函数。第一个函数在准备(但尚未记录)渲染时被调用。这通常会创建缓冲区、纹理和图形管道等资源(如果尚未完成),并排队向它们上传数据。
void CustomRenderNode::prepare() { QRhi *rhi = m_window->rhi(); QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch(); if (m_verticesDirty) { m_vertexBuffer.reset(); m_verticesDirty = false; } if (!m_vertexBuffer) { m_vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_vertices.count() * sizeof(QVector2D))); m_vertexBuffer->create(); resourceUpdates->uploadStaticBuffer(m_vertexBuffer.get(), m_vertices.constData()); }
当以QQuickWindow 的 swapchain 或纹理(如果是分层项目,或在ShaderEffectSource 中)为目标的呈现过程的录制处于活动状态时,render() 函数将被调用。
void CustomRenderNode::render(const RenderState *) { QRhiCommandBuffer *cb = commandBuffer(); cb->setGraphicsPipeline(m_pipeline.get()); QSize renderTargetSize = renderTarget()->pixelSize(); cb->setViewport(QRhiViewport(0, 0, renderTargetSize.width(), renderTargetSize.height())); cb->setShaderResources(); QRhiCommandBuffer::VertexInput vertexBindings[] = { { m_vertexBuffer.get(), 0 } }; cb->setVertexInput(0, 1, vertexBindings); cb->draw(m_vertices.count()); }
另请参阅 QSGRenderNode,QRhi,Scene Graph - RHI Under QML,Scene Graph - RHI Texture Item 和Qt Quick Scene Graph。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.