En esta página

Gráfico de escena - Elemento de textura RHI

Muestra cómo implementar un QQuickItem personalizado que muestre una textura renderizada en QRhi.

Este ejemplo muestra cómo implementar un elemento que realiza un renderizado 3D portátil y multiplataforma en una textura utilizando las API de QRhi y, a continuación, muestra esa imagen.

Nota: Este ejemplo muestra una funcionalidad avanzada de bajo nivel que realiza un renderizado 3D portátil y multiplataforma, a la vez que depende de APIs con garantía de compatibilidad limitada del módulo Qt Gui. Para poder utilizar las API de QRhi, la aplicación enlaza con Qt::GuiPrivate e incluye <rhi/qrhi.h>.

Comparación con otros enfoques

El ejemplo RHI Under QML muestra cómo implementar un renderizado 3D portable y multiplataforma con las APIs QRhi de manera que el renderizado personalizado se emite antes que el renderizado propio del gráfico de escena Qt Quick, proporcionando efectivamente un "underlay". Este enfoque es eficiente, ya que ahora se necesitan objetivos de renderizado y pases de renderizado adicionales, el renderizado personalizado se inyecta en el pase de renderizado principal antes de las propias llamadas de dibujo del gráfico de escena.

Por el contrario, este ejemplo implica un objetivo de renderizado separado, un QRhiTexture, cuyo dimensions coincide con el tamaño de QQuickItem en la escena, y un pase de renderizado completo que se utiliza para limpiar y luego dibujar en esa textura. A continuación, la textura se muestrea en el pase de renderizado principal y se utiliza para texturizar un quad, mostrando así una imagen 2D.

Comparado con el enfoque de subyacente/superpuesto, esto permite mostrar, mezclar y transformar la imagen 2D aplanada del renderizado 3D en cualquier parte de la escena Qt Quick ya que aquí tenemos un verdadero QQuickItem. Esto tiene el inconveniente de ser más caro en términos de recursos y rendimiento, ya que implica renderizar primero en una textura.

Visión general

El ejemplo se implementa utilizando QQuickRhiItem y QQuickRhiItemRenderer. QQuickRhiItem es una clase de conveniencia que puede ser subclasificada para obtener fácil y rápidamente una QQuickItem personalizada con todas las funciones que muestra el contenido de un QRhiTexture utilizando QSGSimpleTextureNode bajo el capó. El contenido de la textura es generado por la lógica de la aplicación implementada en su subclase QQuickRhiItemRenderer.

ExampleRhiItem es una subclase de QQuickRhiItem que ofrece algunas propiedades, como angle y backgroundAlpha. Estas van a ser leídas, escritas y animadas desde QML. Para soportar el modelo de renderizado por hilos de Qt Quick, el QQQuickRhiItemRenderer tiene una función virtual synchronize() que puede ser reimplementada para realizar de forma segura la copia de datos entre QQuickRhiItem (perteneciente al hilo principal/GUI) y QQuickRhiItemRenderer (perteneciente al hilo de renderizado, si existe).

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() es invocada al menos una vez antes de la primera llamada a render(), pero en la práctica puede ser invocada múltiples veces: si la geometría de QQuickItem cambia (debido a algún cambio de layout, redimensionamiento de la ventana, etc.), si cambian los ajustes de QQuickRhiItem como el recuento de muestras y el formato de textura, o si el elemento es reparenteado para que pertenezca a un nuevo QQuickWindow, todo esto desencadena llamar de nuevo a initialize() porque implica que uno o más de los recursos gestionados por QQuickRhiItem cambian, lo que va a tener implicaciones también en la subclase. El código de ejemplo aquí está preparado para manejar estas situaciones especiales (cambiar QRhi, cambiar el recuento de muestras, cambiar el formato de textura). (como no mantiene la textura usada como buffer de color, el caso en el que la textura es recreada debido a un tamaño diferente no necesita un manejo especial).

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

El resto de initialize() es código basado en QRhi.

La escena 3D usa una proyección en perspectiva, que se calcula basándose en el tamaño de salida, consultado desde QRhiRenderTarget por conveniencia (porque esto funciona independientemente de usar multimuestreo o no, mientras que acceder a colorTexture() y msaaColorBuffer() necesitaría una lógica de bifurcación basada en cuál de los objetos resulta ser válido).

Obsérvese el uso de QRhi::clipSpaceCorrMatrix() para tener en cuenta las diferencias de sistema de coordenadas entre las API de gráficos 3D.

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

La implementación de render() registra el dibujo de un único triángulo. El buffer uniforme con la matriz 4x4 se actualiza cada vez ya que esperamos que el ángulo de rotación cambie. El color claro tiene el alfa de fondo proporcionado por el elemento. Recuerda la necesidad de premultiplicar el valor alfa en los componentes rojo, verde y azul.

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

Proyecto de ejemplo @ code.qt.io

Ver también QQuickRhiItem, Gráfico de escena - RHI bajo QML, y Gráfico de escena - QSGRenderNode personalizado.

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