Qt Quick 3D - Benutzerdefinierte Morphing-Animation
Demonstriert das Schreiben einer benutzerdefinierten Geometrie in C++ mit einem Morph-Ziel.
Dieses Beispiel zeigt, wie man eine komplexe benutzerdefinierte Geometrie in C++ definiert, die eine Basisform und ein Morph-Ziel enthält, mit Normalenvektoren für beide.
Benutzerdefinierte Geometrie
Der Hauptteil dieses Beispiels ist die Erstellung einer benutzerdefinierten Geometrie mit einem Morph-Ziel. Dazu wird die Unterklasse QQuick3DGeometry verwendet:
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
vom Konstruktor aus auf, und wenn sich eine Eigenschaft geändert hat.
Die Funktion calculateGeometry
enthält all die mühsame Mathematik zur Berechnung der Formen und Normalenvektoren. Sie ist spezifisch für dieses Beispiel, und der Code wird nicht im Detail erklärt. Generell gilt: Um eine glatte Schattierung zu implementieren, ist es notwendig, den Normalenvektor für jeden Scheitelpunkt zu berechnen. Mathematisch gesehen kann der Normalenvektor aus den partiellen Ableitungen der Funktion, die die Ebene beschreibt, berechnet werden:
In diesem Beispiel machen wir es uns einfach, indem wir eine Kosinuswelle für die Grundform verwenden und wissen, dass ihre Ableitung eine Sinusfunktion ist.
In der Praxis können die Normalenvektoren oft durch geometrische Überlegungen bestimmt werden. Für das Morph-Ziel nutzen wir die Tatsache, dass jeder Vektor vom Mittelpunkt einer Kugel zur Oberfläche an diesem Punkt normal zur Kugel ist. Beachten Sie, dass Normalenvektoren in QtQuick3D eine Einheitslänge haben müssen, was durch die Verwendung von QVector3D::normalized() erreicht werden kann.
QML-Teil
Wir definieren ein Morph-Ziel, das demjenigen entspricht, das wir in der benutzerdefinierten Geometrie erstellt haben, und führen eine Animation für das Gewicht durch, damit 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 } }
Schließlich erstellen wir ein Modell mit unserer benutzerdefinierten Geometrie und wenden das Morph-Target darauf an:
Model { y: -1 geometry: MorphGeometry {} morphTargets: [ morphtarget ] materials: [ material ] }
© 2025 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.