このページでは

Qt Quick 3D - 簡単なスキニングの例

Qt Quick 3Dで簡単なスキニングアニメーションをレンダリングする方法を解説します。

骨格の変形を示す2つのアニメーション図形

一般的に、ほとんどのスキニングアニメーションはモデリングツールで設計されますが、Quick3DではBalsamインポーターを通じてglTF形式もサポートしており、 Qt Design Studio。この例では、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
]

頂点の位置と形状

ジョイントとウェイトのデータ

各頂点については、スキニング処理中にその頂点に影響を与えるべきジョイントのインデックスを指定する必要があります。各頂点について、これらのインデックスを4次元ベクトルとして格納します(Qtでは、1つの頂点に影響を与えるジョイントの数を4つまでに制限しています)。 このジオメトリにはジョイントノードが2つ(0と1)しかありませんが、4次元ベクトルを使用するため、残りの2つのジョイントインデックスとその重みを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
        }
    }
}

2つのJointは、Skeleton で接続されています。joint1 をz軸を中心に45度回転させます。以下の画像は、ジオメトリ内でのジョイントの配置と、初期のスケルトンの向きを示しています。

ジオメトリ内のジョイント初期スケルトン

ジオメトリ内に2つのジョイントがある

初期の骨格

inverseBindPosesを使用したモデルの配置

モデルに有効なskeleton が設定されると、スケルトンの初期ポーズを定義する必要があります。これにより、スケルトンアニメーションの基準が確立されます。ジョイントを初期位置から移動させると、joints およびweights テーブルに従ってモデルの頂点が移動します。 各ノードのジオメトリは、独特な方法で指定されます。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)
]

初期位置

InversebindPoses によって変換されました

Joint ノードを使ったアニメーション

スキニングされたオブジェクトの準備ができたので、Jointsのプロパティ、具体的には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
        }
    }
}

より包括的なスキニングの手法

スケルトンはリソースですが、その階層構造と位置はモデルのトランスフォーメーションに使用されます。

Skeleton ノードの代わりに、リソースタイプの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 にはjointsとinverseBindPosesの2つのリストしか含まれていないことがわかります。これは、階層構造を持たず、既存のノードの階層構造をそのまま利用する点で、Skeleton のアプローチとは異なります。

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

サンプルプロジェクト @ code.qt.io

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