Qt Quick 3D - シンプルなスキニングの例

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

一般的に、ほとんどのスキンアニメーションはモデリングツールでデザインされますが、Quick3DはBalsamインポーターと Qt Design Studioを通してglTFフォーマットもサポートしています。この例では、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つに制限しています)。私たちのジオメトリにはジョイントノードが2つ(0と1)しかありませんが、4Dベクトルを使用するため、残りの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
        }
    }
}

つのJointSkeleton でつながっています。joint1 をz軸を中心に45度回転させます。以下の画像は、ジョイントがジオメトリにどのように配置され、初期スケルトンがどのように配向されているかを示しています。

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

"2 joints in the geometry"

"Initial Skeleton"

inverseBindPosesを使ったモデルの配置

モデルが有効なskeleton を持ったら、スケルトンの初期ポーズを定義する必要があります。これはスケルタルアニメーションのベースラインを定義します。ジョイントを初期位置から動かすと、jointsweights テーブルに従ってモデルの頂点が動きます。各ノードのジオメトリは特殊な方法で指定されます。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"

ジョイントノードを使ったアニメーション

スキニングされたオブジェクトを準備したので、Jointのプロパティ、特に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
        }
    }
}

スキニングへの、より完全なアプローチ

スケルトンはリソースですが、その階層と位置はModelのトランスフォームに使用されます。

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

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

プロジェクト例 @ code.qt.io

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 ここで提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。