本页内容

场景图 - RHI 纹理项

演示如何实现一个自定义的QQuickItem ,用于显示由QRhi 渲染的纹理。

使用 QRhi 渲染的 RGB 三角形,以 QQuickItem 形式显示,并带有用于 MSAA 和纹理选项的设置面板

本示例演示了如何实现一个项,该项利用QRhi API将跨平台、可移植的3D渲染结果渲染到纹理中,并随后显示该图像。

注意:本示例 演示了执行可移植、跨平台 3D 渲染的高级低级功能,同时依赖于 Qt GUI 模块中兼容性保证有限的 API。为了能够使用QRhi API,应用程序需链接到Qt::GuiPrivate 并包含<rhi/qrhi.h> 。

与其他方法的比较

RHI Under QML示例展示了如何利用QRhi API实现可移植、跨平台的3D渲染,其实现方式是将自定义渲染置于Qt Quick 场景图自身渲染之前,从而有效地提供了一个“底层渲染层”。 这种方法非常高效,因为现在不需要额外的渲染目标和渲染通道,自定义渲染是在主渲染通道中、在场景图自身的绘制调用之前插入的。

相比之下,本示例涉及一个独立的渲染目标(即QRhiTexture ),其dimensions 与场景中QQuickItem 的大小相匹配,并使用一个完整的渲染通道来清空该纹理并向其中绘制。随后,在主渲染通道中对该纹理进行采样,并将其用作四边形的纹理,从而有效地显示了一张2D图像。

与底层/叠加层方法相比,由于这里拥有真正的QQuickItem ,因此可以在Qt Quick 场景的任意位置显示、混合和变换3D渲染的扁平化2D图像。但这会以资源消耗和性能开销为代价,因为它需要先渲染到纹理中。

概述

该示例使用QQuickRhiItem 和QQuickRhiItemRenderer 实现。QQuickRhiItem 是一个便利类,可以通过继承该类轻松快速地获得一个功能齐全的自定义QQuickItem ,该组件通过底层的QSGSimpleTextureNode 来显示QRhiTexture 的内容。纹理的内容由应用程序提供的逻辑生成,该逻辑实现于其QQuickRhiItemRenderer 子类中。

ExampleRhiItem 是一个QQuickRhiItem 的子类,提供了一些属性,例如angle 和backgroundAlpha 。这些属性将通过QML进行读取、写入和动画控制。 为了支持Qt Quick 的线程化渲染模型,QQQuickRhiItemRenderer提供了一个虚拟函数synchronize(),可以通过重写该函数,在QQuickRhiItem (属于主线程/GUI线程)与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 ,这些情况都会触发再次调用initialize(),因为它们意味着QQuickRhiItem 管理的资源中有一项或多项发生了变化,这也会对子类产生影响。 此处的示例代码旨在处理这些特殊情况(更改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() 则需要根据当前有效对象的情况添加分支逻辑)

请注意使用QRhi::clipSpaceCorrMatrix() 来应对不同 3D 图形 API 之间的坐标系差异。

    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.