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

文件

© 2024 Qt 公司有限公司。此处包含的文档贡献归其各自所有者所有。此处提供的文档根据 Free Software Foundation 发布的 GNU 自由文档许可证 1.3 版本 的条款发行。Qt 及相关标志是芬兰Qt公司以及其他国家和地区注册的商标。所有其他商标均为其各自所有者的财产。