0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

四元数(クォータニオン)の基本とUnityでの回転処理

0
Posted at

はじめに

3D空間での回転を扱うとき、UnityではQuaternion型が標準で使われています。本記事では、四元数の数学的な定義から、なぜ回転に使われるのか、そして実際にUnityでどう扱うのかを整理します。

四元数とは

四元数(クォータニオン)は、実数部と3つの虚数部からなる数です。

q = w + xi + yj + zk

ここで $i, j, k$ は虚数単位で、次の関係を満たします。

i^2 = j^2 = k^2 = ijk = -1

例えば $1 + 1.5i + 2.3j + 2k$ のような値も四元数の一つです。この式自体は単なる数の組ですが、これを線形変換(回転)の関数として使うことで、3D空間上のある点を別の点へ移動させることができます。

四元数による回転(サンドイッチ積)

ある点を $p$、回転を表す四元数を $q$ とすると、次の計算によって点 $p$ を回転後の点 $p'$ に移すことができます。

p' = q\,p\,q^{-1}

なぜ両側からかけるのか

  • 点 $p$ は $(0, x, y, z)$ というスカラー部が0の「純虚四元数」として表現します。
  • 単純に $q p$ だけを計算すると、結果のスカラー部が0にならず、3D点として意味を持たなくなってしまいます。
  • $q,p,q^{-1}$ という「サンドイッチ」の形にすることで、計算結果が必ず純虚四元数になり、かつ正しく回転として機能することが数学的に保証されます。

回転を表す四元数は、回転軸 $\vec{v} = (v_x, v_y, v_z)$(単位ベクトル)と回転角 $\theta$ を使って以下のように構成されます。

q = \left(\cos\frac{\theta}{2},\ v_x \sin\frac{\theta}{2},\ v_y \sin\frac{\theta}{2},\ v_z \sin\frac{\theta}{2}\right)

オイラー角との比較

項目 オイラー角 四元数
ジンバルロック 起きる 起きない
補間のしやすさ 難しい(不連続になりやすい) Slerpで滑らかに補間可能
直感的な理解 しやすい(X/Y/Z軸回転) しにくい
パラメータ数 3個 4個(ノルム1に正規化)

VRやHMDのようにカメラや手のトラッキングを扱う場面では、ジンバルロックを避けられる四元数が特に重宝されます。

Unityでの実装

UnityのQuaternion型は、内部的にはここまで説明してきた数学とほぼ同じ仕組みで動いています。以下は、四元数の仕組みを確認しながら回転を扱うサンプルスクリプトです。

using UnityEngine;

/// <summary>
/// 四元数による回転の仕組みを確認するためのサンプル。
/// Unity標準のQuaternionと、数式通りの手動計算(サンドイッチ積)を
/// 両方試せるようにしている。
/// </summary>
public class QuaternionRotationDemo : MonoBehaviour
{
    [Header("回転設定")]
    [SerializeField] private Vector3 rotationAxis = Vector3.up; // 回転軸 v
    [SerializeField] private float rotationAngleDeg = 90f;      // 回転角 θ
    [SerializeField] private Vector3 targetPoint = Vector3.forward; // 回転させたい点 p

    private void Start()
    {
        // ① Unity標準の書き方
        Quaternion q = Quaternion.AngleAxis(rotationAngleDeg, rotationAxis.normalized);
        Vector3 rotatedByUnity = q * targetPoint; // 内部で q*p*q^-1 相当の計算をしている

        // ② 数式どおりに手動でサンドイッチ積を計算
        Vector3 rotatedByManualMath = RotatePointManually(targetPoint, rotationAxis.normalized, rotationAngleDeg);

        Debug.Log($"Unity標準のQuaternionでの回転結果: {rotatedByUnity}");
        Debug.Log($"手動計算(q*p*q^-1)での回転結果: {rotatedByManualMath}");
    }

    /// <summary>
    /// q = (cos(θ/2), v*sin(θ/2)) を作り、p' = q*p*q^-1 を手動で計算する。
    /// Unity標準のQuaternion * Vector3と同じ結果になることを確認できる。
    /// </summary>
    private Vector3 RotatePointManually(Vector3 p, Vector3 axis, float angleDeg)
    {
        float halfAngleRad = angleDeg * Mathf.Deg2Rad * 0.5f;
        float w = Mathf.Cos(halfAngleRad);
        Vector3 xyz = axis * Mathf.Sin(halfAngleRad);

        // q = (w, x, y, z)
        Quaternion q = new Quaternion(xyz.x, xyz.y, xyz.z, w);
        Quaternion qInv = Quaternion.Inverse(q);

        // p を純虚四元数 (0, x, y, z) として扱う
        Quaternion pQuat = new Quaternion(p.x, p.y, p.z, 0f);

        // p' = q * p * q^-1
        Quaternion resultQuat = MultiplyQuaternion(MultiplyQuaternion(q, pQuat), qInv);

        return new Vector3(resultQuat.x, resultQuat.y, resultQuat.z);
    }

    /// <summary>
    /// 四元数同士の積(ハミルトン積)を手動計算する。
    /// UnityのQuaternion同士は「*」演算子でも同じ結果になるが、
    /// 中身の計算式を示すためにあえて自前で実装している。
    /// </summary>
    private Quaternion MultiplyQuaternion(Quaternion a, Quaternion b)
    {
        float w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z;
        float x = a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y;
        float y = a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x;
        float z = a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w;
        return new Quaternion(x, y, z, w);
    }
}

コードの解説

  • ①Unity標準の書き方:Quaternion.AngleAxis(角度, 軸)で回転を表す四元数を作り、q * pointの形でベクトルに掛けるだけで回転後の座標が得られます。Unity内部ではこの記事で説明した $q,p,q^{-1}$ とほぼ等価な計算が行われています。

  • ②手動計算:数式で説明した通り、

    1. 回転軸・角度から $q = (\cos\frac{\theta}{2}, v\sin\frac{\theta}{2})$ を作る
    2. 点 $p$ を $w=0$ の純虚四元数として扱う
    3. $q * p * q^{-1}$ を計算する
    4. 結果の $(x, y, z)$ 成分が回転後の座標になる

    という手順をそのままコードに落としています。MultiplyQuaternionはハミルトン積(四元数同士の掛け算)の定義式をそのまま実装したものです。

実行すると、①と②の結果がほぼ一致することを確認できます(浮動小数点誤差でごくわずかにずれることがあります)。

補間(Slerp)の例

四元数のもう一つの利点である滑らかな補間も、Unityでは1行で扱えます。

// startRotationからendRotationへ、tの割合(0〜1)で滑らかに補間
Quaternion current = Quaternion.Slerp(startRotation, endRotation, t);

オイラー角同士を単純に線形補間すると回転経路が不自然になることがありますが、Slerp(球面線形補間)は四元数の性質を利用して最短経路で滑らかに補間してくれます。

まとめ

  • 四元数は $w + xi + yj + zk$ で表される数で、回転軸と角度から構成できる
  • 点の回転は $q,p,q^{-1}$ というサンドイッチ積で計算される
  • オイラー角と違いジンバルロックが起きず、Slerpによる滑らかな補間も可能
  • UnityではQuaternion型がこの仕組みを内部で担っており、AngleAxisやSlerpなどのAPIで簡単に扱える

参考動画

四元数のイメージをより深く掴みたい方は、以下の動画シリーズもおすすめです。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?