ゆるめのクォータニオン入門

この記事は、NEXTSCAPE クラウドインテグレーション事業本部 Advent Calendar 2018 11日目の記事です。

こんにちは、つい最近 HoloLens 開発チームに入った長沢です。
皆さん、クォータニオンってお聞きになったことがあるでしょうか?
3D 系の技術にあまりなじみのなかった私は、HoloLens 開発をするようになってから初めて聞く言葉でした。

どうやら、3次元空間上で回転を表現する手法で、Unity 独自の概念というわけではなく、3D 技術一般で使われるもののようです。
Web 上には素晴らしい解説記事がたくさん出てくるのですが、概念自体が難しいため数学的な説明が多く、もう少しお手軽に理解できた気になる記事をかけないかと思って、この入門記事を書くことにしました。
厳密な記述をするよりも、なんとなく理解した気になれることを重視しました。より詳細な解説への橋渡し的な記事になってくれるとよいなと思っています。

クォータニオンとの出会い

Unity Editor 上で、オブジェクトを回転させるときは、Rotation を指定します。ここでは xyzそれぞれの軸の周りをどのくらい回転させるかという指定ができます。わかりやすいですよね。
ところが、プログラムから Rotation の型を見ると Quaternion なる聞いたことのない名前の型。
コンストラクタを見てみると、引数が 4 つ。

public Quaternion (float x, float y, float z, float w);

3 つではなく、4 つ?ここからなぞは始まるわけです。

クォータニオンってなに

前述の通り、クォータニオンは、回転を表現するための手法の一つです。
Unity Editor 上で使われているのも同じく回転を表現する手法の一つで、こちらはオイラー角という名前がついてます。

クォータニオンは、特定の回転軸 (原点を通る直線) を定め、その周りをどれだけ回転させるかという情報をもとに定義される値です。
高校数学でベクトルを習った方は、3次元空間上で直線を表すのにベクトルが1つ必要だったことを覚えているでしょうか。
軸を指定するためのベクトル (3つの数字) と回転の大きさを表す数字が 1 つ。この4つの数を基本として、回転を定義するのがクォータニオンです。

本来であれば、3次元空間上の回転が、特定の軸の周りの回転として表せることを示さなければいけないと思いますが、「なんとなく、どっかの軸の周りにくるっとするば任意の回転できそうだよね!」ということで無理やり納得していただけると嬉しいです。
頭の良い方たちが考えてくれた手法なので、これでちゃんと表現できると信じましょう!

クォータニオンの定義

ここからは、具体的にクォータニオンの定義と演算規則について述べていきます。
非常に乱暴ですが、定義や演算規則については、とにかくそうなっていると思ってください。
大切なのは、よくわからないけど、これらの演算規則に従う体系を考えると回転が非常にすっきりとした演算に落とし込めるということです。
まず、肝心のクォータニオンですが、特定の軸 \((v_z, v_y, v_z)\) 周りの \(\theta\) の回転に対応するクォータニオンは下記の通りです。

$$ \mathbf{q} = \mathbf{ i } v_x \sin \frac{\theta}{2} + \mathbf{ j } v_y \sin \frac{\theta}{2} + \mathbf{ k } v_z \sin \frac{\theta}{2} + \cos \frac{\theta}{2} $$

ここで、\(v\) は大きさ1のベクトルを想定します。(すなわち \( v_x ^ 2 + v_y ^ 2 + v_z ^ 2 = 1 \) です)
\(i, j, k\) はそれぞれ特別な記号で、以下のような演算規則に従います。

$$
\mathbf{ i }^2 = \mathbf{ j }^2 = \mathbf{ k }^2 = -1 \\
\mathbf{ i } \mathbf{ j } = \mathbf{ k }, \mathbf{ j } \mathbf{ k } = \mathbf{ i }, \mathbf{ k } \mathbf{ i } = \mathbf{ j } \\
\mathbf{ j } \mathbf{ i } = – \mathbf{ k }, \mathbf{ k } \mathbf{ j } = – \mathbf{ i }, \mathbf{ i } \mathbf{ k } = – \mathbf{ j } \\
$$

また、3 次元上の点P \((x, y, z)\)を下記のように表します。
$$
\mathbf{p} = \mathbf{ i } x + \mathbf{ j } y + \mathbf{ k } z + w
$$

ここまでは、あくまでクォータニオンや演算規則を定義しているにすぎません。肝心なのはこのような演算を導入すると、点Pの回転後の座標が下記のように計算できることです。
$$
\mathbf{p’} = \mathbf{q} \mathbf{p} \mathbf{q^{-1}}
$$

ここで、式に出てくる \(\mathbf{q^{-1}}\) は、下記により定義されます。
$$
\mathbf{q^{-1}} = – \mathbf{ i } v_x \sin \frac{\theta}{2} – \mathbf{ j } v_y \sin \frac{\theta}{2} – \mathbf{ k } v_z \sin \frac{\theta}{2} + \cos \frac{\theta}{2}
$$

つまり、\(\mathbf{ q }\) の \(\mathbf{ i },\mathbf{ j },\mathbf{ k }\) の係数の符号を反転させたものです。

また、回転を連続で行った場合の結果は、クォータニオンの積として計算できます。
すなわち、\(\mathbf{q_1}\) で表される回転の後に、\(\mathbf{q_2}\) で表される回転を行うようなクォータニオンは、積 \(\mathbf{q_2} \mathbf{q_1}\) を計算することで求めることができます。

このように、回転および回転の合成がクォータニオンを使った演算によって定義できていることがポイントとなります。

計算例

前の節では、回転がクォータニオンを使った演算で計算できると言いましたが、特に証明も何もしませんでした。それだとあんまりなので、ここでは、実際にクォータニオンを使って計算をしてみようと思います。
ただ、一般的な場合について計算をするのは難しいので、z軸周りの回転という具体例を通して、クォータニオンを使った計算の雰囲気をなんとなくつかんでいただければと思います。

計算例1:クォータニオンを使った z 軸周りの回転
z軸周りに、\(\theta\) だけ回転させるような場合を考えてみましょう。
この場合、回転の軸を表す大きさ1のベクトルは、\((0, 0, 1)\) です。なので、この回転を表すクォータニオンは、
$$
\mathbf{p} = \mathbf{k} \sin \frac{\theta}{2} + \cos \frac{\theta}{2}
$$
また、のちの計算結果を見やすくするために、変換前の点は、xy 座標を極座標に変換しておきます。点P \((x, y, z) = (r \cos \theta_0, r \sin \theta_0, z)\)。
回転後の座標は下記の計算で求めることができるのでしたね。

$$
\begin{eqnarray}
\mathbf{p’} &=& \mathbf{q} \mathbf{p} \mathbf{q^{-1}} \\
&=& (\mathbf{k} \sin \frac{\theta}{2} + \cos \frac{\theta}{2})(\mathbf{i} r \cos \theta_0 + \mathbf{j} r \sin \theta_0 + \mathbf{k} z + w)(-\mathbf{k} \sin \frac{\theta}{2} + \cos \frac{\theta}{2})
\end{eqnarray}
$$

計算自体はかなり面倒ですが、基本的に \(i, j, k\) に対して演算規則を適応していくだけです。
途中、三角関数の加法定理と三角関数の基本定理 (\(\sin^2 \alpha + cos^2 \alpha = 1\)) を使って整理する必要がありますが、それ以外は無心で計算するだけです。
計算していくと以下の結果を得ます。
$$
\mathbf{p’} = \mathbf{i} r \cos (\theta_0 + \theta) + \mathbf{j} r \sin (\theta_0 + \theta) + \mathbf{k} z + w
$$

\(xy\)座標を表す、\(\mathbf{i}, \mathbf{j}\) の係数を見ると、もともとの角度から \(\theta\) だけ回転していることがわかります。

計算例2:クォータニオンを使った、回転の合成
次に、z軸周りに \(\theta_1\) 回転させた後、さらに z 軸周りに \(\theta_2\) 回転させるという合成変換を考えます。
それぞれ対応するクォータニオンを \(\mathbf{q_1}, \mathbf{q_2}\) とすると、積\(\mathbf{q_2} \mathbf{q_1}\) がこの合成変換を表すクォータニオンになるはずです。
$$
\begin{eqnarray}
\mathbf{q_2}\mathbf{q_1} &=& (\mathbf{k} \sin \frac{\theta_2}{2} + \cos \frac{\theta_2}{2})(\mathbf{k} \sin \frac{\theta_1}{2} + \cos \frac{\theta_1}{2}) \\
&=& \mathbf{k} \sin \frac{\theta_1 + \theta_2}{2} + \cos \frac{\theta_1 + \theta_2}{2}
\end{eqnarray}
$$

こちらの方が計算は簡単で、単純に三角関数の加法定理を使って整理するだけです。
クォータニオンの定義に当てはめて考えると、このクォータニオンが表すのは、z軸周りの \(\theta_1 + \theta_2\) の回転です。確かに想定した結果が得られているようです。

なぜクォータニオンを使うのか

ここまでで、クォータニオンが何なのかをなんとなく説明してきたんですが、正直、Unity Editor で指定できるようなオイラー角による指定の方がはるかに直観的ではないでしょうか?
じゃあ、なぜクォータニオンを使うのかといえば、もちろん利点があるからなんですが、以下に、Webで調べてみると出てくる利点を、ごく簡単にまとめます。

表現や計算が簡素だし、誤差も少ない。
クォータニオンの演算も決して簡素だとは言い難いですが、他の方法と比べると、クォータニオンを使った方が演算回数は少なくて済むとのこと。
また、その結果、誤差も少なくなるということです。
非常に感覚的な議論ですが、オイラー角による回転は、それぞれの軸回りに 3 回の回転をしているとみなすことができます。一方、クォータニオンは特定の軸の周りに1回回転させるだけ。確かにクォータニオンの方が効率的っぽいなと思って納得しましょう。

補間に都合がよい
補間というのは文字通り間を補うことです。
回転に関して言えば、ある回転と別の回転があって、これらの間にあたる回転を考えることです。
これも、厳密には他のサイトなどを見てもらうこととして、ここでは、個人的な理解をもとに非常に抽象的な説明だけ行います。
まだ、自分自身の理解も十分にはこなれていないので、説明がわかりずらく感じる場合は、軽く読み飛ばしてしまってください。

まず、クォータニオンは \(q = \mathbf{i} x + \mathbf{j} y + \mathbf{k} z + w\) のような形で書けるのでした。即ち \((x, y, z, w)\) の4つの数字により指定することができます。
見方を少し変えると、クォータニオンは、4次元空間上の点として表現できることになります。
また、証明を省略しますが、クォータニオン全体は \(x^2 + y^2 + z^2 + w^2 = 1\) を満たす 4 次元空間上の部分空間(球面の方程式の類推から超球面と呼びます)を形成します。
この時、この超球面上にある 2 つの点(それぞれクォータニオンを表す)を考えます。片方の点から超球面上を経由してもう一方の点に移動するとしましょう。
これは、1つの回転を表すクォータニオンを連続的に変化させて別のクォータニオンに変化させているとみることができます。(超球面上の移動なので、途中の状態もクォータニオンです)
つまり、ある回転と別の回転の間を補間しながら変化させるという操作が、クォータニオンを使うと幾何学的な移動として表現できることになります。
しかも、超球面上の移動という、比較的シンプルな移動となっているため、計算を考えやすいようで、これがクォータニオンを使うと補間が行いやすいといわれる理由のようです。

ジンバルロックなるものが解消できる
らしいです。ジンバルロックというのは、オイラー角を使った際に発生する問題とのこと。
でも、私は力尽きました。。。解説はほかのサイトに譲ります。
ただ、解説を見ていただくとわかるのですが、そもそもジンバルロックはオイラー角で表現していることに起因する問題なので、他の表現を使えば発生しないというのは納得できるかと思います。

先にも書いた通り、ここら辺になってくると自分でも理解不足を感じるので、またの機会にもう少し掘り下げてみようと思います。

最後に

ここまで読んでいただきありがとうございます。
一番最初に書いた通り、まずはざっくりとクォータニオンを理解するための記事を書いてみました。

ふわっとした議論で誤魔化している部分や、厳密さに欠ける記述が多々あるのですが、このページが、他のより厳密な解説ページへの橋渡しと機能してくれれば大変うれしく思います。

さかのぼること十数年前、当時学生だった私は、どうせ社会人になってから使うことなんてないんだろうなと思いながら数学を勉強していました。
クォータニオンの話といい、最新の機械学習といい、意外と数学を使っているみたいです。
もう少しまじめに勉強しておけばよかったと後悔する日々を送っています。。。

それではまた。