이 페이지에서

씬 그래프 - RHI 텍스처 항목

QRhi 로 렌더링된 텍스처를 표시하는 사용자 정의 QQuickItem 를 구현하는 방법을 보여줍니다.

QRhi를 사용하여 렌더링된 RGB 삼각형을 QQuickItem으로 표시하며, MSAA 및 텍스처 옵션을 설정할 수 있는 설정 패널이 제공됩니다.

이 예제는 QRhi API를 사용하여 크로스 플랫폼 및 이식 가능한 3D 렌더링을 텍스처로 수행한 후 해당 이미지를 표시하는 아이템을 구현하는 방법을 보여줍니다.

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

다른 접근 방식과의 비교

RHI Under QML 예제는 QRhi API를 사용하여 이식성 있는 크로스 플랫폼 3D 렌더링을 구현하는 방법을 보여줍니다. 이 예제에서는 사용자 정의 렌더링이 Qt Quick 장면 그래프의 자체 렌더링보다 먼저 수행되도록 하여, 효과적으로 “언더레이(underlay)”를 제공합니다. 이 접근 방식은 추가적인 렌더 타깃과 렌더 패스가 필요하지 않으며, 사용자 정의 렌더링이 장면 그래프의 자체 드로우 호출보다 먼저 메인 렌더 패스에 삽입되므로 효율적입니다.

반면, 이 예제에서는 별도의 렌더 타겟인 QRhiTexture 이 사용되며, 이 타겟의 dimensions 는 씬 내 QQuickItem 의 크기와 일치하고, 전체 렌더 패스가 해당 텍스처를 지운 다음 그 안에 그리기 위해 사용됩니다. 그런 다음 메인 렌더 패스에서 텍스처를 샘플링하여 사각형(quad)에 텍스처를 적용함으로써, 효과적으로 2D 이미지를 표시합니다.

언더레이/오버레이 방식과 비교할 때, 이 방식은 진정한 QQuickItem 을 사용하기 때문에 Qt Quick 장면 내 어디에서나 3D 렌더링의 평면화된 2D 이미지를 표시하고, 블렌딩하며, 변형할 수 있습니다. 다만, 먼저 텍스처로 렌더링하는 과정이 포함되므로 리소스와 성능 측면에서 더 많은 비용이 소요된다는 단점이 있습니다.

개요

이 예제는 QQuickRhiItem 및 QQuickRhiItemRenderer 을 사용하여 구현되었습니다. QQuickRhiItem 은 편의 클래스로, 이를 상속받아 내부적으로 QSGSimpleTextureNode 를 사용하여 QRhiTexture 의 내용을 표시하는 모든 기능을 갖춘 맞춤형 QQuickItem 를 쉽고 빠르게 얻을 수 있습니다. 텍스처의 내용은 애플리케이션에서 제공하는 로직에 의해 생성되며, 이는 QQuickRhiItemRenderer 하위 클래스에 구현되어 있습니다.

ExampleRhiItem QQuickRhiItem 의 서브클래스로, 및 와 같은 몇 가지 속성을 제공합니다. 이러한 속성은 QML을 통해 읽기, 쓰기 및 애니메이션 처리가 이루어집니다. 의 스레드 기반 렌더링 모델을 지원하기 위해, QQQuickRhiItemRenderer는 가상 함수 ()를 가지고 있으며, 이 함수를 재구현하여 (메인/GUI 스레드에 속함)과 (렌더링 스레드에 속함, 존재하는 경우) 간에 데이터를 안전하게 복사할 수 있습니다. angle backgroundAlpha Qt Quick synchronize QQuickRhiItem QQuickRhiItemRenderer

QQuickRhiItemRenderer *ExampleRhiItem::createRenderer()
{
    return new ExampleRhiItemRenderer;
}

void ExampleRhiItem::setAngle(float a)
{
    if (m_angle == a)
        return;

    m_angle = a;
    emit angleChanged();
    update();
}

void ExampleRhiItem::setBackgroundAlpha(float a)
{
    if (m_alpha == a)
        return;

    m_alpha = a;
    emit backgroundAlphaChanged();
    update();
}

void ExampleRhiItemRenderer::synchronize(QQuickRhiItem *rhiItem)
{
    ExampleRhiItem *item = static_cast<ExampleRhiItem *>(rhiItem);
    if (item->angle() != m_angle)
        m_angle = item->angle();
    if (item->backgroundAlpha() != m_alpha)
        m_alpha = item->backgroundAlpha();
}

initialize()는 render()가 처음 호출되기 전에 적어도 한 번 호출되지만, 실제로는 여러 번 호출될 수도 있습니다. QQuickItem 의 지오메트리가 변경되거나(레이아웃 변경, 창 크기 조정, 등), 샘플 수나 텍스처 형식과 같은 QQuickRhiItem 설정이 변경되거나, 항목이 새로운 QQuickWindow 에 속하도록 부모가 재설정되는 경우 등입니다. 이러한 상황들은 하나 이상의 QQuickRhiItem 에서 관리하는 리소스가 변경됨을 의미하므로, initialize()를 다시 호출하게 되며, 이는 서브클래스에도 영향을 미치게 됩니다. 여기 제시된 예제 코드는 이러한 특수한 상황( QRhi 변경, 샘플 수 변경, 텍스처 형식 변경)을 처리할 수 있도록 작성되었습니다. (컬러 버퍼로 사용되는 텍스처를 직접 관리하지 않기 때문에, 크기 변경으로 인해 텍스처가 재생성되는 경우에는 별도의 처리가 필요하지 않습니다.)

void ExampleRhiItemRenderer::initialize(QRhiCommandBuffer *cb)
{
    if (m_rhi != rhi()) {
        m_rhi = rhi();
        m_pipeline.reset();
    }

    if (m_sampleCount != renderTarget()->sampleCount()) {
        m_sampleCount = renderTarget()->sampleCount();
        m_pipeline.reset();
    }

    QRhiTexture *finalTex = m_sampleCount > 1 ? resolveTexture() : colorTexture();
    if (m_textureFormat != finalTex->format()) {
        m_textureFormat = finalTex->format();
        m_pipeline.reset();
    }

initialize()의 나머지 부분은 QRhi 를 기반으로 한 간단한 코드입니다.

3D 씬은 원근 투영을 사용하며, 이는 편의상 QRhiRenderTarget 에서 조회한 출력 크기를 기반으로 계산됩니다(이는 멀티샘플링 사용 여부와 관계없이 작동하는 반면, colorTexture() 및 msaaColorBuffer()에 접근할 경우 어떤 객체가 유효한지에 따라 분기 로직이 필요하기 때문입니다).

3D 그래픽스 API 간의 좌표계 차이를 해결하기 위해 QRhi::clipSpaceCorrMatrix()를 사용하는 점에 유의하십시오.

    if (!m_pipeline) {
        m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
        m_vbuf->create();

        m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64));
        m_ubuf->create();

        m_srb.reset(m_rhi->newShaderResourceBindings());
        m_srb->setBindings({
            QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, m_ubuf.get()),
        });
        m_srb->create();

        m_pipeline.reset(m_rhi->newGraphicsPipeline());
        m_pipeline->setShaderStages({
           { QRhiShaderStage::Vertex, getShader(QLatin1String(":/scenegraph/rhitextureitem/shaders/color.vert.qsb")) },
           { QRhiShaderStage::Fragment, getShader(QLatin1String(":/scenegraph/rhitextureitem/shaders/color.frag.qsb")) }
        });
        QRhiVertexInputLayout inputLayout;
        inputLayout.setBindings({
            { 5 * sizeof(float) }
        });
        inputLayout.setAttributes({
            { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
            { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
        });
        m_pipeline->setSampleCount(m_sampleCount);
        m_pipeline->setVertexInputLayout(inputLayout);
        m_pipeline->setShaderResourceBindings(m_srb.get());
        m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
        m_pipeline->create();

        QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
        resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);
        cb->resourceUpdate(resourceUpdates);
    }

    const QSize outputSize = renderTarget()->pixelSize();
    m_viewProjection = m_rhi->clipSpaceCorrMatrix();
    m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
    m_viewProjection.translate(0, 0, -4);

render()의 구현은 단일 삼각형의 그리기 과정을 기록합니다. 회전 각도가 변할 것으로 예상되므로 4x4 행렬이 포함된 유니폼 버퍼는 매번 업데이트됩니다. 클리어 컬러에는 아이템에서 제공한 배경 알파 값이 베이킹되어 있습니다. 빨강, 녹색, 파랑 성분의 알파 값에도 사전 곱셈을 적용해야 한다는 점을 기억하십시오.

void ExampleRhiItemRenderer::render(QRhiCommandBuffer *cb)
{
    QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch();
    QMatrix4x4 modelViewProjection = m_viewProjection;
    modelViewProjection.rotate(m_angle, 0, 1, 0);
    resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData());

    // Qt Quick expects premultiplied alpha
    const QColor clearColor = QColor::fromRgbF(0.5f * m_alpha, 0.5f * m_alpha, 0.7f * m_alpha, m_alpha);
    cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates);

    cb->setGraphicsPipeline(m_pipeline.get());
    const QSize outputSize = renderTarget()->pixelSize();
    cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
    cb->setShaderResources();
    const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
    cb->setVertexInput(0, 1, &vbufBinding);
    cb->draw(3);

    cb->endPass();
}

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

QQuickRhiItem, QML 기반 RHI의 씬 그래프, 사용자 정의 QSGRenderNode에 대한 씬 그래프항목도 참조하십시오 .

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