エフアンダーバー

個人でのゲーム開発

クォータニオンと回転

クォータニオン(四元数、Quaternion)は3Dグラフィックスのプログラミングにおいて回転を表す数としてよく出てきます。

曰く、

  • サイズが小さい(回転行列よりも少ない数で表せる)
  • ジンバルロックが起きない
  • 補間が容易

とのことで、非常に便利な理論です。

しかし、どういう原理で動いているのかを知りたいと思ってWikipediaなんかを見ると、大量の数式と謎の言葉の波に飲み込まれます。

そんなこんなで今までよくわからないままに使っていたのですが、 最近になって少しだけ理解が深まったので、現時点で知っていることについて説明してみようと思います。

対象読者

クォータニオンがどういうものなのか知りたい人向けです。 単にクォータニオンを利用した回転方法を知りたいだけであれば、他の資料を当たった方が簡単だと思います。

あまり難しい計算はしませんが、高校数学くらいの知識は持っているものとします。 具体的には、複素数、三角関数、ベクトル、行列についてです。

とはいえ、そんなによく理解していなくてもどうにかなるように書いたつもりです。 i^2=-1だとか、加法定理だとか、ベクトルと行列の計算方法だとか、回転行列がどんな形をしているかだとか、それくらいわかれば十分です。

複素数と座標

クォータニオンの話に入る前に準備として複素数の話をします。 というのも、クォータニオンというのは複素数の拡張だからです。

複素数というと確かx^2=-1のような二次方程式の解を表すために登場したなんだかよくわからない数でした。 ここではそのよくわからなさを理解しようとはせず、複素数の形だけに注目して2次元座標を表すことを考えます。

複素数は一般にx + iyという形で表せます。 このとき、xyは任意の実数で、両者はなんの関係性もない独立した数です。

そこで、xの値をx座標、yの値をy座標だと思い込むと、任意の複素数は2次元座標と一対一で結びつきます。

 \displaystyle
(x, y) \longleftrightarrow x + iy

2次元ベクトルが2次元座標を表すように、複素数もまた2次元座標を表すことができる2次元的な数なのです (ベクトルというと矢印や座標を思い浮かべるかもしれませんが、ベクトルの意味は単なる一並びの数であって、プログラミングにおける一次元配列に相当します)。

i^2=-1の関係はどこに行ったのかと思うかもしれませんが、 ベクトルに内積や外積といった特殊な掛け算が定義されているのと同じように、 複素数にも特殊な掛け算が定義されているというだけのことです。

複素数と回転

複素数が2次元座標を表せることはわかりました。 しかし、ベクトルではなく複素数を使う利点とは何でしょうか。 それは複素数の掛け算によって回転が簡単に表せることなのです (内積や外積により角度や直交方向が表せるのと同じ)。

ベクトルによる座標表現の場合、回転は回転行列を用いて次のように表せました。

 \displaystyle
\begin{pmatrix}
\cos \theta & -\sin \theta \\
\sin \theta & \cos \theta \\
\end{pmatrix}
\begin{pmatrix}
x \\ y \\
\end{pmatrix}
=
\begin{pmatrix}
x \cos \theta - y \sin \theta \\
x \sin \theta + y \cos \theta \\
\end{pmatrix}

これは座標(x, y)を、原点を中心に角度\theta回転させると、上式右辺のような座標に移るということを示しています。

さて、ここで次の複素数の計算式を見てください。

 \displaystyle
(\cos \theta + i \sin \theta)(x + iy) = (x \cos \theta - y \sin \theta) + i (x \sin \theta + y \cos \theta)

なんとなく先ほどの式に現れていたような部分が見て取れるのではないでしょうか。

左辺右側の複素数と右辺の複素数をそれぞれ座標だと考えると、左が回転前の座標、右が回転後の座標になっています。 回転行列の式と見比べると、\cos \theta + i \sin \thetaという複素数が回転行列と同じ役割を果たしていることがわかるかと思います。 つまり、複素数を用いると、\cos \theta + i \sin \thetaという複素数をかけるだけで角度\thetaの回転を表現できるのです。

天下り的な説明*1ではありますが、 複素数で回転を表せるということがわかりました。 ちなみに、どんな複素数でも回転を表すというわけではなく、絶対値(\sqrt{x^2 + y^2})が1の複素数だけが回転を表します。 これは回転を表す複素数 \cos \theta + i \sin \thetaの絶対値が1だからです(論理的には逆ですが)。

クォータニオンの定義

複素数は2次元的な数で、2次元の回転を表すことができました。 ということは、3次元版の複素数を考えれば3次元の回転ができるんじゃないか、というのは自然な発想ではないでしょうか。 しかし、実は複素数を3次元に拡張するのは満たすべき性質を考えると数学的に難しいことでした。 それならばと、ひとつ飛ばして4次元にまで拡張した複素数がクォータニオンです*2

クォータニオンは1つの実部と3つの虚部からなる数です。 3つの虚部が必要ということで、iに加えて新しい虚数単位jkを導入し、クォータニオンをw + ix + jy + kzのように表します。 wが実部で、x, y, zがそれぞれ虚部になります。

計算方法は概ね複素数と同じですが、乗算ではj^2ijといった項がでてきてしまうためi^2=-1のような演算規則が必要になります。 演算規則の定義は次の通りです。

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

ただし、クォータニオンでは行列の乗算のように右からかけるか左からかけるかで計算結果が異なります(交換法則が成立しない)。

上の定義に右から左から適当な数(i, j, k)をかけると、結局次のような関係が得られます。

 \displaystyle
\begin{array}{lll}
ij = k,  & jk = i,  & ki = j  \\
ji = -k, & kj = -i, & ik = -j \\
\end{array}

複素数が2次元座標を表現できたように、クォータニオンも4次元座標を表現できます。 例えば、座標(w, x, y, z)はクォータニオンw + ix + jy + kzと表せます。 4次元座標というとうまく想像できないかもしれませんが、計算上使うだけなので、単に4つ組の数字くらいに考えておくといいと思います。 重要なのは、どれか1つの数字を無視すれば、3次元座標になるということくらいです。

クォータニオンと回転

結論だけいうと、絶対値(\sqrt{w^2 + x^2 + y^2 + z^2})が1のクォータニオンは4次元の回転を表すことができます。

しかし、改めて考えてみると4次元の回転というのはいったいどんな操作なのでしょうか。

それを確かめるためにクォータニオンw + ix + jy + kzに対して、 回転を表すクォータニオン\cos \theta + i \sin \thetaを左からかけてみます (回転を表す複素数と同じ式ですが、ちゃんとy=0, z=0の回転を表すクォータニオンになっています)。

 \displaystyle
(\cos \theta + i \sin \theta)(w + ix + jy + kz) \\
\begin{split}
\quad
&= \cos \theta(w + ix + jy + kz) + \sin \theta(iw + i^{2}x + ijy + ikz) \\
&= \cos \theta(w + ix + jy + kz) + \sin \theta(iw - x + ky - jz) \\
&= (w \cos \theta - x \sin \theta) + i(w \sin \theta + x \cos \theta) \\
&\quad + j(y \cos \theta - z \sin \theta) + k(y \sin \theta + z \cos \theta)
\end{split}

これはつまり、次の座標変換を表しています。

 \displaystyle
\begin{pmatrix}
w \\ x \\ y \\ z \\
\end{pmatrix}
\overset{\cos \theta + i \sin \theta}{\longmapsto}
\begin{pmatrix}
w \cos \theta - x \sin \theta \\
w \sin \theta + x \cos \theta \\
y \cos \theta - z \sin \theta \\
y \sin \theta + z \cos \theta \\
\end{pmatrix}

ここで、実部とiの項、およびjの項とkの項についてそれぞれ見てみると、 それぞれが2次元における角度\thetaの回転式になっているのがわかります。 つまり、クォータニオン\cos \theta + i \sin \thetawx平面とyz平面をそれぞれ角度\thetaだけ回転させています。 4次元空間における回転といっても、結局は2次元平面上の回転なのです(3次元空間での回転でもそうですよね)。

また、この計算結果にはクォータニオンによる回転の重要な性質が現れています。 それは1点で交わる2つの平面*3上で同じ大きさの角度だけ回転させるというものです(isoclinic rotation)。 4次元空間における回転にもいくつか種類があるのですが、1つのクォータニオンの乗算で表現できる種類の回転はこれになります。

座標平面上の回転

先ほどは\cos \theta + i \sin \thetaを左からかけることにより、 wx平面とyz平面をそれぞれ角度\thetaだけ回転できることを確かめました。

同様にして、\cos \theta + j \sin \theta\cos \theta + k \sin \thetaを左からかけると、 wy平面とzx平面、wz平面とxy平面をそれぞれ角度\thetaだけ回転できることがわかります。

まとめると次のようになります。

  • \cos \theta + i \sin \theta左からかけるとwx平面とyz平面が角度\theta回転
  • \cos \theta + j \sin \theta左からかけるとwy平面とzx平面が角度\theta回転
  • \cos \theta + k \sin \theta左からかけるとwz平面とxy平面が角度\theta回転

乗算方向による回転の変化

クォータニオンの乗算では左右どちらからかけるかによって計算結果が異なるのでした。 回転操作は乗算なので、当然この影響を受けます。

変化を確かめるために\cos \theta + i \sin \thetaを今度は右からかけてみます。

 \displaystyle
(w + ix + jy + kz)(\cos \theta + i \sin \theta) \\
\begin{split}
\quad
&= \cos \theta(w + ix + jy + kz) + \sin \theta(iw + i^{2}x + jiy + kiz) \\
&= \cos \theta(w + ix + jy + kz) + \sin \theta(iw - x - ky + jz) \\
&= (w \cos \theta - x \sin \theta) + i(w \sin \theta + x \cos \theta) \\
&\quad + j(y \cos \theta + z \sin \theta) + k(-y \sin \theta + z \cos \theta) \\
&= (w \cos \theta - x \sin \theta) + i(w \sin \theta + x \cos \theta) \\
&\quad + j(y \cos (-\theta) - z \sin (-\theta)) + k(y \sin (-\theta) + z \cos (-\theta))
\end{split}

計算過程からわかるように、異なる二つの虚数単位の積が逆転することにより正負が反転し、 結果としてw軸を含まない平面の回転が逆向きになっていることがわかります。

他の座標平面に対する結果も合わせて次にまとめます。

  • \cos \theta + i \sin \theta右からかけるとwx平面が角度\theta回転、yz平面が角度(-\theta)回転
  • \cos \theta + j \sin \theta右からかけるとwy平面が角度\theta回転、zx平面が角度(-\theta)回転
  • \cos \theta + k \sin \theta右からかけるとwz平面が角度\theta回転、xy平面が角度(-\theta)回転

共役クォータニオンによる回転

あるクォータニオンに対して、虚部の符号を反転させたクォータニオンを共役クォータニオンといいます。 クォータニオンw + ix + jy + kzの共役クォータニオンはw - ix - jy - kzになります。 共役クォータニオンの表記法はいくつかありますが、ここではq^*のように星をつけて表記することにします。

定義より、\cos \theta + i \sin \thetaの共役クォータニオンは\cos \theta - i \sin \thetaです。 これを少し変形すると\cos (-\theta) + i \sin (-\theta)になるため、 この共役クォータニオンは逆向きの回転を表していることがわかります。 \cos \theta + j \sin \theta\cos \theta + k \sin \thetaの場合も同様です。

一般に回転を表すクォータニオンの共役クォータニオンは逆向きの回転を表します。 実部まで符号を反転させてしまうと逆回転にならないため注意してください *4

回転の合成

回転操作は乗算であり、クォータニオンの乗算には結合法則が成り立ちます。 よって、座標pをクォータニオンq_1で回転させた後にクォータニオンq_2で回転させたいという場合には、 予め計算しておいたクォータニオンq_{12} = q_2 q_1を用いて次のように計算できます。

 \displaystyle
q_2(q_1p) = (q_2q_1)p = q_{12}p

つまり、回転を表すクォータニオン同士の乗算により回転の合成ができるのです。

また共役クォータニオンにはq_1^*q_2^* = (q_2q_1)^*という性質が成り立つため、 共役クォータニオン同士の合成も元のクォータニオンの合成で表せます (共役クォータニオンは逆回転を表すので当たり前といえば当たり前ですが)。

 \displaystyle
q_1^*(q_2^*p) = (q_1^*q_2^*)p = (q_2q_1)^*p = q_{12}^*p

クォータニオンの乗算は左右どちらからかけるかによって結果が変わってしまうため、 合成の際には座標に対してどちらから回転を適用するかを考慮した上で、回転の順番を意識する必要があります。

クォータニオンと3次元回転

さて、ここまでは4次元の回転を考えてきましたが、我々が扱いたいのは3次元の回転です。 クォータニオンによる回転操作でどのように3次元の回転を実現するかを考えていきます。

3次元座標と4次元座標

クォータニオンによる回転操作は4次元空間にて適用するため、 3次元の座標は4次元の座標に変換する必要があります。

言葉にするとなんだか難しそうですが、やることは簡単でw成分を加えてやるだけです。 w成分の値はなんでも構いません。 というのも、理論上必要なだけで、3次元の回転操作ではこの値は一切関与しないためです。

仮にw値を0とすると、3次元座標(x, y, z)は次のようにクォータニオンで表せます。

 \displaystyle
(x, y, z) \longleftrightarrow (0, x, y, z) \longleftrightarrow 0 + ix + jy + kz

回転後の座標を表すクォータニオンも逆の手順で3次元座標へと変換できます。

回転の適用

前節では、各座標平面上の回転を表すクォータニオンを紹介しました。 これらはyz平面、zx平面、xy平面上で任意の角度の回転ができるものでした。 w成分を除いた3次元空間で考えると、これらはそれぞれx, y, z軸周りの回転を表します。 3つの座標軸周りの回転を合成すれば、3次元のあらゆる回転を表せそうです(参照:オイラー角)。

しかし、そうなると邪魔になってくるのが、同時に回転してしまうwx平面、wy平面、wz平面です。 どうにかして座標軸周りの回転のみを適用したいのですが、前節で述べたとおり、 クォータニオンの乗算では必ず二つの平面上で回転が起きてしまいます。

これを解消する方法は単純で、逆方向の回転によって打ち消してしまうことです。 具体的には次に示す演算をします。

 \displaystyle
qpq^*

pが変換前の座標、qが回転を表すクォータニオンです。 まず、クォータニオンを左からかけて通常通り回転させます。 同時に、共役クォータニオンを右からかけることでw軸を含む平面だけ逆回転させます。 右からかけるとw軸を含まない平面だけ逆回転しますが、 共役クォータニオンを利用することでそれがさらに反転し、w軸を含む平面だけが逆回転するようになります。 すると、w軸を含む平面は回転した量と同じだけ逆回転するため、回転が相殺されます。

一方、w軸を含まない平面は二倍回転することになるため、適用するクォータニオンの回転角度は半分にしておく必要があります。 角度\thetaの回転をさせたい場合には、\frac{\theta}{2}回転させるクォータニオンを乗算します。

各軸周りに対して、同じ操作をすると次のようになります。

 \displaystyle
q_{z}(q_{y}(q_{x}pq_{x}^*)q_{y}^*)q_{z}^* = (q_{z}q_{y}q_{x})p(q_{x}^*q_{y}^*q_{z}^*) = (q_{z}q_{y}q_{x})p(q_{z}q_{y}q_{x})^* = q_{xyz}pq_{xyz}^*

よって、各軸周りの回転を合成したクォータニオンq_{xyz}があれば、3次元座標にあらゆる回転(任意軸回転)を加えることができます。

さて、このq_{xyz}ですが、実はわざわざ合成しなくても次の式で簡単に計算できることが知られています。

 \displaystyle
\cos \frac{\theta}{2} + i r_x \sin \frac{\theta}{2} + j r_y \sin \frac{\theta}{2} + k r_z \sin \frac{\theta}{2}

\thetaは回転角度、rは回転軸の向きを表す単位ベクトル(大きさが1のベクトル)です。

計算手順のまとめ

  1. 3次元座標(x, y, z)をクォータニオンp = ix + jy + kzに変換
  2. rを回転軸として角度\theta回転させるクォータニオン q = \cos \frac{\theta}{2} + i r_x \sin \frac{\theta}{2} + j r_y \sin \frac{\theta}{2} + k r_z \sin \frac{\theta}{2}を計算
  3. 回転後の座標を表すクォータニオンp' = qpq^*を計算
  4. p' = ix' + jy' + kz'を3次元座標(x', y', z')に変換

クォータニオンと回転の一意性

4次元の回転において異なるクォータニオンが同一の回転を表すことはありません。 クォータニオンが表現できる種類の回転に対して、対応するクォータニオンは一意に定まります。

一方で、3次元の回転においては異なるクォータニオンが同一の回転を表すことがあります。 これは3次元回転の実現方法に起因しています。

3次元の回転ではひとつの平面における回転を打ち消すためにクォータニオンを二回乗算する必要がありました。 そもそもクォータニオンによる乗算は一回で[0, 2\pi)の角度の回転ができます。 これを二回乗算するのですから結果の回転角度は[0, 4\pi)になり、それぞれの角度に対応するものが二つずつ存在します(double-cover)。

回転を表すクォータニオンの計算式に\theta + 2\piを代入してみるとわかりやすいと思います。

 \displaystyle
\cos \frac{\theta + 2\pi}{2} + i r_x \sin \frac{\theta + 2\pi}{2} + j r_y \sin \frac{\theta + 2\pi}{2} + k r_z \sin \frac{\theta + 2\pi}{2} \\
\begin{split}
\quad
&= \cos (\frac{\theta}{2} + \pi) + i r_x \sin (\frac{\theta}{2} + \pi) + j r_y \sin (\frac{\theta}{2} + \pi) + k r_z \sin (\frac{\theta}{2} + \pi) \\
&= -(\cos \frac{\theta}{2} + i r_x \sin \frac{\theta}{2} + j r_y \sin \frac{\theta}{2} + k r_z \sin \frac{\theta}{2})
\end{split}

\thetaと同じ回転結果となるはずにも関わらず、クォータニオンは\thetaを代入した際の値の正負を反転させた結果となっています。

このように、3次元において同じ回転を表すクォータニオンは二つずつ存在するため、 クォータニオンが一致しないからといって異なる回転だと判断してはいけません。

クォータニオンの補間

クォータニオンを回転に利用する大きな動機のひとつは補間が容易であることです。

よく使われるクォータニオンの補間に球面線形補間(spherical linear interpolation, slerp)と線形補間(linear interpolation, lerp)があります。

球面線形補間

球面線形補間はクォータニオンにおける基本的な補間です。

回転を表すクォータニオンは絶対値が1であるのでした。 回転を表すクォータニオンを座標だと考えて4次元空間に配置すると、\sqrt{w^2 + x^2 + y^2 + z^2}が1なので、 これらのクォータニオンは必ず半径1の超球*5面上に存在します。 超球面上の二つのクォータニオンに対し、超球面上を通るように、最短距離を、一定速度で(線形に)移動するような補間を球面線形補間といいます。

4次元で考えづらい場合には、徐々に次元を上げていくとわかりやすいかもしれません。 2次元においては、円上の二点の間を、常に円上を通るように一定速度で移動するような補間です。 3次元になると球面上の二点の間を球面上を通るように移動する経路は無数にありますが、そのうちの最短の経路を一定速度で移動するような補間です。 4次元空間がどんな世界なのかは想像もつきませんが、同じような補間がきっとできるはずです。

球面線形補間を式にすると次の通り。

 \displaystyle
\frac{\sin(1-t)\theta}{\sin\theta}q_1 + \frac{\sin t\theta}{\sin\theta}q_2

\thetaは二つのクォータニオンの間の角度です。 4次元空間における角度ってどこだろう、と思うかもしれませんが、そもそも角度というのは内積によって定義されていて、

 \displaystyle
q_1 \cdot q_2 = |q_1||q_2|\cos\theta

を満たす\thetaこそが角度になります。 クォータニオンの内積は、クォータニオンを(w,x,y,z)という4次元ベクトルとみなした場合のベクトルの内積と同じです。

回転を表すクォータニオンでは絶対値が1なので、それらの内積の値は\cos\thetaに等しくなります。 よって、内積の値に逆三角関数(三角関数の逆関数で\sin\cosの値から角度を求める)を適用すれば角度\thetaが求まります。

線形補間

球面線形補間はきれいな補間ができるのですが、式をみればわかるようにそれほど単純な計算ではありません。 プログラミングにおいて、膨大な回数をこなすには少し計算量が多いのです。 そこで、近似的に少ない計算量で球面線形補間を実現する方法が線形補間です。

球面線形補間では超球面上を通るように補間しましたが、線形補間では直線経路で補間します。 直線経路で補間すると、補間結果のクォータニオンは基本的に超球面上にはありません。 つまり、回転をあらわすクォータニオンでなくなってしまいます。 これでは困るため、結果のクォータニオンをその絶対値で割ってやることで、絶対値が1になるように(半径1の超球面上に乗るように)正規化します。

線形補間を式にすると次の通り。

 \displaystyle
(1-t)q_1 + tq_2

この式は球面線形補間の式における、\thetaが非常に小さいときの近似式になっています (\thetaが十分に小さい場合に\sin\theta \simeq \thetaと近似できるため)。 大雑把な感覚として、補間しようとしている二つのクォータニオンが近いものであればあるほど、球面線形補間の値に近い値が得られます。

f:id:fspace:20170630220100p:plain f:id:fspace:20170630220111p:plain
2次元回転(複素数)の線形補間

補間における注意

クォータニオンの補間には一点だけ注意しておかなければならないことがあります。 それは、同じ回転を表すもうひとつのクォータニオンの存在です。

前節で述べたとおり、3次元の回転を表すクォータニオンは符号を反転させると、 まったく同じ回転を表すもうひとつのクォータニオンになります。 これらは3次元の回転としては同じでも、4次元空間における位置は原点を挟んでまったくの逆側です。 当然、どちらのクォータニオンとの間を補間するかによって結果はまったく異なります。

基本的に補間は距離の短い経路を用いた方が直感的になるため、 3次元の回転を表すクォータニオン同士を補間する場合には、符号をうまく反転させて近いクォータニオン同士を補間します。 どちらのクォータニオンの方が近いか判定するには角度を利用します。 二つのクォータニオンの内積を計算して、値が正なら|\theta| \lt \frac{\pi}{2}なのでそのまま補間を、 値が負なら|\theta| \gt \frac{\pi}{2}なので片方のクォータニオンの符号を反転させて補間をします。

参考

おわりに

クォータニオンに関する記事は、使い方のみに焦点を絞ったものや難しい数学の原理を説明したものが多く、 これら二つの間を埋めるような内容の記事が少ないな、と思ったので書いてみました。 できる限りわかりやすくする、という名目のもと難しい説明が必要な内容を(重要さに関わらず)片っ端からすっ飛ばしたので、 この記事を読んだだけでは疑問点も多いかもしれませんが、他の数学的記事の理解の助けになれば幸いです。

実はこの記事、うまい説明の流れが思いつかずに数か月間積んでます。 そのままお蔵入りにするのももったいないので、とりあえず書き残したことを加筆したのですが、 当初の構成を覚えていないのでバグ(論理の破綻)が紛れ込んでいる可能性が多分にあります。 何かおかしな点を見つけたら指摘してもらえると助かります。

*1:天の啓示のように突然式が出てきて「ほら正しいでしょ?」という類の説明。数学でよく使われる謎の言葉で人事云々とは全く関係ない。

*2:クォータニオンの発想の時点では特に回転のためではなく単純な数学的関心だったようです。

*3:想像しがたいかもしれませんが4次元空間なのでそういうことが起こります。

*4:演算子のカスタマイズが可能なプログラミング言語だと単純にマイナス符号をつけて逆回転とか思いがち(自分だけ?)ですが大抵そうはなりません。

*5:4次元以上の空間における円や球に相当するもの。