【数学】ゲーム開発で覚えておくべきQuaternion(クォータニオン/四元数)の性質

3D空間で回転を計算する時によく使われるQuaternion(四元数)について簡単にまとめます。
ゲーム開発で必要な知識のみ。深入りはしません。

Quaternionとその表現方法

Quaternionとは複素数のように、虚数部分を持つ数です。
ただし複素数と違い、虚数単位を3つ持ちます。

 { \displaystyle
q = xi + yj + zk + w
}

 {x} {y} {z} {w}は実数、 {i} {j} {k}虚数単位です。
上記の式は各実数を便宜的に4次元ベクトルの要素とみなして次のようにも表現されます。

 { \displaystyle
q = (x, y, z, w)
}

さらに便宜的に虚数部の {(x, y, z)}を3次元ベクトル { \vec{v}}とみなすことで次のようにも表現されます。

 { \displaystyle
q = ( \vec{v} , w)
}

まとめると以下のようになります。
表現の形が違うだけですべてQuaternionを表しています。

 { \displaystyle
q = xi + yj + zk + w = (x, y, z, w) = ( \vec{v} , w)
}

基礎知識

Quaternionで回転の計算をするのに必要な基礎知識がいくつかあります。

まず、Quaternionをベクトルとみなしたとき、
ベクトルと同様にノルム(長さ)が求められます。

 { \displaystyle
|q|=\sqrt{x^{2} + y^{2} + z^{2} + w^{2}}
}

次に、虚数部に-1を掛けたものを共役Quaternion {q^{*}}として定義します。

 { \displaystyle
q^{*}= -xi - yj - zk + w
}

Quaternionと共役Quaternionとの間には、
積がノルムの二乗になるという関係があります。

 { \displaystyle
qq^{*}=|q|^{2}
}

次に逆Quaternion {q^{-1}}を定義します。
逆Quaternionはは元のQuaternionとの積が1となるQuaternionです。

 { \displaystyle
qq^{-1}= 1
}

この式はここまでの知識を使って次のように変形できます。

 { \displaystyle
q^{-1}= \frac{1}{q} = \frac{q^{*}}{|q|^{2}}
}

したがって、 {q}のノルムが1(単位Quaternion)ならば、
逆Quaternionと共役Quaternionは等しくなります。

 { \displaystyle
q^{-1}= q^{*}
}

Quaternionの積

Quaternion同士の積は次のように計算できます。

 { \displaystyle
q_{1} q_{2} = (\vec{v_{1}} \times \vec{v_{2}} + w_{2} \vec{v_{1}} + w_{1} \vec{v_{2}}, w_{1} w_{2} - \vec{v_{1}} \cdot \vec{v_{1}})
}

これを頑張って解くと次のようになります。

 { \displaystyle \begin{eqnarray}
q_{1} q_{2} = (x_{1} w_{2} + w_{1}x_{2} - z_{1}y_{2} + y_{1}z_{2}, \\
y_{1} w_{2} + z_{1}x_{2} + w_{1}y_{2} - x_{1}z_{2}, \\
z_{1} w_{2} - y_{1}x_{2} + x_{1}y_{2} + w_{1}z_{2}, \\
w_{1} w_{2} - x_{1}x_{2} - y_{1}y_{2} - z_{1}z_{2})
\end{eqnarray} }

回転に使う

単位ベクトル { \vec{u} = (u_{1}, u_{2}, u_{3}) } を軸として { \theta }だけ回転させるとき、 まずは下記のようなQuaternionを定義します。

 { \displaystyle
q = \big(\vec{u} sin \big( \frac{\theta}{2} \big), cos \big( \frac{\theta}{2} \big)\big)
}

三次元のベクトル {p = (p_{1}, p_{2}, p_{3})}を上記のQuaternionで回転させたベクトル {p'}を求めるには、

 { \displaystyle
p' = qpq^{*}
}

を求めます。
ただしQuaternionと次元数を合わせるために、計算上は {p = (p_{1}, p_{2}, p_{3}, 1)}とします。

ここで { \vec{u}}は単位ベクトルなので、 { |q| = 1}となります。( { sin^{2} \theta + cos^{2} \theta = 1 }
よって {q^{-1}= q^{*}}なので、上の式は次のように変換できます。

 { \displaystyle
p' = qpq^{-1}
}

実装してみる

さてここまでの内容を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にアタッチして再生してみます。

f:id:halya_11:20180803140131g:plain

うまく動いていそうです。

関連

light11.hatenadiary.com

参考

qiita.com

http://www.mss.co.jp/technology/report/pdf/18-07.pdf