プロジェクト演習Ⅳ インタラクティブゲーム制作 プログラミング4 2011/11/15 クォータニオンの使い方 ~恐るべき1/4たまねぎの威力~
今日の内容 3DCGの根元 クォータニオン (四元数)の使い方 全てを握る行列 オイラー角の真実 数学的な立式や証明は極力排除 回転について考える クォータニオン (四元数)の使い方 数学的な立式や証明は極力排除 ゲームに使える部分を可能な限り厳選
まずは3DCGの根元からおさらい 平行移動行列×回転行列×拡大縮小行列 全てのモデルやカメラは、4×4の行列によって「位置、姿勢、スケール」を保持している Direct3DもOpenGLも原理は同じ fk_Modelは自動的に変換してくれている XNAはModelの中のEffectsで設定する OpenGLとDirect3Dでは乗算順が逆になるので注意(MV系とVM系の違い) 平行移動行列×回転行列×拡大縮小行列
描画モデル以外に必要な行列 View行列 Projection行列 世界の全てを、視点の位置と方向を基準に変換する行列 モデル行列と内容は同じ(演算時には逆行列化して用いる) FKではfk_Modelの変数をfk_Sceneにエントリーして設定 XNAではEffectsに 個別設定 Projection行列 視点から見た風景を 2次元に畳み込む行列 近くの物は大きく、遠くの物は小さくなる FKではデフォルトの行列が設定済み 変更したい場合はfk_Sceneで設定 XNAではEffectsに 個別設定
モデル行列の作り方 平行移動 拡大縮小 回転行列 座標値を所定の位置にセットするだけ getPosition()の値 各軸の倍率を所定の 位置にセットするだけ getScale()の値 回転行列 一番面倒 最終的にはオイラー角の値を3つの回転行列に分割し、一定の順番で乗算することで作成 getAngle()の値を以下の順番で適用する Y軸中心に-h度回転 X軸中心にp度回転 Z軸中心に-b回転
オイラー角の罠 基礎演習で教えているオイラー角についての説明は「ウソではないが大事なことをかなり端折っている」と言える どの順番で、どういう法則で適用するのか、という情報が無いと意味がない ジンバルロックの危険性 2軸が重なり姿勢制御ができなくなる現象 定点的な姿勢を表現するのには問題ないが、連続的な回転を表現すると問題が起きやすい
オイラー角以外の 姿勢・回転制御方法 最終的にはオイラー角に直す必要があるが、途中計算に異なる形式を用いることでフォローが可能 方向ベクトル&アップベクトル 真正面の向きと、頭の向きを指定する姿勢表現 直感的で良いが、回転を表すのには不向き ベクトルを合成すると零ベクトルになる可能性がある axis-angle 任意の軸を中心に何度回転しろ、という操作 あくまで回転表現なので、姿勢表現には不向き 姿勢表現に使う場合は、ある基準の方向から「どの軸で何度回転したか」の順番を覚えておいて再現する必要がある その代わりジンバルロックは起きない
意味がわかんねぇ! そこで、クォータニオン 姿勢、回転のどちらも表すことができる 補間計算が容易で強力 唯一の難点は… ジンバルロックも心配ない 唯一の難点は… 意味がわかんねぇ!
クォータニオンの正体とは 以下の数式で表される複素数 ベクトルやオイラー角と違い、各成分の値に直接的な意味を見いだすことは困難 fk_Quaternionクラスを利用することを推奨 XNAにも標準で存在するので使ってみよう
2つの姿勢の補間姿勢を求める 多分一番よく使う利用方法 オイラー角で表された姿勢Aと姿勢Bの 線形補間(E=(1.0-t)A+tB)を行いたい オイラー角で直接この計算を行うと悲惨な ことになる クォータニオンを通すことで安全確実な変換ができる
コーディング例 // angA, angBには姿勢がセットされるとする fk_Angle angA, angB, angRes; fk_Quaternion qA, qB, qAns; double t = 0.5; // 任意の補間パラメータを指定 // オイラー角をそれぞれクォータニオンに変換 qA.makeEuler(angA); qB.makeEuler(angB); // 球面線形補間を使って補間クォータニオンを算出 qAns = fk_Math::quatInterSphere(qA, qB, t); // 補間したクォータニオンをオイラー角に変換 angRes = qAns.getEuler();
解説 makeEuler()でオイラー角→Q変換 getEuler()でQ→オイラー角変換 fk_Math::quatInterSphere() fk_Mathクラスのstaticメンバ関数 staticメンバとは、クラスのインスタンスを作らなくても利用できる関数 関数単体で機能する物はよくstatic化される
回転合成とベクトルの回転 こちらの利用方法も便利なので覚えよう setRotate(角度, x, y, z) 回転軸と角度を指定して、その回転を表す クォータニオンを作成する これのおかげでset()の出番はまず無い
回転合成は乗算で qAB = qA*qB; で実現可能 乗算の順序を入れ替えてしまうと意味合いが変わってしまうので注意 Aの回転後Bの回転を行うのと、 Bの回転後Aの回転を行うのでは異なる結果になる FKのオイラー角は以下の処理で変換できる (makeEuler()で実際に同じ処理をしている) qH.setRotate(-euler.h, 0, 1, 0); qP.setRotate(euler.p, 1, 0, 0); qB.setRotate(-euler.b, 0, 0, 1); qHPB = qH * qP * qB;
あるベクトルを 自由に回転させるには まずは好きな回転を表すQを作る その上で、以下の処理を呼べば終了 用途は色々考えられる vAfter = qRot * vBefore; 用途は色々考えられる 射撃武器による銃口補正や誘導補正 キャラやカメラをある地点に徐々に向ける モーションの合成(ちょっと高度だけど)
まとめ 3次元の回転表現は色々あり、 状況に応じて使い分けが必要 クォータニオンを用いることで、 補間や合成が容易になり、夢が広がる 最終的には行列に落とし込むが、最初から オイラー角ベースのみだと色々問題が多い クォータニオンを用いることで、 補間や合成が容易になり、夢が広がる 回転の合成(Q同士の乗算) オイラー角の補間(Q同士の線形補間) ベクトルの回転(Qとベクトルの乗算)
今日の課題 OBBのサンプルシーンで以下のような 処理を作ってみよう ロックオンポイントを3カ所作る ボタンを押す事に操作キャラがロックオンしている対象が切り替わる 操作キャラがロックオンポイントへ「滑らかに」向くようにする