Sur cette page

Scene Graph - Élément de texture RHI

Ce guide explique comment implémenter un élément personnalisé « QQuickItem » qui affiche une texture générée par « QRhi ».

Triangle RVB généré à l'aide de QRhi et affiché sous forme de QQuickItem, avec un panneau de configuration pour le MSAA et les options de texture

Cet exemple montre comment implémenter un élément qui effectue un rendu 3D portable et multiplateforme dans une texture à l’aide des API d’ QRhi, puis affiche cette image.

Remarque : cet exemple illustre des fonctionnalités avancées de bas niveau permettant un rendu 3D portable et multiplateforme, tout en s'appuyant sur des API du module Qt GUI dont la compatibilité n'est pas entièrement garantie. Pour pouvoir utiliser les API d'QRhi, l'application doit être liée à Qt::GuiPrivate et inclure <rhi/qrhi.h>.

Comparaison avec d’autres approches

L’exemple « RHI Under QML » montre comment implémenter un rendu 3D portable et multiplateforme à l’aide des API QRhi, de telle sorte que le rendu personnalisé soit exécuté avant le rendu propre au graphe de scène de Qt Quick, fournissant ainsi un véritable « sous-couche ». Cette approche est efficace car, comme aucune cible de rendu ni passe de rendu supplémentaire n’est nécessaire, le rendu personnalisé est injecté dans la passe de rendu principale avant les appels de dessin propres au graphe de scène.

En revanche, cet exemple implique une cible de rendu distincte, une texture QRhiTexture dont les dimensions dimensions correspondent à celles de l’ QQuickItem dans la scène, ainsi qu’un passage de rendu complet utilisé pour effacer puis dessiner dans cette texture. La texture est ensuite échantillonnée lors du passage de rendu principal et sert à texturer un quadrilatère, affichant ainsi une image 2D.

Par rapport à l’approche « underlay/overlay », cela permet d’afficher, de mélanger et de transformer l’image 2D aplatie du rendu 3D n’importe où dans la scène d’ Qt Quick, puisque nous disposons ici d’une véritable « QQuickItem ». Cela se fait au prix d’une consommation plus importante en termes de ressources et de performances, car cela implique de réaliser d’abord un rendu vers une texture.

Présentation

L’exemple est implémenté à l’aide de ` QQuickRhiItem ` et ` QQuickRhiItemRenderer`. ` QQuickRhiItem ` est une classe utilitaire qui peut être sous-classée pour obtenir facilement et rapidement une ` QQuickItem ` personnalisée et complète, affichant le contenu d’un ` QRhiTexture ` en utilisant ` QSGSimpleTextureNode ` en arrière-plan. Le contenu de la texture est généré par la logique fournie par l’application et implémentée dans sa sous-classe ` QQuickRhiItemRenderer `.

ExampleRhiItem Il s’agit d’une sous-classe de ` QQuickRhiItem ` qui offre quelques propriétés, telles que ` angle ` et ` backgroundAlpha`. Celles-ci seront lues, écrites et animées à partir de QML. Afin de prendre en charge le modèle de rendu multithread d’ Qt Quick, le QQQuickRhiItemRenderer dispose d’une fonction virtuelle synchronize() qui peut être réimplémentée pour effectuer en toute sécurité la copie de données entre l’ QQuickRhiItem (appartenant au thread principal/GUI) et l’ QQuickRhiItemRenderer (appartenant au thread de rendu, s’il 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() est appelée au moins une fois avant le premier appel à `render()`, mais peut en pratique être invoquée plusieurs fois : si la géométrie de l’ QQuickItem e change (en raison d’un changement de mise en page, d’un redimensionnement de la fenêtre, etc.), si des paramètres d’ QQuickRhiItem, tels que le nombre d’échantillons et le format de texture, changent, ou si l’élément est réaffecté à un nouveau parent ( QQuickWindow), tous ces événements déclenchent un nouvel appel à `initialize()`, car ils impliquent qu’une ou plusieurs des ressources gérées par ` QQuickRhiItem` changent, ce qui aura également des répercussions sur la sous-classe. Le code d’exemple présenté ici est conçu pour gérer ces situations particulières (modification de QRhi, modification du nombre d’échantillons, modification du format de texture). (Comme il ne conserve pas la texture utilisée comme tampon de couleur, le cas où la texture est recréée en raison d’une taille différente ne nécessite aucun traitement particulier.)

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

Le reste de la fonction `initialize()` est un code simple basé sur ` QRhi`.

La scène 3D utilise une projection en perspective, calculée en fonction de la taille de sortie, interrogée auprès de l’ QRhiRenderTarget pour plus de commodité (car cela fonctionne indépendamment de l’utilisation ou non du multisampling, alors que l’accès à colorTexture() et msaaColorBuffer() nécessiterait une logique de branchement en fonction de l’objet qui se trouve être valide).

Notez l’utilisation de QRhi::clipSpaceCorrMatrix() pour tenir compte des différences entre les systèmes de coordonnées des différentes API graphiques 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);

L’implémentation de ` render()` enregistre le tracé d’un seul triangle. Le tampon uniforme contenant la matrice 4x4 est mis à jour à chaque fois, car nous nous attendons à ce que l’angle de rotation change. La couleur d’effacement intègre l’alpha d’arrière-plan fourni par l’élément. N’oubliez pas qu’il est également nécessaire de pré-multiplier la valeur alpha par les composantes rouge, verte et bleue.

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

Exemple de projet sur code.qt.io

Voir également QQuickRhiItem, Graphique de scène - RHI sous QML et Graphique de scène - QSGRenderNode personnalisé.

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