씬 그래프 - 사용자 정의 QSGRenderNode

이 예제는 QSGRenderNode 을 사용하여 Qt Quick 씬 그래프에서 사용자 정의 렌더링을 구현하는 방법을 보여줍니다.

이 사용자 정의 렌더 노드 예제는 QSGRenderNode 에서 파생된 씬 그래프 노드를 기반으로 하며, 자체적인 QRhi 기반 렌더링을 제공하는 QQuickItem 서브클래스를 구현하는 방법을 보여줍니다.

‘Direct’ 및 ‘Layer’ 렌더링 모드에서 표시된, 직사각형이 겹쳐진 RGB 삼각형

참고: 이 예제는 Qt GUI 모듈에서 호환성이 제한적으로 보장되는 API에 의존하면서도, 이식성 있는 크로스 플랫폼 3D 렌더링을 수행하는 고급 저수준 기능을 보여줍니다. QRhi API를 사용하려면, 애플리케이션이 Qt::GuiPrivate 에 링크되고 <rhi/qrhi.h> 를 포함해야 합니다.

QSGRenderNode 이를 통해 씬그래프 내에서 렌더 하드웨어 인터페이스(RHI)에 직접 접근할 수 있습니다. 이 예제는 QSGRenderNode 기반 렌더 노드를 생성하고 사용자 정의 항목을 통해 이를 관리하는 방법을 보여줍니다. 렌더 노드는 RHI 파이프라인을 생성하고, 버텍스 및 유니폼 버퍼를 업데이트하며, RHI 명령 버퍼로 렌더링합니다.

실제로 이는 OpenGL, Metal, Vulkan과 같은 네이티브 3D API에 의존하지 않고, 시네그래프 자체의 렌더링과 연동하여 사용자 정의 렌더링을 수행하는 이식성 높은 크로스 플랫폼 방식입니다. 대신 애플리케이션은 Qt의 그래픽 및 셰이더 추상화 계층을 사용합니다.

QSGRenderNode before 이는 씬에 사용자 정의 2D/3D 렌더링을 통합하는 세 가지 방법 중 하나를 가능하게 하는 요소입니다. 나머지 두 가지 옵션은 또는 씬의 자체 렌더링과 함께 렌더링을 수행하거나, 전용 렌더 타겟(텍스처)을 대상으로 완전히 별도의 렌더 패스를 생성한 다음 씬 내의 아이템을 통해 해당 텍스처를 표시하는 것입니다. 을 기반으로 한 접근 방식은 추가적인 렌더링 패스나 렌더 타겟이 필요 없다는 점에서 전자와 유사하며, 장면의 자체 렌더링과 “인라인”으로 사용자 정의 렌더링 명령을 삽입할 수 있게 해줍니다. Qt Quick after Qt Quick QSGRenderNode Qt Quick

이 세 가지 접근 방식에 대해서는 다음 예제를 참조하십시오:

  • 씬 그래프 - QML 기반 RHI - ` QQuickWindow::beforeRendering()` 신호를 기반으로 한 “언더레이(underlay)” 방식을 보여줍니다. 추가 렌더링 패스나 리소스가 필요하지 않지만, ` Qt Quick ` 씬의 나머지 부분과의 합성 및 블렌딩은 상당히 제한적입니다. ` Qt Quick ` 씬 “아래” 또는 “위”에 렌더링하는 것이 가장 간단한 방법입니다.
  • 씬 그래프 - RHI 텍스처 아이템 - 텍스처로 렌더링하고 생성된 콘텐츠로 텍스처링된 사각형을 표시하는 사용자 정의 QQuickItem 을 만드는 방법을 보여줍니다. 이 방법은 매우 유연하며, 결과 2D 이미지를 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;
}

prepare() 및 render() 함수는 Qt Quick 씬이 렌더링될 때마다 호출됩니다. 전자는 렌더 패스를 준비할 때(아직 기록하기 전) 호출됩니다. 이때 일반적으로 아직 생성되지 않은 버퍼, 텍스처, 그래픽 파이프라인과 같은 리소스를 생성하고, 해당 리소스로 데이터를 업로드하도록 큐에 추가합니다.

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

render() 함수는 QQuickWindow 의 스왑체인이나 텍스처(레이어드 아이템의 경우, 또는 ShaderEffectSource 내에 있을 때)를 대상으로 하는 렌더 패스의 기록이 진행 중일 때 호출됩니다.

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

예제 프로젝트 @ code.qt.io

QSGRenderNode, QRhi, QML 기반 RHI의 Scene Graph , RHI Texture Item, Qt Quick Scene Graph도 참조하십시오 .

© 2026 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.