Vertex Skinning
Introduction
Qt Quick 3D supports vertex skinning for skeletal animation of mesh geometries.
See the Simple Skinning Example for a practical demonstration of skeletal animation.
In most cases, application developers will not be using the skinning API manually. The normal workflow is to use an external content creation tool to define the skeleton and the skin (this is sometimes also referred to as rigging), and then use the Balsam Asset Import Tool to convert the asset to Qt Quick 3D's native format.
Defining a skeleton
The basis of skeletal animation is the Skeleton. This is an abstract representation of how the model can move, inspired by how a physical skeleton works for vertebrates. The "bones" of the skeleton is represented by a hierarchy of Joint nodes. These do not necessarily need to represent actual bones, of course.
Skeleton { id: qmlskeleton Joint { id: joint0 index: 0 skeletonRoot: qmlskeleton Joint { id: joint1 index: 1 skeletonRoot: qmlskeleton } Joint { id: joint2 index: 2 skeletonRoot: qmlskeleton } } }
Connecting a skeleton to a model
To apply a skeleton to a model, set the model's skeleton property:
Model { skeleton: qmlskeleton ...
In order for the skeleton to have an effect, the model's geometry needs to include skinning information. This is done by including vertex attributes with JointSemantic
and WeightSemantic
in the vertex buffer.
The JointSemantic
attribute determines which of the joints in the skeleton can influence a given vertex. This uses the index values specified by Joint.index. Since this attribute contains 4 indexes, a maximum of 4 joints can influence one vertex.
The WeightSemantic
attribute describes the strength of the influence of those joints. It contains four floating point values, each value determining the weight given to the joint with the index at the corresponding position in the JointSemantic
attribute.
For example, given the skeleton above, if a vertex has these attributes:
JointSemantic attribute | WeightSemantic attribute |
---|---|
QVector4D(2, 0, 0, 0) | QVector4D(1.0, 0.0, 0.0, 0.0) |
that vertex will be 100% influenced by joint2, and it will move exactly as much as that joint. The last three indexes in the JointSemantic
attribute are ignored since the corresponding weights are 0.0
.
As another example, with these attributes:
JointSemantic attribute | WeightSemantic attribute |
---|---|
QVector4D(1, 2, 0, 0) | QVector4D(0.5, 0.25, 0.0, 0.0) |
the vertex will be moved by 50% of joint1's movement plus 25% of joint2's movement.
In addition, since the skeleton is an abstract representation, the model need to specify geometry information for the joints. For performance reasons, this is not done by specifying the information directly. Instead, Model.inverseBindPoses contains the inverse of the transformation matrix needed to move each joint to its initial position.
Animating the skeleton
Transforming a joint in a skeleton will move all vertexes connected to that joint. Since Joint inheriths from Node, a skeleton can be animated simply by using standard QML animations.
NumberAnimation { target: joint1 property: "eulerRotation.z" duration: 5000 from: -90 to: 90 running: true }
While it is possible to create complex animations by nesting SequentialAnimation, ParallelAnimation and NumberAnimation, it is generally more convenient to use timeline animations for animating skinned models.