私は特にSMDフォーマットに精通していませんが、ここに行きます...
注:この回答は、オブジェクト/ノードの複合変換を構築する方法を知っていることを前提としています。これは、平行移動、回転、およびスケールを組み合わせたマトリックスです (SMD ではスケールは使用されていないようですが)。また、行列乗算、行列反転、行列*ベクトル乗算が使用されます。
骨とアニメーション
モデルのボーン ノードはツリーを形成します。nodes
ルート ボーン (セクション)を除いて、各ボーンには親ボーンがあります。各ノードには、独自のローカル変換 (位置と回転) があります。
ローカル ノード トランスフォーム: ノードのローカル トランスフォームは、その位置と回転から構築された 4x4 マトリックスであり、ローカル スペースから親ノードのスペースにポイントを変換します。親の空間のそのベクトル。これを行う方法の詳細については、このリンクを参照してください。詳しくは、Google で検索してください。
SMD では、ボーン トランスフォームはアニメーションのキーフレーム (セクション)でのみ定義されます。skeleton
「参照」SMD ファイルには、アニメーションの単一フレームがあります。モデルの参照位置での各ボーン ノードの位置と回転。
アニメーション SMD ファイルには、複数のフレームを持つアニメーション シーケンスがあり、それぞれが (いくつかの) ボーンに対して異なる変換を指定します。アニメーションを再生するときは、フレームの時間と現在のシーン/ゲーム時間に基づいてフレーム間を補間し、各ボーンの変換 (位置 + 回転) を考え出します。
静止骨の変換を取得する
前処理 (メッシュの読み込み時など) では、いわゆる「静止時」のボーン変換を計算する必要があります。これらは、参照位置にあるときの、各ボーンのモデルからボーンへの空間変換です。理由は次のとおりです。
すべての頂点位置はモデル空間で定義されますが、頂点を個々のボーンと一緒に移動させたいため、最終的に頂点変換はボーン空間から開始する必要があります。したがって、頂点の位置は最初にボーン空間に変換する必要があります。ここで、安静時ボーン トランスフォームの出番です。
したがって、私たちが探している静止時変換は、頂点をモデル空間からボーン空間に変換します。すべてのボーンを基準位置に置きます。ルート ノードからツリーをたどり、変換行列を連結します。たとえば、上腕ノードの場合、変換は次のようになります。
transform = root * spine * shoulderR * upperArmR
ただし、これは上腕の空間からモデル空間への変換です。したがって、単純にマトリックスを反転して、静止時のボーン変換を取得します。すべてのボーンに対してこれを行い、これらの行列を保存します。
保存時の変換は時間の経過とともに変化しないことに注意してください。モデルの参照位置に基づいて固定されます。
頂点/ボーンの関連付け
各頂点は、1 つ以上のボーン ノードに関連付けられています。このような関連付けごとに、頂点には対応する重みがあります。通常、すべての重みの合計は になり1
ます。SMD では、これらの関連付けはtriangles
セグメントで定義されます。リンク先のページに基づくと、形式は次のとおりです。
triangles
my_material
bone_id x y z nx ny nz u v bone_links
これは頂点を定義し(x, y, z)
、最初にそれをボーンに関連付けますbone_id
(重みは1
と仮定します)。このbone_links
セクションでは、これを (ある程度) オーバーライドして、次のように複数の関連付けを指定できます。
bone_links = num_links bone_id[0] weight[0] bone_id[1] weight[1] ... etc.
重みの合計が 1 にならない場合、残りの重みは元の との関連付けに使用されbone_id
ます。
したがって、ボーン 0、1、および 2 に関連付けられた頂点の例は次のようになります。
0 x y z nx ny nz u v 3 0 0.15 1 0.35 2 0.5
頂点変換
ここで、現在のボーン トランスフォームに基づいて頂点の位置を最終的に決定します。前述のとおり。現在の時間とアニメーションに基づいて、現在のボーン トランスフォームを決定 (補間) します。ボーンごとに、ボーンからワールドへの変換を計算します。例 (これは前に見ました):
boneToWorld = root * spine * shoulderR * upperArmR
ここで、単一のボーンに関連付けられた頂点の場合、アニメーション化/スキン化された位置は次のようになります。
vertexPosAnimated = boneToWorld * boneAtRest * vertexPosModel
これにより、最初に頂点の位置がモデル空間 ( vertexPosModel
) から関連付けられているボーンの空間に変換されます (この変換は時間が経っても変化しません)。次に、ボーンの現在の位置を使用して、頂点がボーンからモデル空間に再び変換されます。これにより、トランスフォームの変化に合わせてボーンを動かすことができます。
ボーンが現在静止位置にある場合、boneAtRest
は の逆でありboneToWorld
、boneToWorld * boneAtRest
単位行列も同様であるため、頂点の位置は変更されないままであり、これは正しいことです!
最後に、頂点は複数のボーンに関連付けることができるため、上記の代わりに、関連付けられたボーンごとに上記の加重合計を計算します。たとえば、3 つのボーンに関連付けられた頂点の場合:
vertexPosAnimated =
boneToWorld[0] * boneAtRest[0] * vertexPosModel * weight[0] +
boneToWorld[1] * boneAtRest[1] * vertexPosModel * weight[1] +
boneToWorld[2] * boneAtRest[2] * vertexPosModel * weight[2];
最終的な考え
これらはいくつかの大まかなストロークであり、シェーダーの実装については (それがあなたがやろうとしているのであれば) 議論していませんが、すべての原則をカバーしたと思います。それはすでにかなり長い答えです.
過去に 3D エンジンの開発に役立つと思ったものの 1 つに、M3G のドキュメント (古い Java Mobile 3D API) があります。のエントリは、SkinnedMesh
基本的に私がここに投稿したものについても説明しています。
また、静止時のボーン変換を使用して頂点をモデルからボーン空間に変換し、現在の変換を使用して戻すという概念を理解してみてください。それがすべての鍵です。
幸運を!