Sur cette page

Graphique de scène - Géométrie personnalisée

Montre comment mettre en œuvre une géométrie personnalisée dans le graphe de scène Qt Quick.

L'exemple de géométrie personnalisée montre comment créer un site QQuickItem qui utilise l'API du graphe de scène pour construire une géométrie personnalisée pour le graphe de scène. Pour ce faire, il crée un élément BezierCurve qui fait partie du module CustomGeometry et l'utilise dans un fichier QML.

Déclaration de courbe de Bézier

#include <QtQuick/QQuickItem>

class BezierCurve : public QQuickItem
{
    Q_OBJECT

    Q_PROPERTY(QPointF p1 READ p1 WRITE setP1 NOTIFY p1Changed)
    Q_PROPERTY(QPointF p2 READ p2 WRITE setP2 NOTIFY p2Changed)
    Q_PROPERTY(QPointF p3 READ p3 WRITE setP3 NOTIFY p3Changed)
    Q_PROPERTY(QPointF p4 READ p4 WRITE setP4 NOTIFY p4Changed)

    Q_PROPERTY(int segmentCount READ segmentCount WRITE setSegmentCount NOTIFY segmentCountChanged)
    QML_ELEMENT

public:
    BezierCurve(QQuickItem *parent = nullptr);
    ~BezierCurve();

    QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;

    QPointF p1() const { return m_p1; }
    QPointF p2() const { return m_p2; }
    QPointF p3() const { return m_p3; }
    QPointF p4() const { return m_p4; }

    int segmentCount() const { return m_segmentCount; }

    void setP1(const QPointF &p);
    void setP2(const QPointF &p);
    void setP3(const QPointF &p);
    void setP4(const QPointF &p);

    void setSegmentCount(int count);

signals:
    void p1Changed(const QPointF &p);
    void p2Changed(const QPointF &p);
    void p3Changed(const QPointF &p);
    void p4Changed(const QPointF &p);

    void segmentCountChanged(int count);

private:
    QPointF m_p1;
    QPointF m_p2;
    QPointF m_p3;
    QPointF m_p4;

    int m_segmentCount;
};

La déclaration de l'élément sous-classe la classe QQuickItem et ajoute cinq propriétés. Une pour chacun des quatre points de contrôle de la courbe de Bézier et un paramètre pour contrôler le nombre de segments de la courbe. Pour chacune des propriétés, nous avons des fonctions getter et setter correspondantes. Étant donné que ces propriétés peuvent être liées à QML, il est également préférable d'avoir des signaux de notification pour chacune d'entre elles afin que les changements soient pris en compte par le moteur QML et utilisés en conséquence.

    QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override;

Le point de synchronisation entre la scène QML et le graphe de scène de rendu est la fonction virtuelle QQuickItem::updatePaintNode() que tous les éléments avec une logique de graphe de scène personnalisée doivent implémenter.

Remarque : dans de nombreuses configurations matérielles, le rendu du graphe de scène s'effectue sur un thread distinct. Il est donc essentiel que l'interaction avec le graphe de scène se fasse de manière contrôlée, avant tout par l'intermédiaire de la fonction QQuickItem::updatePaintNode().

Mise en œuvre de la courbe de Bézier

BezierCurve::BezierCurve(QQuickItem *parent)
    : QQuickItem(parent)
    , m_p1(0, 0)
    , m_p2(1, 0)
    , m_p3(0, 1)
    , m_p4(1, 1)
    , m_segmentCount(32)
{
    setFlag(ItemHasContents, true);
}

Le constructeur de la courbe de Bézier définit des valeurs par défaut pour les points de contrôle et le nombre de segments. La courbe de Bézier est spécifiée en coordonnées normalisées par rapport au rectangle de délimitation de l'élément.

Le constructeur définit également le drapeau QQuickItem::ItemHasContents. Ce drapeau indique au canevas que cet élément fournit un contenu visuel et appellera QQuickItem::updatePaintNode() lorsqu'il sera temps de synchroniser la scène QML avec le graphe de la scène de rendu.

BezierCurve::~BezierCurve() = default;

La classe BezierCurve n'a pas de membres de données qui doivent être nettoyés, le destructeur ne fait donc rien. Il convient de mentionner que le graphe de scène de rendu est géré par le graphe de scène lui-même, potentiellement dans un thread différent, de sorte qu'il ne faut jamais conserver les références QSGNode dans la classe QQuickItem ni essayer de les nettoyer explicitement.

void BezierCurve::setP1(const QPointF &p)
{
    if (p == m_p1)
        return;

    m_p1 = p;
    emit p1Changed(p);
    update();
}

La fonction setter pour la propriété p1 vérifie si la valeur est inchangée et sort prématurément si c'est le cas. Elle met ensuite à jour la valeur interne et émet le signal de changement. Elle appelle ensuite la fonction QQuickItem::update() qui notifie au graphe de la scène de rendu que l'état de cet objet a changé et qu'il doit être synchronisé avec le graphe de la scène de rendu. Un appel à update() entraînera un appel à QQuickItem::updatePaintNode() à un moment ultérieur.

Les autres fixateurs de propriété sont équivalents et sont omis dans cet exemple.

QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    QSGGeometryNode *node = nullptr;
    QSGGeometry *geometry = nullptr;

    if (!oldNode) {
        node = new QSGGeometryNode;

La fonction updatePaintNode() est le principal point d'intégration pour la synchronisation de l'état de la scène QML avec le graphe de la scène de rendu. La fonction se voit passer un QSGNode qui est l'instance renvoyée lors du dernier appel à la fonction. Elle sera nulle la première fois que la fonction sera appelée et nous créons notre QSGGeometryNode que nous remplirons avec une géométrie et un matériau.

        geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
        geometry->setLineWidth(2);
        geometry->setDrawingMode(QSGGeometry::DrawLineStrip);
        node->setGeometry(geometry);
        node->setFlag(QSGNode::OwnsGeometry);

Nous créons ensuite la géométrie et l'ajoutons au nœud. Le premier argument du constructeur de QSGGeometry est une définition du type de vertex, appelée "ensemble d'attributs". Étant donné que les graphiques souvent utilisés en QML s'articulent autour de quelques jeux d'attributs standard communs, ceux-ci sont fournis par défaut. Nous utilisons ici le jeu d'attributs Point2D qui comporte deux valeurs flottantes, l'une pour les coordonnées x et l'autre pour les coordonnées y. Le deuxième argument est le nombre de vertex.

Il est également possible de créer des ensembles d'attributs personnalisés, mais cela n'est pas abordé dans cet exemple.

Comme nous n'avons pas de besoins particuliers en matière de gestion de la mémoire pour la géométrie, nous spécifions que la géométrie doit appartenir à QSGGeometryNode.

Pour minimiser les allocations, réduire la fragmentation de la mémoire et améliorer les performances, il serait également possible de faire de la géométrie un membre d'une sous-classe de QSGGeometryNode, auquel cas nous n'aurions pas défini l'indicateur QSGGeometryNode::OwnsGeometry.

        auto *material = new QSGFlatColorMaterial;
        material->setColor(QColor(255, 0, 0));
        node->setMaterial(material);
        node->setFlag(QSGNode::OwnsMaterial);

L'API du graphe de scène fournit quelques implémentations de matériaux couramment utilisées. Dans cet exemple, nous utilisons QSGFlatColorMaterial, qui remplit la forme définie par la géométrie d'une couleur unie. Une fois de plus, nous transmettons la propriété du matériau au nœud, afin qu'il puisse être nettoyé par le graphe de scène.

    } else {
        node = static_cast<QSGGeometryNode *>(oldNode);
        geometry = node->geometry();
        geometry->allocate(m_segmentCount);
    }

Dans le cas où l'élément QML a changé et que nous voulons seulement modifier la géométrie du nœud existant, nous transformons le oldNode en une instance de QSGGeometryNode et extrayons sa géométrie. Si le nombre de segments a changé, nous appelons QSGGeometry::allocate() pour nous assurer que le nombre de sommets est correct.

    QSizeF itemSize = size();
    QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
    for (int i = 0; i < m_segmentCount; ++i) {
        qreal t = i / qreal(m_segmentCount - 1);
        qreal invt = 1 - t;

        QPointF pos = invt * invt * invt * m_p1
                    + 3 * invt * invt * t * m_p2
                    + 3 * invt * t * t * m_p3
                    + t * t * t * m_p4;

        float x = pos.x() * itemSize.width();
        float y = pos.y() * itemSize.height();

        vertices[i].set(x, y);
    }
    node->markDirty(QSGNode::DirtyGeometry);

Pour remplir la géométrie, nous extrayons d'abord le tableau de sommets. Comme nous utilisons l'un des ensembles d'attributs par défaut, nous pouvons utiliser la fonction de commodité QSGGeometry::vertexDataAsPoint2D(). Ensuite, nous parcourons chaque segment et calculons sa position, puis nous écrivons cette valeur dans le vertex.

    return node;
}

À la fin de la fonction, nous renvoyons le nœud afin que le graphe de scène puisse le rendre.

Point d'entrée de l'application

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);

    QQuickView view;
    QSurfaceFormat format = view.format();
    format.setSamples(16);
    view.setFormat(format);
    view.setSource(QUrl("qrc:///scenegraph/customgeometry/main.qml"));
    view.show();

    return app.exec();
}

L'application est une application QML simple, avec un QGuiApplication et un QQuickView auquel nous transmettons un fichier .qml.

    QML_ELEMENT

Pour utiliser l'élément BezierCurve, nous devons l'enregistrer dans le moteur QML, en utilisant la macro QML_ELEMENT. Cela lui donne le nom BezierCurve et l'intègre au module CustomGeometry 1.0 tel qu'il est défini dans les fichiers de construction du projet :

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

cmake_minimum_required(VERSION 3.16)
project(customgeometry_declarative LANGUAGES CXX)

find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick)

qt_standard_project_setup()

qt_add_executable(customgeometry_declarative WIN32 MACOSX_BUNDLE
    beziercurve.cpp beziercurve.h
    main.cpp
)

target_link_libraries(customgeometry_declarative PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Quick
)

qt_add_qml_module(customgeometry_declarative
    URI CustomGeometry
    QML_FILES main.qml
    RESOURCE_PREFIX /scenegraph/customgeometry
    NO_RESOURCE_TARGET_PATH
)

install(TARGETS customgeometry_declarative
    BUNDLE  DESTINATION .
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

qt_generate_deploy_qml_app_script(
    TARGET customgeometry_declarative
    OUTPUT_SCRIPT deploy_script
    MACOS_BUNDLE_POST_BUILD
    NO_UNSUPPORTED_PLATFORM_ERROR
    DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})
TARGET = customgeometry
QT += quick

CONFIG += qmltypes
QML_IMPORT_NAME = CustomGeometry
QML_IMPORT_MAJOR_VERSION = 1

SOURCES += \
    main.cpp \
    beziercurve.cpp

HEADERS += \
    beziercurve.h

RESOURCES += customgeometry.qrc

target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/customgeometry
INSTALLS += target

Comme la courbe de Bézier est dessinée sous forme de bandes de lignes, nous spécifions que la vue doit être multi-échantillonnée pour obtenir un anticrénelage. Cette opération n'est pas obligatoire, mais elle permet d'améliorer l'aspect de l'élément sur le matériel qui la prend en charge. Le multi-échantillonnage n'est pas activé par défaut car il entraîne souvent une utilisation plus importante de la mémoire.

Utilisation de l'élément

import QtQuick
import CustomGeometry

Notre fichier .qml importe le module QtQuick 2.0 pour obtenir les types standard ainsi que notre propre module CustomGeometry 1.0 qui contient nos objets BezierCurve nouvellement créés.

Item {
    width: 300
    height: 200

    BezierCurve {
        id: line
        anchors.fill: parent
        anchors.margins: 20

Nous créons ensuite notre élément racine et une instance de courbe de Bézier que nous ancrons pour remplir la racine.

        property real t
        SequentialAnimation on t {
            NumberAnimation { to: 1; duration: 2000; easing.type: Easing.InOutQuad }
            NumberAnimation { to: 0; duration: 2000; easing.type: Easing.InOutQuad }
            loops: Animation.Infinite
        }

        p2: Qt.point(t, 1 - t)
        p3: Qt.point(1 - t, t)
    }

Pour rendre l'exemple un peu plus intéressant, nous ajoutons une animation qui modifie les deux points de contrôle de la courbe. Les points d'extrémité restent inchangés.

    Text {
        anchors.bottom: line.bottom

        x: 20
        width: parent.width - 40
        wrapMode: Text.WordWrap

        text: qsTr("This curve is a custom scene graph item, implemented using line strips")
    }
}

Enfin, nous superposons un court texte décrivant ce que l'exemple montre.

Exemple de projet @ code.qt.io

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