Imaginantia

思ったことを書きます

Quaternionのおはなし

Quaternion について色々と書きます。ベクトルの外積三角関数くらいは仮定します。

もくじ

Quaternionとは?

四元数 (Quaternion) というのは、4つの数 (1,i,j,k) の線形和で表現できると、その数に備わる演算からなる数の体系です。

この i,j,k というのは「演算を四則演算のように書くため」に使われているだけの記号で、Quaternion をプログラムで表現するならただの4次元ベクトルで良いです。

つまりある Quaternion は4つの実数 x,y,z,w を使って xi+yj+zk+w という形式 (線形和) で表せるということです。

この上の特徴的な演算が掛け算です。この掛け算は可換ではないが足し算と分配する演算なので、2つの Quaternion の掛け算を定義するには i^2, j^2, k^2, ij, jk, ki, ji, kj, ik それぞれの計算結果があれば良いということになります。

例えば i+3j2i の掛け算が 2i^2 + 6ji になることはわかるけど、i^2ji をどうにか計算しないと xi+yj+zk+w の形式で表現できないという話。

実際にこれは i^2 = j^2 = k^2 = -1, ij = k, jk = i, ki = j, ji = -k, kj = -i, ik = -j として定義され、無事にどんな2つの Quaternion の積もまた xi+yj+zk+w と表現できることがわかります。

 

さて、これを用いると3次元回転の表現に便利である、ということで様々なところで使われています。

「3次元回転」というのは3次元空間における「空間を原点中心に回して出来る変換」のことです。回すこと (ぐいーっと回していく過程) ではなく、回したこと (最終的な姿勢) の表現です。

ところで Unity とかで物体を回すのに Quaternion についてちゃんと知らなきゃいけないかというと勿論そうではありませんQuaternion.AngleAxis とか使えばほしい回転は一瞬で手に入るし、ベクトルと「掛けて」あげれば思い通りに回ってくれます。

が、それだけだと足りないケースもまたあるので、私は今この文章を書いています。

ベクトル・スカラー表現

Quaternion q = xi+yj+zk+w があったとき、\mathbf{v} = xi+yj+zk 部分をベクトル \mathbf{v} = (x,y,z) と捉えて q = \mathbf{v}+w と表記することがあります (i,j,k が線形独立なので大丈夫、ベクトルとスカラーは見たら区別できるので大丈夫)。

これは単純に便利だからでもあり、またこの方が性質が綺麗に出てくるからというところもあります。

こうすると 2つの Quaternion q_1 = \mathbf{v}_1 + w_1, q_2 = \mathbf{v}_2 + w_2 の掛け算は  q_1 q_2 = (\mathbf{v}_1 + w_1)(\mathbf{v}_2 + w_2) = \mathbf{v}_1\times \mathbf{v}_2 + \mathbf{v}_1w_2 + \mathbf{v}_2w_1 + w_1w_2 - \mathbf{v}_1\cdot\mathbf{v}_2 となります。これはちゃんと読めばめちゃわかりやすい等式です。

つまり元々 Quaternion の掛け算はベクトルの外積内積みたいな性質を併せ持っていたということで、ベクトル部分の (Quaternion的) 掛け算によって  \mathbf{v}_1\times \mathbf{v}_2 - \mathbf{v}_1\cdot\mathbf{v}_2 が出てくるわけです。あとはただの分配法則とスカラー積です。

雰囲気をわかっていれば式を覚える必要はないですね。

 

Quaternion q = xi+yj+zk+w の"大きさ" を \lVert q\rVert = \sqrt{x^2+y^2+z^2+w^2} で定義します。大きさ 1 の Quaternion を単位 Quaternion と呼びます。

これはベクトルを使って書けば  \lVert q\rVert = \sqrt{\lVert \mathbf{v}\rVert^2 + w^2} = 1 という条件になりますが、これは \lVert \mathbf{v}\rVert^2 + w^2 = 1 ということです。

a^2+b^2=1 であるような2数 a,b はある \phi を使って a=\sin\phi, b=\cos\phi と表せるのでした。これに倣い、どんな単位 Quaternion q も、ある \phi と長さ 1 のベクトル \mathbf{u} を使って q = \mathbf{u}\sin\phi+ \cos\phi と表せます。

これはオイラーの等式 \exp{i\phi} = i\sin\phi+ \cos\phi とよく似た状況 (\mathbf{u} = (1,0,0) ならそのまま) なんですが、それはそのうち。

実部の無い複素数を純虚数と呼ぶように、スカラー部の無い Quaternion を純 Quaternion と呼びます。

単位 Quaternion たちは、掛け算によってを成します。群っていうのは「素直に掛け算ができて」「素直に割り算ができる」やつのことです。

割り算 q_1/q_2 = q_1{q_2}^{-1} をするには {q_2}^{-1} という逆数が必要ですが、これは q = \mathbf{v} + w に対して q^{-1} = - \mathbf{v} + w で定義できます。

計算してみましょう。qq^{-1} = (\mathbf{v}+w)(-\mathbf{v}+w) = - \mathbf{v}\times\mathbf{v} + w^2 + \mathbf{v}\cdot\mathbf{v} = \mathbf{0} + \cos^2\phi+ \lVert \mathbf{u}\rVert^2\sin^2\phi= 1 です。この 1単位元 (掛け算の影響がない数) ですね。

つまり Quaternion は好きに打ち消したりできるということです。変換として捉えるなら、「元に戻す操作」ができるということです。

ただし、可換ではありません。すなわち基本的には q_1q_2\ne q_2q_1 であるということです。 演算順序が結果に大きくかかわってくるので、その辺のライブラリとか使うときには最初に確認したほうがいいやつです。

 

これは3次元球面 S^3 = \{(x,y,z,w)\in\mathbb{R}^4\mid x^2+y^2+z^2+w^2=1\} を群とみなすことができるという話でもあります。1次元球面 (円周) S^1 = \{(x,y)\in\mathbb{R}^2\mid x^2+y^2=1\}複素数と見做して回転したりできるようになったのと似たような状況ですね。

3次元回転

さて、Quaternion を使って3次元回転をうまく表現することができます。順にやり方を考えていきます。

180°回転

まず、適当な3次元ベクトル \mathbf{p} を Quaternion と見做し、なんとなく適当な単位 Quaternion q = \mathbf{v}左から掛けてみます。

q\mathbf{p} = \mathbf{v} \times \mathbf{p} - \mathbf{v}\cdot\mathbf{p} です。これは \mathbf{p}\mathbf{v} と平行な成分と直交する成分に分解している式であると言えます。

つまり、\mathbf{p} = (\mathbf{v}\cdot\mathbf{p})\mathbf{v} + (\mathbf{p} - (\mathbf{v}\cdot\mathbf{p})\mathbf{v}) を使って、前者の情報がスカラー成分 -\mathbf{v}\cdot\mathbf{p} に、後者の情報がベクトル成分 \mathbf{v}\times\mathbf{p} に収まっている (外積を取ると平行成分がちょうど消えます) わけです。

ついでに外積を取ったのでベクトルの向きは「\mathbf{v} を軸として点 \mathbf{p} を90°回転した向き」を向いているはずです。

例えば \mathbf{p} = (3,4,1), \mathbf{v} = (0,1,0) とすると q\mathbf{p} = j(3i+4j+1k) = 1i-3k-4 です。\mathbf{v} すなわちY軸と直交する成分である (p_x,p_z) = (3,1) が90°まわって (1,-3) に移っていることがわかります。残った p_y = 4スカラー部分にやってきています。

 

今度は q右から掛けてみます。すると \mathbf{p}q = \mathbf{p}\times\mathbf{v} - \mathbf{p}\cdot\mathbf{v} = -\mathbf{v}\times\mathbf{p} - \mathbf{v}\cdot\mathbf{p} です。

ということは先程とはベクトル部分の向きが違うということになります。

…そこで、\mathbf{p} に対して「q を左から掛け、q^{-1} を右から掛ける」ということをやってみます。そうすると今度は「ベクトル部分の向きが揃い」、ついでに「スカラー部分の符号が反転するから結果的に0になる」気がします。

実際これはそうなります。

このとき掛ける順番は関係ありません。何故なら (qv)q^{-1} = q(vq^{-1}) というのは交換法則ではなく結合法則で、これは Quaternion も満たすからです。順番が関係ないということはお互いのやることが「すり抜ける」ということなので、愚直に考えたような手法がおおよそ成功します。

q\mathbf{p}q^{-1} = (\mathbf{v}\times\mathbf{p} - \mathbf{v}\cdot\mathbf{p})(-\mathbf{v}) = \mathbf{v}\times(\mathbf{v}\times\mathbf{p}) + (\mathbf{v}\cdot\mathbf{p})\mathbf{v} です。

つまり「垂直成分には \mathbf{v} による90°回転が2回掛かって180°回転になって」「並行成分はそのまま残る」というわけです。これは紛れもなく\mathbf{v} による点 \mathbf{p} の180°回転です。いいですね。

もう少し書くと、Quaternion を掛ける行為は「4次元空間上で2平面を同時に同じ角度回す操作 (Isoclinic rotation)」になっています。j を左から掛けるということは「XZ平面で90°回し、YW平面で90°回すこと」に対応します (だから並行成分がスカラー部に降りてくる)。逆に j を右から掛けるということは「XZ平面で-90°回し、YW平面で90°回すこと」に対応します (どちらも90°回ってますね)。なので右から掛けるときに逆回転にしてあげることで「XZ平面で180°回し、YW平面で0°回すこと」になるわけです。

好きな軸で180°回す方法は手に入れたので、それを拡張していくことにします。

3次元回転のAngle-Axis表現

まず「3次元回転」とは何かというと、「原点を動かさず」「点と点の距離を保つ変換で」「反転ではないもの」のことです。「点と点の距離を保つ (線形) 変換」たちには直交群 O(3) という名前がついています (3は3次元の3)。「反転ではない」場合は特殊直交群 SO(3) という名前がついています。こいつが回転です。

とは言えあまりその性質は今は使わなくて。

3次元で物体が回っているとき、なんとそこには回転軸というものが存在します。もう少し丁寧に言うと、まず「回転」というのは空間上の2軸を取り出し、その2軸が張る平面で空間全体を回す行為です。

X軸とZ軸を使って回すとき、それらに直交するY軸の上の物体は一切動きがありません。これを回転軸と言います。3次元なので、2個消費すると1個残るわけです (これが Hodge dual ですね…)。

ある意味で正しい言い方としては「XZ平面で回す」なんですが (2-vectorとしての扱い)、習慣的にこれを「Y軸で回す」と言うわけです。

どんな回転であっても、それが含む回転平面に対して法線が取れて、それが回転軸になります。

そして、「どれくらい回転するか」は回転軸回りの角度 (右ねじ的なイメージ) で表現できます。そうすると任意の3次元回転は、回転軸と回転角度で表現できるということになるわけです。

これは4次元では成り立ちません (回転平面が2個存在することがあるので)。2次元だと逆に回転軸が自明なのでいらないですね。

ちなみに、「軸を \mathbf{u} とした \theta 回転」は、「軸を -\mathbf{u} とした -\theta 回転」と等しいです。

ねじる向きがちょうど反転するわけですね。

任意回転

任意回転を Quaternion で表現する方法を考えようと思います。まず3次元の任意の回転は2つの180°回転で表現できることが知られています。

つまりどんな軸 \mathbf{u} 周りのどんな角度 \theta での回転であっても、2つの長さ 1 のベクトル \mathbf{a},\mathbf{b} で表現できるのです。

この \mathbf{a},\mathbf{b} は「まず \mathbf{u} と直交する単位ベクトルを適当にとって \mathbf{a} とする」「\mathbf{a}\mathbf{u} を軸にして \pi+\theta/2 回転したベクトルを \mathbf{b} とする」ことで得ることができます。かんたん。

\mathbf{a} 軸で180°回転してから \mathbf{b} 軸で180°回転する」ことで元の回転を再現できて、例えば \mathbf{a} 上のオレンジ色の点は、まず \mathbf{a} 軸180°回転によっては変化せず、\mathbf{b} 軸180°回転によって手前の位置に来ます。これは \mathbf{u} を軸として \theta 回転した位置にあたりますね。

 

これを使って実際に点 \mathbf{p} を Quaternion を用いて変換するとすると、まず \mathbf{a} で変換して \mathbf{apa}^{-1}、さらに \mathbf{b} で変換して \mathbf{p}' = \mathbf{b}(\mathbf{apa})^{-1}\mathbf{b}^{-1} = \mathbf{bapa}^{-1}\mathbf{b}^{-1} となりますね。

ところで \mathbf{a}^{-1}\mathbf{b}^{-1} = (\mathbf{ba})^{-1} です (逆元に関する定理)。つまり q = \mathbf{ba} とすると、\mathbf{p}' = (\mathbf{ba})\mathbf{p}(\mathbf{a}^{-1}\mathbf{b}^{-1}) = (\mathbf{ba})\mathbf{p}(\mathbf{ba})^{-1} = q\mathbf{p}q^{-1} と表現できます!

すなわちこの q が、「軸 \mathbf{u} 周りの \theta 回転を表現する Quaternion」と言えるわけです!点 \mathbf{p} を回転するには q\mathbf{p}q^{-1} を計算すればよいのですね。特別な場合として、\theta = \pi なら q = \mathbf{u} となると言えそうです。

 

さて、q = \mathbf{b}\mathbf{a} = \mathbf{b}\times\mathbf{a} - \mathbf{b}\cdot\mathbf{a} なわけですが、まず \mathbf{b}\times\mathbf{a} の向きは \mathbf{u} のはずです (\mathbf{a} にも \mathbf{b} にも直交するベクトルなので)。大きさは \sin(\theta/2) ですね (幾何的な外積の性質)。同様に \mathbf{b}\cdot\mathbf{a} の大きさは -\cos(\theta/2) になります。

即ち、どんな \mathbf{a} を選んだかに依らず、q = \mathbf{u} \sin(\theta/2) + \cos(\theta/2) になるということです。実際、\theta = \pi のときは \sin(\theta/2) = 1, \cos(\theta/2) = 0 なので期待通り q = \mathbf{u} になります。

なんだか知らないうちに角度に 1/2 がついてきましたが、これは「左右で挟むので、半回転ずつやればいい」みたいな気持ちで捉えても良いと思います (4次元回転の話における議論はそのまま任意回転に拡張できるのです)。

 

というわけで任意の3次元回転が表現できるようになりました。逆に、任意の単位 Quaternion は \mathbf{u}\sin(\theta/2) + \cos(\theta/2) の形式で表現できるので、3次元回転と Quaternion はまさしく対応していると言えます。

が、1対1対応ではありません

というのは、360°回転を表す Quaternion は、無回転を表す 1 ではなくちょうど -1 になるのです (\mathbf{u}\sin(\pi) + \cos(\pi) = -1 なので)。どんな軸だとしても -1 になるので「360°回転を表す Quaternion がいっぱいある」わけではありません。720°回すと 1 になりますし。だけど、全ての回転にはちょうど1つ「ひっくりかえった版」が付随するのです。

ひっくり返ったとしても点を回した結果は変わりません。それは Quaternion を左右から掛けるために -1 をちょうど打ち消しあうからです。だから3次元回転としてみると同じものなのです (Quaternion としては別物ですよ)。

実際、任意の3次元回転にはそれに対応する Quaternion がちょうど2つ存在して、しかもこの対応関係は「めちゃくちゃきれい」(「リー代数が同じ」) であることが知られています。

とりあえずまず、細かいところについて見ていきます。

局所構造

何かというと、「ある Quaternion の近くにある Quaternion」について考えたい、という話です。

「回転しない Quaternion」の近くには「どこかちょっとだけ回す Quaternion」があって、「Y軸で90°回す Quaternion」の近くには「Y軸でおよそ90°くらい回す Quaternion」があるはずです。

実際には「Y軸でおよそ90°くらい回す Quaternion」というのは「Y軸で90°回したあとに、どこかちょっとだけ回す Quaternion」として表せるので、考える必要があるのは「どこかちょっとだけ回す Quaternion」だけです。

1 の周りにある構造」を、好きな q で回してしまえばそれが「q の周りにある構造」になります。群なので、各元は位相的には区別できません (周りにある構造はみんな同じという意味)。

「近い Quaternion」っていう状況は、例えば物体に角加速度を与えたり、好きな向きにゆっくり向けたいようなときに出てくるものです。よくあるので、大事です。

接空間

\mathbf{u} でほんのすこし、 \varepsilon 回転する Quaternion q を考えます。\varepsilon を小さくしていけば無回転に近づいていきます。

このとき q = \mathbf{u}\sin\varepsilon + \cos\varepsilon ですが、\varepsilon が無限小 (\varepsilon^2 = 0) であると捉えて \sin\cos を近似します。

すると q = \mathbf{u}(\varepsilon + o(\varepsilon^3)) + (1 + o(\varepsilon^2)) = 1 + \varepsilon\mathbf{u} です。

つまり 1 の周りには 1 + \varepsilon\mathbf{u} などの Quaternion が居そう、ということです。

\varepsilon\mathbf{u} というのは結局「任意の微小な3次元ベクトル」なので (\mathbf{u} は長さ 1 だが、\varepsilon によって好きな微小長さにできるため)、結局「Quaternion の周りには3次元ベクトルの成す構造がある」ということになります。この「ある点の周りにある構造」は接空間と呼ばれます。

まぁこれは「X軸でちょっと回す」「Y軸でちょっと回す」「Z軸でちょっと回す」という独立な3方針がある、という話です。

接空間はこうやって微小変位に基づいて構成できるのですが、それは要は「ある点の周りの様子を線形近似している」ようなものです (テイラー展開して一次の項まで残した状態)。なので、一般に接空間はベクトル空間になってくれます。

Quaternion だと (3次元球面なので) わかりにくいですが、2次元球面の接空間だとわかりやすいと思います。これです。

つまり、球上の一点に対して、文字通り平面が接しているのです。どんな点に対してもそれに接する平面が存在します。そういうことです。平面はベクトル空間を成します (接している点が原点)。

こういう、「周りの構造」を微小変位を用いて素直に議論できるような空間が可微分多様体と呼ばれます。また、可微分多様体で (きれいに) 群になっているものをリー群と呼びます。単位 Quaternion たちは3次元のリー群になります。

リー群における単位元 (1) の接空間は、なんやかんやあって (リー群に付随する) リー代数と呼ばれます (つまり大事なものだということです)。あまり細かい話はできませんが…。

指数写像

今のところ \mathbf{v}=\varepsilon\mathbf{u} を「微小変位」だと捉えてきましたが、リー群における接空間はもっと扱いやすい存在だったりします。

1+\mathbf{v}\mathbf{v}\mathbf{0} に近づくにすれ単位 Quaternion にだんだん近づいていきますが、単位 Quaternion ではありません。しかし、接空間をぺたっと回してあげることで 1+\mathbf{v}相当する単位 Quaternion を取り出すことができるのです。

「ぺたっと」っていうのは、こういうことです。

できそう。\mathbf{v} の方向に向かってずいずいと紙を這わせていく感じですね。

これによってリー代数上の点 \mathbf{v} を、リー群上の点 \exp\mathbf{v} に変換することができます。この変換を指数写像と言います。

要は「どれくらい回すか」を示すベクトルがあると、それに対して Quaternion を構成できるという話です。うれしい。

実際にこれは \mathbf{v} = \phi\mathbf{u} に対して \exp\mathbf{v} = \mathbf{u}\sin\phi+ \cos\phi で定義されます。ここで \phi= \lVert \mathbf{v}\rVert, \mathbf{u} = \mathbf{v} / \phi です (長さと向きに分解しただけ)。

例えば、\mathbf{v} = (0,\pi/2,0) というベクトルに対しては \exp\mathbf{v} = j です。

…逆に言えば、単位 Quaternion q = \mathbf{u}\sin\phi + \cos\phi に対し、それに指数写像で対応するようなベクトルを \log q = \phi\mathbf{u} で与えることができます。\exp(\log q) = q ですね。

なんで \exp\log という記号を使うのかというと、それがぴったりだからなんですが、その辺は面白いので調べてください。

というわけでお互いに行き来できることがわかったので、これで「Quaternion のベクトル表現」を手に入れることができました。

例えば3次元回転の話を思い出すと、「軸 \mathbf{u}\theta 回す回転」を表す Quaternion \mathbf{u}\sin(\theta/2) + \cos(\theta/2) に指数写像で相当するベクトルは \displaystyle\frac12\theta\mathbf{u} であるということになります。

ただし、ぴったり行き来するわけではありません。ある Quaternion に対応するようなベクトルはいっぱいあります (例えば \phi2\pi を足しても同じ Quaternion になります)。

ちなみに、一般に「回転ベクトル」と呼ばれるものは \theta\mathbf{u} そのものだと思います (SO(3) 的にはそれが正しいらしい)。直観的にもこっちのほうが嬉しそう。

 

ところでこの「ベクトルとしての微小回転」というのは、例えば角速度とかで出てくる状況です。むしろ接空間というのは「ある点における速度の成す空間」ということでもあります。

ふつうの空間 (\mathbb{R}^3 上の移動) だと速度も同じように表現できるわけですが、一般には一致するとは限らないのです。

例えば円周 S^1 上を動くような点の速度を考えると、位置はぐるぐる回ってもとに戻りますが速度はいくらでも大きくなりえます。

位置は x^2 + y^2 = 1 となるような (x,y) として表現できますが、同時に (\cos\theta,\sin\theta) = (x,y) となるような \theta でも表現できます。

そうするとこの「角速度 v」によって点は \Delta t 秒後に \theta + v\Delta t の位置にくる、と言えます。単位は \mathrm{rad/s} ですね。

これと同様にして。

現在の回転を表す Quaternion q と角速度ベクトル \mathbf{v} があるとき、「q + \mathbf{v}\Delta t」みたいな感じで \Delta t 秒後の回転を得られる…気がします。

しかし Quaternion には足し算はありません。なので一般にはこの式は使えるとは限らず、「位置の空間と速度の空間の違い」を示してあげる必要があるのです。

平面上の回転角とその速度だと、計算上は空間の違いが見えなくなります (どっちも単なるスカラーなので)。2\pi の違いを無視していいかどうか、という意味では全然違うんですが。速度の方には \Delta t が掛かることが前提なので、元々ものすごい速度だったとしても回転角はちっちゃくなる (周期に届かない) 可能性があります。まぁまず単位が違うけど。

そこで、まず \Delta t 分の回転を表す \Delta q = \exp(\mathbf{v}\Delta t) を作ります。この分だけ q から動かせば良いので、\Delta q\ q を計算すれば良さそう。

ちなみに q \Delta q だと意味が変わっちゃいますね。3次元回転として使うことを考えると q (\Delta q \mathbf{p} \Delta q^{-1})q^{-1} になって「無回転の状態で \Delta q 回し、そして q 回す」になっちゃいます。

「間違っている」 わけではないです。この q は回転を表すとみなしていて、今回は q\mathbf{p}q^{-1} で回すのでこの順番が適切なだけです。

というわけで、物体をゆっくり回していくことができるようになりました。

slerp

逆に、「現在の物体の姿勢 q_0 を特定の姿勢 q_1 にゆっくり持っていきたい」ということがあると思います。

まぁ既に道具は揃っています。まず \Delta q = q_1 q_0^{-1} を (左から) 掛ければ姿勢は q_0 から q_1 になります。

で、指数写像たちを使うとこれをちっちゃくできます。例えば 0.5\log \Delta q は「\Delta q の回転の半分を表すベクトル」です。

なので、\exp(0.5\log \Delta q) は「\Delta q の半分の回転」です。0.5 以外にも好きな数が使えます。おわり

これを slerp (Spherical Linear intERPolation) と呼びますが、そんな驚くようなものでもないですね。

回転のベクトル表現がわかっていれば素直に出てきます。

 

ただ少しだけ気をつけるべき点があります。例えば「最初から全部ベクトルとして計算する」 \exp(\mathrm{lerp}(\log q_0, \log q_1, 0.5)) こともできるわけですが、これはさっきとは結果が違うことがあるのです。

これは \log が不連続になる場合があるという話です。定義では「q = \mathbf{u}\sin\phi + \cos\phi に対して \log q = \phi\mathbf{u}」となっていましたが、\mathbf{u}\phi も一意に定まりません

例えば -\mathbf{u}\sin(-\phi) + \cos(-\phi) = q ですし、勿論 \mathbf{u}\sin(\phi+2\pi) + \cos(\phi+2\pi) = q でもあります。

一意に定めようとする (どれを選ぶかを決めてしまう) と、空間がぱきっと割れます。例えば 0 \le \phi \lt \pi を徹底しよう、と言うことができます。が、そうすると「段々回っていく Quaternion」を考えたときに \phi が急激に変化するタイミングがある、ということになります。

同様にして、\log q_0\log q_1 は「実際にいくらこの2つの回転が近いものだったとしても」\log の定義によっては全然違うベクトルになっている可能性があるのです。これを使って補間を行うと「ぐるんっ」って回っちゃいます。

で、不連続性自体は避けられません。なのでできるだけ不連続にならないように気をつけて操作する必要がでてきて、だから最初に q_0q_1 の「違い」だけを接空間に持っていくことにしたのです。

そうすると「最短経路」を通るようになって、最小限の不連続性で済むようになります。うれしい。

1:2対応

3次元回転と単位 Quaternion は 1:2 対応するという話をしました。それについて実用的な話をちょっと。

Unity で物体を回したりしたとき、それに付随する Quaternion が連続的に変化する保証は実はありません。

つまり Unity はこっそりと q ではなく -q を選んでいることがあります。…この違いは、Quaternion の内部を触らなければわかりません。

https://docs.unity3d.com/ja/2019.4/ScriptReference/Quaternion.html

自分でベクトル表現とかを作ろうとすると不連続性が「みえる」ようになります。もしも連続にする必要があるなら (何らかの理由で w 成分を直接見たり、4次元回転を表現したくて q-q を区別しなきゃいけないとかなら) 自分で対処しなければなりません (1個前を記録しておいて、q-q のうち近い方を採用する、など)。

…見ての通り使うことは殆どありません

これは「Quaternion の要素ごとに線形補間したりすることが根本的にまずい」という例でもあります。要素にさえアクセスしなければいいのです。

色々長々と書いてきましたが、結局与えられているインターフェースで十分なことができるなら、それ以上は深追いする必要は無いのです。

私はやる必要がありました。

おまけ: Unity の回転 Animation

transform.rotation で得られる回転は Quaternion ですが、AnimationClip に記録されているのは Euler 角というよくわからないものです。

こいつは Quaternion でも回転ベクトルでもなく、なんか意味わからん不思議物体なんですが、どうしてか一般的に使われています。

ちゃんと言うと、これは「地面方向がある世界」を仮定すると合理的 (Y軸でくるくる周り、Z軸で画面を傾け、X軸で上下を見る) なのですが、一般的な空間 (空中でくるくる回ってるときとか) においてはその選択が恣意的すぎるという状況です。 特に数学の幾何という分野は「空間全体を回転しても空間の性質は変化しない」ことを念頭に置いてるところがあるので、そういう考え方と相性が悪いのです。 まぁ、ぶっちゃけ計算とも相性が悪いんですけど。

とは言えそれだと slerp による「きれいな補間」ができません。そこでちゃんと Quaternion として扱う方法が用意されているようです。

ただ、そうすると720°回転ができなくなります。そうですよね。Quaternion としては同じものになってしまうので。

実際には360°回転もできません。3次元回転として同じものなので。

さらに言えば180°回転も思い通りにいきません。どっちに回すべきかわからないので。179.99...°までは大丈夫です。

まぁ、AnimationClip なのでキーをいっぱい打てば勿論ちゃんと動くんですけど。

接空間に基づいた動きをしているわけではないのでこうなっちゃってるみたいです。難しいね…。

おまけのおまけ: UnrealEngine の回転

UEには回転を表す Rotator というものがありますが、なんとこいつの中身は Euler 角です

えー、UEというのは元々FPSツクールなので、みんな地面に立ってるんだと思ってるんでしょうね。んなわけないだろ。

こんなので線形補間とかしたら綺麗な動きにはならないはずなんですが、UE使っているような人たちはきっとそういうところは見えないんでしょうね。

UEにも一応 Quaternion は用意されていますし、内部的にはいろいろ使われてるんでしょうけど (物理演算とかで Euler 角を扱うのは無理だと思います) そういう「正しい世界は難しいからみんなは (正しくないけど) 直感的に操作できる方法を使ってね」みたいな考えがもうダメなんですよ。

そういうときって学習を進めていくと「過剰な簡単化」によって逆に詰まったりするものですから。だってUE使ってる人たちってUEに振り回されてるでしょ。アレは使えていると思わされてるだけです。

Unity のように「Inspector では操作しやすく Euler 角で表示」「内部的には全部 Quaternion で処理」するのが最も誠意ある態度だと思います。ちゃんと「さらなる学習をしたい人」に道が用意されてるわけです。

本来「正しさ」というのは寄り添ってくれるものなんです。どうすればいいかを教えてくれるもの。それを捨てて狭いところに閉じこもってたら何にもなりません。

直す気はないんでしょうけどね、大量のレガシーが大切だと思いますので。Unity の SRP はすごいよ。

UE、そういう点が「論外」なので論外なんですが、どうして使う人が居るのか…。

自浄作用が働かないって怖いですよね。

まとめ

いろいろやってきたので一覧にしますね。

  • 基本
    • Quaternion は実数 x,y,z,w を用いて q = xi+yj+zk+w として表される数
    • xi+yj+zk をまとめて q = \mathbf{v} + w と表記すると便利
    • 掛け算 q_1 q_2 = (\mathbf{v}_1 + w_1)(\mathbf{v}_2 + w_2) = \mathbf{v}_1\times \mathbf{v}_2 + \mathbf{v}_1w_2 + \mathbf{v}_2w_1 + w_1w_2 - \mathbf{v}_1\cdot\mathbf{v}_2
    • 大きさ (ノルム) \lVert q\rVert = \sqrt{x^2+y^2+z^2+w^2} = \sqrt{\lVert \mathbf{v}\rVert^2 + w^2}
    • 大きさ 1 の Quaternion を単位 Quaternion と言う (以降これしか扱わない)
    • 単位 Quaternion は大きさ 1 のベクトル \mathbf{u} と実数 \phi を使って q = \mathbf{u}\sin\phi + \cos\phi と表せる
    • 逆元は q^{-1} = -\mathbf{v} + w で、実際に q^{-1}q = qq^{-1} = 1
  • 3次元回転
    • 大きさ 1 のベクトル \mathbf{u} を Quaternion とみなすと、点 \mathbf{p}\mathbf{u} 軸で180°回転した点が \mathbf{u}\mathbf{p}\mathbf{u}^{-1} と表せる
    • 任意の3次元回転は2回の180°回転で表せる
    • \mathbf{u}\theta 回す回転を表す Quaternion を q = \mathbf{u}\sin(\theta/2) + \cos(\theta/2) で得ることができ、q\mathbf{p}q^{-1} で回転できる
      • 「左から掛ける」のと「右から逆元を掛ける」ので合計 \theta 回転するみたいな感じになってる
    • q-q は3次元回転としては同じもの
  • 接空間
    • 微小変位をベクトルを使って表現できる
    • ベクトル \mathbf{v} = \phi\mathbf{u} に対して \exp\mathbf{v} = \mathbf{u}\sin\phi + \cos\phi を考えることができ、逆 (\log q) も取れる
    • つまり Quaternion と、それを表すベクトルとの対応が (そこそこ) 取れる
    • 回転 q が角速度 \mathbf{v} で動いていく時 \Delta t 秒後には \exp(\mathbf{v}\Delta t) q になってる
    • q_0q_1 の間の補間を \exp(r\log q_1q_0^{-1}) q_0 で計算できる (slerp)
    • 不連続点に注意
  • その他
    • Quaternion の要素を触りに行くときには連続性に注意したい
    • Euler 角は意味わからん

とりあえず、Quaternion に纏わる関数ライブラリとか作るときには \exp\log を入れたらいいと思います。

References