Qt Quick 3D - 自定义变形动画
演示如何使用变形目标编写 C++ 自定义几何体。

本示例演示了如何在 C++ 中定义一个包含基础形状和变形目标的复杂自定义几何体,并为二者分别指定法线向量。
自定义几何体
本示例的核心内容是创建一个带有变形目标的自定义几何体。我们通过继承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;
};构造函数定义了网格数据的布局:
MorphGeometry::MorphGeometry(QQuick3DObject *parent)
: QQuick3DGeometry(parent)
{
updateData();
}updateData 函数负责实际上传网格几何体:
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);
}我们在构造函数中以及属性发生变化时调用 `updateData `。
calculateGeometry 函数包含了计算形状和法向量所需的所有繁琐数学运算。该函数专为此示例设计,此处不再详细解释代码。一般而言:要实现平滑着色,必须为每个顶点计算法向量。从数学上讲,法向量可通过描述该平面的函数的偏导数来计算:

在本例中,我们采用余弦波作为基础形状以简化计算,因为我们知道其导数是正弦函数。
实际上,法向量通常可以通过几何推理来确定。对于变形目标,我们利用了这样一个事实:从球体中心到球面的任何向量,在该点处都与球面垂直。请注意,在QtQuick3D 中,法向量必须是单位向量,这可以通过使用QVector3D::normalized()来实现。
QML 部分
我们定义一个与自定义几何体中创建的形态目标相对应的形态目标,并对权重进行动画处理,使其在两种形状之间循环切换:
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
}
}最后,我们使用自定义几何体创建一个模型,并将其与变形目标关联:
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.