Qt Quick 3D - Simple Skinning Example

Demonstrates how to render a simple skinning animation in Qt Quick 3D.

Generally most skin animations will be designed by modeling tools and Quick3D also supports glTF formats by Balsam importer and Qt Design Studio. This example shows how each property is used for the skin animation in Quick3D.

Note: All the data in this example come from gfTF-Tutorial Skins.

Make a skinning geometry.

To use custom geometry data, we will define a geometry having positions, joints, weights, and indexes.

 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)

Each position is a vertex position and each vertex has 4 joint's indexes and corresponding weights.

Set up skinned data in QML

Position data and indexes

We will draw 8 trianges with 10 vertexes.

"Vertex positions and geomery"

 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
 ]
Joints and weights data

Our geometry will have just 2 joint nodes and remaining 2 joint data will be 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
 ]

Corresponding 2 weight values are as below.

 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
 ]

"2 joints in the geometry"

Skeleton and Joint hierarchy

These 2 Joints are connected in a Skeleton. We will rotate joint1 45 degrees around the z-axis. For skinning, Model should have a proper skeleton property.

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

"Initial Skeleton"

inverseBindPoses

We want to place our Model into the origin. It is possible to transform it but in this example, we will move it with a property inverseBindPoses. For two Joints' initial position we will simply give the same transform which translates -0.5 along the x-axis and -1.0 along the y-axis.

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

Animate with Joint nodes

"Final Scene"

Now, we prepared a skinned object and we can animate it with changing Joints' properties; position, rotation, scale.

Files: