Qt Quick 3D - Einfaches Skinning-Beispiel

Zeigt, wie man eine einfache Skinning-Animation in Qt Quick 3D rendert.

Im Allgemeinen werden die meisten Hautanimationen mit Modellierungswerkzeugen erstellt, und Quick3D unterstützt auch glTF-Formate durch den Balsam-Importer und Qt Design Studio. Dieses Beispiel zeigt, wie jede Eigenschaft für die Hautanimation in Quick3D verwendet wird.

Hinweis: Alle Daten in diesem Beispiel stammen aus dem gfTF-Tutorial Skins.

Erstellen Sie eine Skinning-Geometrie.

Um benutzerdefinierte Geometriedaten zu verwenden, werden wir eine Geometrie mit Positionen, Gelenken, Gewichten und Indizes definieren.

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)

Jede Position ist eine Scheitelpunktposition und jeder Scheitelpunkt hat 4 Gelenkindizes und entsprechende Gewichte.

Einrichten von Skinned-Daten in QML

Positionsdaten und Indizes

Wir werden 8 Dreiecke mit 10 Scheitelpunkten zeichnen. Die folgende Tabelle zeigt den QML-Code und eine Visualisierung der Scheitelpunkte.

QML-CodeVisualisierte
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"

Daten zu Knotenpunkten und Gewichten

Für jeden Scheitelpunkt müssen die Indizes der Gelenke angegeben werden, die während des Häutungsprozesses einen Einfluss auf ihn haben sollen. Für jeden Vertex speichern wir diese Indizes als 4D-Vektoren (Qt begrenzt die Anzahl der Gelenke, die einen Vertex beeinflussen können, auf 4). Unsere Geometrie wird nur zwei Gelenkknoten haben (0 und 1), aber da wir 4D-Vektoren verwenden, setzen wir die verbleibenden zwei Gelenkindizes und ihre Gewichte auf 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
]

Die entsprechenden Gewichtungswerte lauten wie unten.

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
]
Skelett und Gelenkhierarchie

Für das Skinning fügen wir der Model eine Skeletteigenschaft hinzu:

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

Die beiden Jointsind in einem Skeleton verbunden. Wir werden joint1 um 45 Grad um die z-Achse drehen. Die folgenden Bilder zeigen, wie die Gelenke in der Geometrie platziert werden und wie das ursprüngliche Skelett ausgerichtet ist.

Gelenke in der GeometrieUrsprüngliches Skelett

"2 joints in the geometry"

"Initial Skeleton"

Platzieren von Modellen mit inverseBindPoses

Sobald ein Modell eine gültige skeleton hat, muss die Ausgangsposition des Skeletts definiert werden. Damit wird die Grundlinie für die Skelettanimation festgelegt: Das Bewegen eines Gelenks aus seiner Ausgangsposition führt dazu, dass sich die Eckpunkte des Modells gemäß den Tabellen joints und weights bewegen. Die Geometrie jedes Knotens wird auf eine besondere Weise spezifiziert: Model.inverseBindPoses wird auf die Inverse der Matrix gesetzt, die das Gelenk in seine Ausgangsposition transformieren würde. Um es in die Mitte zu verschieben, wird einfach dieselbe Transformation für beide Gelenke festgelegt: eine Matrix, die -0,5 entlang der x-Achse und -1,0 entlang der y-Achse verschiebt.

QML-CodeAusgangspositionErgebnis
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"

Animieren mit Gelenkknoten

Nachdem wir nun ein gehäutetes Objekt vorbereitet haben, können wir es animieren, indem wir die Eigenschaften von Jointändern, insbesondere die 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
        }
    }
}

Ein vollständigerer Ansatz für das Skinning

Das Skelett ist eine Ressource, aber seine Hierarchie und Position wird für die Transformation des Modells verwendet.

Anstelle eines Skeleton Knotens können wir den Ressourcentyp Skin verwenden. Da der Typ Skin kein räumlicher Knoten in der Szene ist, wird seine Position das Modell nicht beeinflussen. Ein minimaler Arbeits-Skin-Knoten besteht normalerweise aus einer Knotenliste, Gelenken und einer optionalen inversen Bindungsmatrix, inverseBindPoses.

Unter Verwendung des Skin Elements kann das vorherige Beispiel wie folgt geschrieben werden:

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

Aus dem Codeschnipsel können wir ersehen, dass Skin nur zwei Listen hat, eine Joints und eine inverseBindPoses, was sich von dem Skeleton Ansatz unterscheidet, da es keine Hierarchie hat und einfach die bestehende Knotenhierarchie verwendet.

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

Beispielprojekt @ code.qt.io

© 2025 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.