Qt Quick 3D - Exemple de géométrie personnalisée

Démonstration de la fourniture de données de vertex personnalisées à partir de C++ et de QML.

Triangle avec texture du logo Qt démontrant une géométrie personnalisée

Cet exemple utilise QQuick3DGeometry et the geometry property du modèle pour rendre un maillage avec des coordonnées de vertex, de normales et de texture spécifiées à partir de C++ et de QML au lieu d'une ressource précuite.

En outre, l'application GridGeometry est également démontrée. GridGeometry est une implémentation intégrée de QQuick3DGeometry qui fournit un maillage avec des primitives de ligne adaptées à l'affichage d'une grille.

Cet exemple se concentre sur le code qui fournit la géométrie personnalisée, alors jetons d'abord un coup d'œil au fichier d'en-tête C++ de ExampleTriangleGeometry:

class ExampleTriangleGeometry : public QQuick3DGeometry
{
    Q_OBJECT
    QML_NAMED_ELEMENT(ExampleTriangleGeometry)
    Q_PROPERTY(bool normals READ normals WRITE setNormals NOTIFY normalsChanged)
    Q_PROPERTY(float normalXY READ normalXY WRITE setNormalXY NOTIFY normalXYChanged)
    Q_PROPERTY(bool uv READ uv WRITE setUV NOTIFY uvChanged)
    Q_PROPERTY(float uvAdjust READ uvAdjust WRITE setUVAdjust NOTIFY uvAdjustChanged)

public:
    ExampleTriangleGeometry();

    bool normals() const { return m_hasNormals; }
    void setNormals(bool enable);

    float normalXY() const { return m_normalXY; }
    void setNormalXY(float xy);

    bool uv() const { return m_hasUV; }
    void setUV(bool enable);

    float uvAdjust() const { return m_uvAdjust; }
    void setUVAdjust(float f);

signals:
    void normalsChanged();
    void normalXYChanged();
    void uvChanged();
    void uvAdjustChanged();

private:
    void updateData();

    bool m_hasNormals = false;
    float m_normalXY = 0.0f;
    bool m_hasUV = false;
    float m_uvAdjust = 0.0f;
};

La chose la plus importante à remarquer est que notre classe ExampleTriangleGeometry hérite de QQuick3DGeometry et que nous appelons la macro QML_NAMED_ELEMENT(ExampleTriangleGeometry), ce qui rend notre classe accessible en QML. Il y a également quelques propriétés définies par la macro Q_PROPERTY qui sont automatiquement exposées dans notre objet QML. Examinons maintenant le modèle QML :

Model {
    id: triangleModel
    visible: false
    scale: Qt.vector3d(100, 100, 100)
    geometry: ExampleTriangleGeometry {
        normals: cbNorm.checked
        normalXY: sliderNorm.value
        uv: cbUV.checked
        uvAdjust: sliderUV.value
    }
    materials: [
        PrincipledMaterial {
            Texture {
                id: baseColorMap
                source: "qt_logo_rect.png"
            }
            cullMode: PrincipledMaterial.NoCulling
            baseColorMap: cbTexture.checked ? baseColorMap : null
            specularAmount: 0.5
        }
    ]
}

Notez que nous spécifions la propriété geometry pour utiliser notre classe ExampleTriangleGeometry, avec les propriétés appropriées spécifiées. C'est tout ce qui est nécessaire du côté de QML pour utiliser une géométrie personnalisée.

Examinons maintenant l'autre partie importante du code C++, à savoir la méthode updateData(). Cette méthode crée et télécharge les données de notre géométrie personnalisée chaque fois qu'une classe ExampleTriangleGeometry est créée ou que l'une de ses propriétés QML est mise à jour.

void ExampleTriangleGeometry::updateData()
{
    clear();

    int stride = 3 * sizeof(float);
    if (m_hasNormals)
        stride += 3 * sizeof(float);
    if (m_hasUV)
        stride += 2 * sizeof(float);

    QByteArray vertexData(3 * stride, Qt::Initialization::Uninitialized);
    float *p = reinterpret_cast<float *>(vertexData.data());

    // a triangle, front face = counter-clockwise
    *p++ = -1.0f; *p++ = -1.0f; *p++ = 0.0f;
    if (m_hasNormals) {
        *p++ = m_normalXY; *p++ = m_normalXY; *p++ = 1.0f;
    }
    if (m_hasUV) {
        *p++ = 0.0f + m_uvAdjust; *p++ = 0.0f + m_uvAdjust;
    }
    *p++ = 1.0f; *p++ = -1.0f; *p++ = 0.0f;
    if (m_hasNormals) {
        *p++ = m_normalXY; *p++ = m_normalXY; *p++ = 1.0f;
    }
    if (m_hasUV) {
        *p++ = 1.0f - m_uvAdjust; *p++ = 0.0f + m_uvAdjust;
    }
    *p++ = 0.0f; *p++ = 1.0f; *p++ = 0.0f;
    if (m_hasNormals) {
        *p++ = m_normalXY; *p++ = m_normalXY; *p++ = 1.0f;
    }
    if (m_hasUV) {
        *p++ = 1.0f - m_uvAdjust; *p++ = 1.0f - m_uvAdjust;
    }

    setVertexData(vertexData);
    setStride(stride);
    setBounds(QVector3D(-1.0f, -1.0f, 0.0f), QVector3D(+1.0f, +1.0f, 0.0f));

    setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);

    addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
                 0,
                 QQuick3DGeometry::Attribute::F32Type);

    if (m_hasNormals) {
        addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
                     3 * sizeof(float),
                     QQuick3DGeometry::Attribute::F32Type);
    }

    if (m_hasUV) {
        addAttribute(QQuick3DGeometry::Attribute::TexCoordSemantic,
                     m_hasNormals ? 6 * sizeof(float) : 3 * sizeof(float),
                     QQuick3DGeometry::Attribute::F32Type);
    }
}

La méthode commence par appeler clear() pour effacer toutes les données précédemment téléchargées. Elle calcule ensuite le stride pour les sommets, en tenant compte de la présence de normales et de coordonnées uv. Un tableau d'octets est ensuite créé pour contenir la mémoire tampon des sommets, qui est ensuite remplie avec les sommets d'un triangle unique dont les angles sont situés en (-1, -1, 0), (1, -1, 0) et (0, 1, 0).

Les données relatives aux sommets sont ensuite téléchargées et la distance est définie en appelant setVertexData() et setStride(). Les limites de la géométrie sont définies en appelant setBounds. Bien qu'elle ne soit pas utilisée dans cet exemple, la définition des limites est nécessaire pour que les ombres fonctionnent. Ensuite, le type de primitive est défini en appelant setPrimitiveType(). Enfin, nous spécifions comment les attributs de position, de normalité et de coordonnées uv sont disposés en mémoire dans le tampon précédemment téléchargé en appelant addAttribute() pour chaque attribut.

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.