プロジェクト演習Ⅳ・Ⅵ インタラクティブゲーム制作 第7回 クォータニオンの使い方 ~恐るべき1/4たまねぎの威力~
今日の内容 3DCGの根元 四元数(クォータニオン)の使い方 全てを握る行列 オイラー角の真実 数学的な立式や証明は極力排除 回転について考える 四元数(クォータニオン)の使い方 数学的な立式や証明は極力排除 ゲームに使える部分を可能な限り厳選
まずは3DCGの根元からおさらい 平行移動行列×回転行列×拡大縮小行列 全てのモデルやカメラは、4×4の行列によって「位置、姿勢、スケール」を保持している Direct3DもOpenGLも原理は同じ fk_Modelは自動的に変換してくれている OpenGLとDirect3Dでは乗算順が逆になるので注意 平行移動行列×回転行列×拡大縮小行列
それぞれの行列の作り方 平行移動 拡大縮小 回転行列 座標値を所定の位置にセットするだけ getPosition()の値 各軸の倍率を所定の 位置にセットするだけ getScale()の値 回転行列 一番面倒 最終的にはオイラー角の値を3つの回転行列に分割し、一定の順番で乗算することで作成 getAngle()の値を以下の順番で適用する Y軸中心に-h度回転 X軸中心にp度回転 Z軸中心に-b回転
オイラー角の罠 基礎演習で教えているオイラー角についての説明は「ウソではないが大事なことをかなり端折っている」と言える どの順番で、どういう法則で適用するのか、という情報が無いと意味がない ジンバルロックの危険性 2軸が重なり姿勢制御ができなくなる現象 定点的な姿勢を表現するのには問題ないが、連続的な回転を表現すると問題が起きやすい
オイラー角以外の 姿勢・回転制御方法 最終的にはオイラー角に直さなくてはならないが、途中の計算に異なる形式を用いることでフォローが可能 方向ベクトル&アップベクトル 真正面の向きと、頭の向きを指定する姿勢表現 直感的で良いが、回転を表すのには不向き ベクトルを合成すると零ベクトルになる可能性がある axis-angle 任意の軸を中心に何度回転しろ、という操作 あくまで回転表現なので、姿勢表現には不向き その代わりジンバルロックは起きない glRotateWithVec()で実現可能
意味がわかんねぇ! そこで、クォータニオン 姿勢、回転のどちらも表すことができる 補間計算が容易で強力 唯一の難点は… ジンバルロックも心配ない 唯一の難点は… 意味がわかんねぇ!
クォータニオンの正体とは 以下の数式で表される複素数 ベクトルやオイラー角と違い、各成分の値に直接的な意味を見いだすことは困難 fk_Quaternionクラスを利用することを推奨 Direct3Dでも無理矢理部分的に併用しよう
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とベクトルの乗算)