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

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

Quaternionとその表現方法

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

q=xi+yj+zk+w

xyzwは実数、ijk虚数単位です。
上記の式は各実数を便宜的に4次元ベクトルの要素とみなして次のようにも表現されます。

q=(x,y,z,w)

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

q=(v,w)

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

q=xi+yj+zk+w=(x,y,z,w)=(v,w)

基礎知識

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

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

|q|=x2+y2+z2+w2

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

q=xiyjzk+w

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

qq=|q|2

次に逆Quaternion q1を定義します。
逆Quaternionはは元のQuaternionとの積が1となるQuaternionです。

qq1=1

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

q1=1q=q|q|2

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

q1=q

Quaternionの積

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

q1q2=(v1×v2+w2v1+w1v2,w1w2v1v1)

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

q1q2=(x1w2+w1x2z1y2+y1z2,y1w2+z1x2+w1y2x1z2,z1w2y1x2+x1y2+w1z2,w1w2x1x2y1y2z1z2)

回転に使う

単位ベクトルu=(u1,u2,u3) を軸としてθだけ回転させるとき、 まずは下記のようなQuaternionを定義します。

q=(usin(θ2),cos(θ2))

三次元のベクトルp=(p1,p2,p3)を上記のQuaternionで回転させたベクトルpを求めるには、

p=qpq

を求めます。
ただしQuaternionと次元数を合わせるために、計算上はp=(p1,p2,p3,1)とします。

ここでuは単位ベクトルなので、|q|=1となります。(sin2θ+cos2θ=1
よってq1=qなので、上の式は次のように変換できます。

p=qpq1

実装してみる

さてここまでの内容を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