Qt Quick 3D - 简单蒙皮示例

演示了如何在 Qt Quick 3D 中渲染简单的蒙皮动画。

通常,大多数蒙皮动画将由建模工具设计,Quick3D 还支持 glTF 格式,通过 Balsam 导入器Qt 设计工作室。该示例展示了Quick3D中每个属性是如何用于蒙皮动画的。

注意:本示例中所有数据均来自gfTF-Tutorial Skins

制作蒙皮几何体。

要使用自定义几何体数据,我们将定义一个包含位置、关节、权重和索引的几何体。

Q_OBJECT
QML_NAMED_ELEMENT(SkinGeometry)
Q_PROPERTY(QList<QVector3D> positions READ positions WRITE setPositions NOTIFY positionsChanged)
Q_PROPERTY(QList<qint32> joints READ joints WRITE setJoints NOTIFY jointsChanged)
Q_PROPERTY(QList<float> weights READ weights WRITE setWeights NOTIFY weightsChanged)
Q_PROPERTY(QList<quint32> indexes READ indexes WRITE setIndexes NOTIFY indexesChanged)

每个位置是一个顶点位置,每个顶点有4个关节的索引及其相应的权重。

在 QML 中设置蒙皮数据。

位置数据和索引。

我们将使用 10 个顶点绘制 8 个三角形。下表显示了 QML 代码和顶点的可视化。

QML 代码可视化
positions: [
    Qt.vector3d(0.0, 0.0, 0.0), // vertex 0
    Qt.vector3d(1.0, 0.0, 0.0), // vertex 1
    Qt.vector3d(0.0, 0.5, 0.0), // vertex 2
    Qt.vector3d(1.0, 0.5, 0.0), // vertex 3
    Qt.vector3d(0.0, 1.0, 0.0), // vertex 4
    Qt.vector3d(1.0, 1.0, 0.0), // vertex 5
    Qt.vector3d(0.0, 1.5, 0.0), // vertex 6
    Qt.vector3d(1.0, 1.5, 0.0), // vertex 7
    Qt.vector3d(0.0, 2.0, 0.0), // vertex 8
    Qt.vector3d(1.0, 2.0, 0.0)  // vertex 9
]
indexes: [
    0, 1, 3, // triangle 0
    0, 3, 2, // triangle 1
    2, 3, 5, // triangle 2
    2, 5, 4, // triangle 3
    4, 5, 7, // triangle 4
    4, 7, 6, // triangle 5
    6, 7, 9, // triangle 6
    6, 9, 8  // triangle 7
]

"Vertex positions and geomery"

关节和权重数据。

每个顶点都需要指定在蒙皮过程中应对其产生影响的所有关节的索引。对于每个顶点,我们将这些索引存储为 4D 向量(Qt 将可能影响一个顶点的关节数量限制为 4)。我们的几何体将只有两个关节节点(0 和 1),但由于我们使用 4D 向量,我们将剩余的两个关节索引及其权重设置为 0。

joints: [
    0, 1, 0, 0, // vertex 0
    0, 1, 0, 0, // vertex 1
    0, 1, 0, 0, // vertex 2
    0, 1, 0, 0, // vertex 3
    0, 1, 0, 0, // vertex 4
    0, 1, 0, 0, // vertex 5
    0, 1, 0, 0, // vertex 6
    0, 1, 0, 0, // vertex 7
    0, 1, 0, 0, // vertex 8
    0, 1, 0, 0  // vertex 9
]

相应的权重值如下。

weights: [
    1.00, 0.00, 0.0, 0.0, // vertex 0
    1.00, 0.00, 0.0, 0.0, // vertex 1
    0.75, 0.25, 0.0, 0.0, // vertex 2
    0.75, 0.25, 0.0, 0.0, // vertex 3
    0.50, 0.50, 0.0, 0.0, // vertex 4
    0.50, 0.50, 0.0, 0.0, // vertex 5
    0.25, 0.75, 0.0, 0.0, // vertex 6
    0.25, 0.75, 0.0, 0.0, // vertex 7
    0.00, 1.00, 0.0, 0.0, // vertex 8
    0.00, 1.00, 0.0, 0.0  // vertex 9
]
骨骼和关节层次结构。

对于蒙皮,我们向Model添加一个骨骼属性

skeleton: qmlskeleton
Skeleton {
    id: qmlskeleton
    Joint {
        id: joint0
        index: 0
        skeletonRoot: qmlskeleton
        Joint {
            id: joint1
            index: 1
            skeletonRoot: qmlskeleton
            eulerRotation.z: 45
        }
    }
}

两个关节连接在骨骼中。我们将围绕 z 轴旋转joint1 45 度。下面的图像显示了关节在几何体中的位置以及初始骨骼的定向。

几何体中的关节初始骨骼

"2 joints in the geometry"

"Initial Skeleton"

使用 inverseBindPoses 放置模型。

一旦一个模型拥有一个有效的骨架,就必须要定义骨架的初始姿态。这定义了骨骼动画的基线:移动一个关节从其初始位置将根据关节权重表使模型的顶点移动。每个节点的几何体以一种独特的方式进行指定:Model.inverseBindPoses被设置为将关节转换到初始位置的矩阵的。为了将其移动到中心,我们将为两个关节设置相同的转换:沿x轴翻译-0.5,沿y轴翻译-1.0。

QML代码初始位置结果
inverseBindPoses: [
    Qt.matrix4x4(1, 0, 0, -0.5,
                 0, 1, 0, -1,
                 0, 0, 1, 0,
                 0, 0, 0, 1),
    Qt.matrix4x4(1, 0, 0, -0.5,
                 0, 1, 0, -1,
                 0, 0, 1, 0,
                 0, 0, 0, 1)
]

"Initial position"

"Transformed by InversebindPoses"

使用关节节点动画

现在我们已经准备了一个着装物体,我们可以通过更改关节的属性来对其动画化,特别是eulerRotation

Timeline {
    id: timeline0
    startFrame: 0
    endFrame: 1000
    currentFrame: 0
    enabled: true
    animations: [
        TimelineAnimation {
            duration: 5000
            from: 0
            to: 1000
            running: true
        }
    ]

    KeyframeGroup {
        target: joint1
        property: "eulerRotation.z"

        Keyframe {
            frame: 0
            value: 0
        }
        Keyframe {
            frame: 250
            value: 90
        }
        Keyframe {
            frame: 750
            value: -90
        }
        Keyframe {
            frame: 1000
            value: 0
        }
    }
}

更完整的皮肤化方法

骨架是一个资源,但它的层次结构和位置用于模型的转换。

我们不仅可以使用骨架节点,还可以使用资源类型Skin。由于Skin类型不是场景中的空间节点,它的位置不会影响模型。最小的有效Skin节点通常由一个节点列表、关节和一个可选的逆绑定矩阵,inverseBindPoses组成。

使用Skin项,前面的示例可以这样编写

skin: Skin {
    id: skin0
    joints: [
        joint0,
        joint1
    ]
    inverseBindPoses: [
        Qt.matrix4x4(1, 0, 0, -0.5,
                     0, 1, 0, -1,
                     0, 0, 1, 0,
                     0, 0, 0, 1),
        Qt.matrix4x4(1, 0, 0, -0.5,
                     0, 1, 0, -1,
                     0, 0, 1, 0,
                     0, 0, 0, 1)
    ]
}

从代码片段中我们可以看到,Skin只有两个列表,一个是关节列表,另一个是inverseBindPoses,这与Skeleton方法不同,因为它没有层次结构,只是使用现有节点的层次结构。

Node {
    id: joint0
    Node {
        id: joint1
        eulerRotation.z: 45
    }
}

文件

© 2024 The Qt Company Ltd. 本文档中的文档贡献是相应所有者的版权。本文档的提供受GNU自由文档许可证版本1.3条款的约束,由Free Software Foundation发布。Qt和相应的标志是The Qt Company Ltd.在芬兰和/或在其他国家/地区的商标。所有其他商标均为其各自所有者的财产。