3D空間で回転を計算する時によく使われるQuaternion(四元数)について簡単にまとめます。
ゲーム開発で必要な知識のみ。深入りはしません。
Quaternionとその表現方法
Quaternionとは複素数のように、虚数部分を持つ数です。
ただし複素数と違い、虚数単位を3つ持ちます。
、
、
、
は実数、
、
、
は虚数単位です。
上記の式は各実数を便宜的に4次元ベクトルの要素とみなして次のようにも表現されます。
さらに便宜的に虚数部のを3次元ベクトル
とみなすことで次のようにも表現されます。
まとめると以下のようになります。
表現の形が違うだけですべてQuaternionを表しています。
基礎知識
Quaternionで回転の計算をするのに必要な基礎知識がいくつかあります。
まず、Quaternionをベクトルとみなしたとき、
ベクトルと同様にノルム(長さ)が求められます。
次に、虚数部に-1を掛けたものを共役Quaternion として定義します。
Quaternionと共役Quaternionとの間には、
積がノルムの二乗になるという関係があります。
次に逆Quaternion を定義します。
逆Quaternionはは元のQuaternionとの積が1となるQuaternionです。
この式はここまでの知識を使って次のように変形できます。
したがって、 のノルムが1(単位Quaternion)ならば、
逆Quaternionと共役Quaternionは等しくなります。
Quaternionの積
Quaternion同士の積は次のように計算できます。
これを頑張って解くと次のようになります。
回転に使う
単位ベクトル を軸として
だけ回転させるとき、
まずは下記のようなQuaternionを定義します。
三次元のベクトルを上記のQuaternionで回転させたベクトル
を求めるには、
を求めます。
ただしQuaternionと次元数を合わせるために、計算上はとします。
ここでは単位ベクトルなので、
となります。(
)
よってなので、上の式は次のように変換できます。
実装してみる
さてここまでの内容をUnityで実装してみます。
using UnityEngine; namespace Sample { [System.Serializable] public struct Quaternion { public float x; public float y; public float z; public float w; /// <summary> /// 共役Quaternion /// </summary> private Quaternion Conjugated { get { return new Quaternion(-x, -y, -z, w); } } public static Quaternion operator*(Quaternion a, Quaternion b) { // Quaternion同士の積の計算 return new Quaternion( a.x * b.w + a.w * b.x - a.z * b.y + a.y * b.z, a.y * b.w + a.z * b.x + a.w * b.y - a.x * b.z, a.z * b.w - a.y * b.x + a.x * b.y + a.w * b.z, a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z ); } public static Vector3 operator *(Quaternion a, Vector3 b) { // ベクトルをQuaternionに変換 var bQuaternion = new Quaternion(b.x, b.y, b.z, 1); // q * p * q^-1 でベクトルを回転 var pos = a * bQuaternion * a.Conjugated; return new Vector3(pos.x, pos.y, pos.z); } /// <summary> /// 回転角度と回転軸からQuaternionを作成する /// </summary> public static Quaternion AngleAxis(float angle, Vector3 axis) { var rad = angle * Mathf.Deg2Rad; var halfRad = rad * 0.5f; var sin = Mathf.Sin(halfRad); var cos = Mathf.Cos(halfRad); axis.Normalize(); return new Quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos); } public Quaternion(float x, float y, float z, float w) { this.x = x; this.y = y; this.z = z; this.w = w; } } }
とりあえずQuaternion同士の積とVectorの回転をできるようにしました。
パフォーマンスとかは気にしてません。
これを使うスクリプトを書いてみます。
using UnityEngine; public class QuaternionSample : MonoBehaviour { private Sample.Quaternion _rotate; private void Awake() { transform.position = Vector3.up; _rotate = Sample.Quaternion.AngleAxis(1, Vector3.forward) * Sample.Quaternion.AngleAxis(1, Vector3.up); } private void Update() { transform.position = _rotate * transform.position; } }
QuaternionをAwake()であらかじめ合成して、Update()でベクトルを回転させています。
結果
前節のスクリプトを適当なGameObjectにアタッチして再生してみます。
うまく動いていそうです。