Twitter @HigenekoTwitter #XNA
クォータニオンでボーン処理、その2:クォータニオンの使い方
今回はXNAフレームワーク内でのクォータニオンの使い方の紹介をします。クォータニオンは回転行列の代替として使えるので、Matrix構造体と同じメソッドが用意されています。そこで、同じ操作に対応する行列とクォータニオンの両方のコードを紹介していきます。
回転
回転行列を生成するのと同じようにクォータニオンでもCreateFromYawPitchRoll、CreateFromAxisAngleメソッドを使って任意の回転をするクォータニオンを生成できます。
// 回転行列の生成 Matrix m1 = Matrix.CreateFromYawPitchRoll(yaw, pitch, roll); Matrix m2 = Matrix.CreateFromAxisAngle(Vector3.Up, angle); // クォータニオンの生成 // 回転行列とまったく同じ意味のものを生成できる Quaternion q1 = Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll); Quaternion q2 = Quaternion.CreateFromAxisAngle(Vector3.Up, angle);
CreateFromYawPitchRollのそれぞれの引数はヨー(Yaw)、ピッチ(Pitch)、ロール(Roll)となっています。飛行機を例にすると、水平飛行状態で機首を左右に回転するのがヨー、機首を上下に回転させるのがピッチ、そして機体の前後を軸とした回転がロールになります。
回転していないクォータニオン(単位クォータニオン)
回転していない状態を表すクォータニオンは単位クォータニオンと呼びます。これも行列と同じように、Quaternion.Identityプロパティが使えます。
// 単位回転行列の生成(回転していない行列) Matrix mi = Matrix.Identity; // 単位クォータニオン(回転していないクォータニオン) Quaternion qi = Quaternion.Identity;
結合(Concatenate)
行列と同じように複数のクォータニオンを結合することで、複数の回転を組み合わせたものを生成することができます。q1 * q2のようにして結合ができますが、ここで注意が必要なのは行列の結合の順番とは逆になることです。たとえば、二つの行列をm1 * m2として’結合した場合、意味的には「m1の回転をした後に、m2の回転をする」となりますが、クォータニオンの結合、q1 * q2の場合は「q2の回転をした後に、q1の回転をする」と違った意味になります。
この原因はXNAフレームワークでは、q1 * q2は結合ではなく、数学的な二つのクォータニオンの乗算として定義されているからです。特に演算子オーバーロードは見た目が数式に近いものが書けるので、数学的な操作と同じように動作するようになっています。これは他の全ての数学関連のクラスや構造体内の演算子オーバーロード(*,/,+,-)でも同じルールに従っています。実はこれがVector3 * Matrixと書けない理由でもあります。
このままだと混乱するので明示的に結合を表すためにConcatenateメソッドがあります。
// 回転行列の結合 // m1の回転をした後に、m2の回転をした結果を計算する Matrix mtx = m1 * m2; // クォータニオンの結合 // 順番が行列とは逆になることに注意 // q2 * q1の意味はq1の回転をした後に、q2の回転をするという意味になる Quaternion quat = q2 * q1; // 上記の混乱を避けるために明示的にConcatenate(結合)メソッドを呼び出すことで // q1の回転の後に、q2の回転をした結果を計算できる quat = Quaternion.Concatenate(q1, q2);
頂点変換
行列を使っての頂点変換は多くの人が使っていると思いますが、クォータニオンを使っても同じように頂点変換ができます。
// 回転行列を使った頂点変換 Vector3 va = Vector3.Transform(position, m1); // クォータニオンを使った頂点変換 Vector3 vb = Vector3.Transform(position, q1);
クォータニオン、行列間の変換
クォータニオンから回転行列へ、また回転行列からクォータニオンへの変換は以下のメソッドを使ってすることができます。
// クォータニオン、行列間の変換 // クォータニオンから回転行列を生成 Matrix m = Matrix.CreateFromQuaternion(q1); // 回転行列からクォータニオンを生成する Quaternion q = Quaternion.CreateFromRotationMatrix(m1);
分解
前述の方法では回転行列とクォータニオン間での変換でしたが、時にはスケールや移動も含まれている行列から回転部分だけを取り出したい場合もあります。この時に便利なのがMatrix.Decomposeメソッドです。
// 任意の行列をスケール、回転(クォータニオン)、移動の三つの情報に分解する // もともとはコンテントパイプライン内でボーン情報をコンパクトにするために // 設計されたもので、重い処理なので大量の分解をリアルタイムにするのには向いていない Matrix boneMatrix = Matrix.Identity; Vector3 scale; Quaternion rotation; Vector3 translation; boneMatrix.Decompose(out scale, out rotation, out translation);
まとめ
と、いうわけで以上に紹介したようにクォータニオンを回転行列の変わり使えるということが解ったかと思います。注意点としては、結合の順番に気をつけるか、Concatenateメソッドを使うことくらいです。
次回はクォータニオンを使う醍醐味である回転の合成、補間について紹介します。
クォータニオンでボーン処理、その1:クォータニオンってなに?
クォータニオンの前知識なしでクォータニオンを使ったスキニングアニメーションのコードを理解するのは厳しいので、今回から数回に分けてクォータニオンを説明していきます。
クォータニオン(Quaterinon)は日本語で四元数(しげんすう)と言い、定義的には3つの虚数単位を持つ超複素数系のひとつです。
………
と、いってもなんのことかさっぱり解らなくても大丈夫です(たぶん)。本来であれば数学的な説明も入れるべきですが、今回はスキンアニメーションでの使われ方の説明なので、そういった部分は割愛します。より詳しく数学的な意味が知りたい人は「超複素数入門」、どの様に回転を表すかを知るためには英語ですが「Quaternions and Rotation Sequences」が良書ですし、日本語でも「実例で学ぶゲーム3D数学」が参考になると思います。
クォータニオンを簡単に説明すると、回転行列の変わりに使える便利な道具です。
行列の中で回転を表す部分は以下のようにRの部分、つまり3x3、9個の数値であらわします。XNAフレームワークの中では9個のfloatになります。
これがクォータニオンでは4個の数値、XNAフレームワーク内では4個のfloatで同じ回転を表すことができます。つまり、回転を表すのに必要なメモリが半分以下になる訳です。メモリ消費量だけで比べるのならx,y,z軸の回転角度を持つほうがより少ない消費量になりますが、オイラー角を使った場合、線形補間ができなかったり、行列への変換に三角関数を使う必要があったり、なにより面倒なジンバルロックの問題があったりと実際のゲーム開発では回転する範囲が制限されている場合以外ではオイラー角による回転が使われることは少なく、クォータニオンが使われるケースがほとんどです。
クォータニオンの利点をまとめると
と、いった感じになりますが、弱点としては
があります。
次回はXNAフレームワーク内でどのようにクォータニオンを使うのかを説明します。
2009/4/25 頂点テクスチャを使う方法を追加
骨を増やしたい
キャラクターなどを表示するときによく使われるスキンアニメーション。Creator Club Onlineにサンプルがありますが、指定できるボーン数が59個までとなっています。
このボーン数は微妙な数で、指間接や、顔の表情をコントロールするボーンがあるキャラクター作ると、この数には収まらないことがあります。通常、ゲームプレイ中はそこまでのディテールはカットシーンでもない限り必要ないので、手首から先の部分と、首から上の部分は別モデルとして表示することでボーン数の制限内に収めることはもちろん、必要のないときのアニメーション処理を省くことでパフォーマンス的にも有効な手段です。
とはいえ、この分割作業は面倒なのも事実で、仕事なら仕方がないですが趣味で作るゲームでは楽して作りたいと思うのは当然のことです。
使用できるボーン数を増やすには以下の4つがあります。
今回は最も手軽な1の手法を紹介します。
float4x3を使う
XNAフレームワークのMatrix構造体は4x4の行列で、回転とスケール部分をR、移動部分をTとすると、
のようになり、キャラクターのボーンに指定する行列、ワールド行列、ビュー行列では一番右側の列は常に同じ値になります。ただしプロジェクション行列では一番右側の部分も使われることに注意してください。
ようするに、キャラクターのボーンに使われる行列では左側の部分、つまり4x3の部分だけが必要で右側の部分の数値を定数レジスタに入れる必要がないということです。
この実装は簡単で、スキンアニメーションサンプル内のSkinnedModel.fxファイル内の三ヶ所を変更するだけです。
元のソースが
float4x4 Bones[MaxBones]; //... VS_OUTPUT VertexShader(VS_INPUT input) { // ... // Blend between the weighted bone matrices. float4x4 skinTransform = 0; //... // Skin the vertex position. float4 position = mul(input.Position,skinTransform);
と、なっているところを(//...となっている部分には他のコードが書いてある)以下のように変更するだけです。
float4x3 Bones[MaxBones]; //... VS_OUTPUT VertexShader(VS_INPUT input) { // ... // Blend between the weighted bone matrices. float4x3 skinTransform = 0; //... // Skin the vertex position. float4 position = float4( mul(input.Position,skinTransform), 1 );
変更部分はfloat4x4をfloat4x3にし、頂点変換部分をfloat4( mul( input.Position, skinTransform), 1)とするだけです。
これで79ボーンまで使えるようになります。この変更をしたときに同ファイル内のMaxBonesの値と、SkinnedModelProcessor.cs内にあるMaxBonesを変更するのを忘れないようにしてください。
ここまで読んで「エフェクトのパラメータ設定部分はどうするの?」と思った人もいると思います。
その疑問の答えは「何も変えなくていい」です。
EffectParameter.SetValueにMatrixやMatrixの配列を指定した場合、このメソッドは対応するシェーダー側の変数が要求する分だけの値を設定します。今回のようにfloat4x3のときはもちろん、float3x3、float2x2、float4x1の場合でもC#側で指定した行列の該当する部分の値が定数レジスタに設定されるようになっています。
また、行列形の変数の場合、使用する定数レジスタ数はシェーダー側で指定した行列の「列」の数と同じになることに注意してください、float4x3では3個になりますが、float3x4では使用する定数レジスタは4個になります。