Qt Quick 3D - Benutzerdefinierte Morphing-Animation
Veranschaulicht das Erstellen einer benutzerdefinierten Geometrie in C++ mit einem Morph-Ziel.

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:

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