Auf dieser Seite

Qt Quick 3D - Benutzerdefinierte Morphing-Animation

Veranschaulicht das Erstellen einer benutzerdefinierten Geometrie in C++ mit einem Morph-Ziel.

Orangefarbenes, wellenförmiges Objekt, das eine benutzerdefinierte Morphing-Animation demonstriert

Dieses Beispiel zeigt, wie man in C++ eine komplexe benutzerdefinierte Geometrie definiert, die eine Basisform und ein Morph-Ziel enthält, jeweils mit Normalenvektoren.

Benutzerdefinierte Geometrie

Der Hauptteil dieses Beispiels besteht darin, eine benutzerdefinierte Geometrie mit einem Morph-Ziel zu erstellen. Dazu erstellen wir eine Unterklasse von ` QQuick3DGeometry`:

class MorphGeometry : public QQuick3DGeometry
{
    Q_OBJECT
    QML_NAMED_ELEMENT(MorphGeometry)
    Q_PROPERTY(int gridSize READ gridSize WRITE setGridSize NOTIFY gridSizeChanged)

public:
    MorphGeometry(QQuick3DObject *parent = nullptr);

    int gridSize() { return m_gridSize; }
    void setGridSize(int gridSize);

signals:
    void gridSizeChanged();

private:
    void calculateGeometry();
    void updateData();

    QList<QVector3D> m_positions;
    QList<QVector3D> m_normals;
    QList<QVector4D> m_colors;

    QList<QVector3D> m_targetPositions;
    QList<QVector3D> m_targetNormals;
    QList<QVector4D> m_targetColors;

    QList<quint32> m_indexes;

    QByteArray m_vertexBuffer;
    QByteArray m_indexBuffer;
    QByteArray m_targetBuffer;

    int m_gridSize = 50;
    QVector3D boundsMin;
    QVector3D boundsMax;
};

Der Konstruktor definiert das Layout der Mesh-Daten:

MorphGeometry::MorphGeometry(QQuick3DObject *parent)
    : QQuick3DGeometry(parent)
{
    updateData();
}

Die Funktion ` updateData ` führt das eigentliche Hochladen der Mesh-Geometrie durch:

void MorphGeometry::updateData()
{
    clear();
    calculateGeometry();

    addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
                 QQuick3DGeometry::Attribute::ComponentType::F32Type);
    addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 3 * sizeof(float),
                 QQuick3DGeometry::Attribute::ComponentType::F32Type);
    addAttribute(QQuick3DGeometry::Attribute::ColorSemantic, 6 * sizeof(float),
                 QQuick3DGeometry::Attribute::ComponentType::F32Type);

    addTargetAttribute(0, QQuick3DGeometry::Attribute::PositionSemantic, 0);
    addTargetAttribute(0, QQuick3DGeometry::Attribute::NormalSemantic, m_targetPositions.size() * sizeof(float) * 3);
    addTargetAttribute(0, QQuick3DGeometry::Attribute::ColorSemantic,
                       m_targetPositions.size() * sizeof(float) * 3 + m_targetNormals.size() * sizeof(float) * 3);
    addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0,
                 QQuick3DGeometry::Attribute::ComponentType::U32Type);

    const int numVertexes = m_positions.size();
    m_vertexBuffer.resize(numVertexes * sizeof(Vertex));
    Vertex *vert = reinterpret_cast<Vertex *>(m_vertexBuffer.data());

    for (int i = 0; i < numVertexes; ++i) {
        Vertex &v = vert[i];
        v.position = m_positions[i];
        v.normal = m_normals[i];
        v.color = m_colors[i];
    }
    m_targetBuffer.append(QByteArray(reinterpret_cast<char *>(m_targetPositions.data()), m_targetPositions.size() * sizeof(QVector3D)));
    m_targetBuffer.append(QByteArray(reinterpret_cast<char *>(m_targetNormals.data()), m_targetNormals.size() * sizeof(QVector3D)));
    m_targetBuffer.append(QByteArray(reinterpret_cast<char *>(m_targetColors.data()), m_targetColors.size() * sizeof(QVector4D)));

    setStride(sizeof(Vertex));
    setVertexData(m_vertexBuffer);
    setTargetData(m_targetBuffer);
    setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
    setBounds(boundsMin, boundsMax);

    m_indexBuffer = QByteArray(reinterpret_cast<char *>(m_indexes.data()), m_indexes.size() * sizeof(quint32));
    setIndexData(m_indexBuffer);
}

Wir rufen ` updateData ` aus dem Konstruktor heraus auf sowie immer dann, wenn sich eine Eigenschaft geändert hat.

Die Funktion ` calculateGeometry ` enthält alle aufwendigen mathematischen Berechnungen zur Ermittlung der Formen und Normalenvektoren. Sie ist spezifisch für dieses Beispiel, weshalb der Code hier nicht im Detail erläutert wird. Allgemein gilt: Um eine glatte Schattierung zu implementieren, muss für jeden Scheitelpunkt der Normalenvektor berechnet werden. Mathematisch lässt sich der Normalenvektor aus den partiellen Ableitungen der Funktion berechnen, die die Ebene beschreibt:

Formel zur Berechnung des Normalenvektors

In diesem Beispiel vereinfachen wir die Sache, indem wir eine Kosinuswelle als Grundform verwenden, da wir wissen, dass deren Ableitung eine Sinusfunktion ist.

In der Praxis lassen sich Normalenvektoren oft durch geometrisches Denken bestimmen. Für das Morph-Ziel nutzen wir die Tatsache, dass jeder Vektor vom Mittelpunkt einer Kugel zur Oberfläche an diesem Punkt senkrecht zur Kugel steht. Beachten Sie, dass Normalenvektoren in „ QtQuick3D “ die Einheitslänge haben müssen, was durch die Verwendung von ` QVector3D::normalized()` erreicht werden kann.

QML-Teil

Wir definieren ein Morph-Ziel, das dem in der benutzerdefinierten Geometrie erstellten entspricht, und animieren das Gewicht, sodass es zwischen den beiden Formen wechselt:

MorphTarget {
    id: morphtarget
    attributes: MorphTarget.Position | MorphTarget.Normal | MorphTarget.Color
    SequentialAnimation on weight {
        PauseAnimation { duration: 1000 }
        NumberAnimation { from: 0; to: 1; duration: 4000 }
        PauseAnimation { duration: 1000 }
        NumberAnimation { from: 1; to: 0; duration: 4000 }
        loops: Animation.Infinite
    }
}

Abschließend erstellen wir ein Modell unter Verwendung unserer benutzerdefinierten Geometrie und wenden das Morph-Ziel darauf an:

Model {
    y: -1
    geometry: MorphGeometry {}
    morphTargets: [ morphtarget ]
    materials: [ material ]
}

Beispielprojekt @ 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.