クォータニオン

回転行列の難点

メモリ

回転には3つ(ピッチ、ヨー、ロール)しか自由度がないにも関わらず、回転行列は9つの要素を持つ。

演算コスト

ベクトルと行列の乗算は3つのドット積(9つの乗算と6つの加算)を必要とする。

補間の難しさ

例えば、始点Aから終点Bまでカメラをスムーズに動かす際に、AとBの中間の多数の回転を見つけなければならない。

2つの姿勢が行列で表されていると、その間の指定された割合の回転を見つけることは難しい。

単位クォータニオン

次の制約に従うクォータニオンを単位クォータニオンという。

q=(x,y,z,w)wherex2+y2+z2+w2=1q = (x, y, z, w) \quad \text{where} \quad x^2 + y^2 + z^2 + w^2 = 1

単位クォータニオンは3次元ベクトルに4番目のスカラー座標をプラスしたものとして、3次元の回転を表すことができる。

q=(qv,qs)=(asinθ2,cosθ2)q = (\bold{q}_v, q_s) = \left(\bold{a}\sin\frac{\theta}{2}, \cos\frac{\theta}{2}\right)

a\bold{a}は回転軸に沿った単位ベクトルであり、θ\thetaは回転角。

回転方向は右手の法則に従うので、親指がa\bold{a}の方向を向いていれば、正の回転は折り曲げた指の方向を向くことになる。

クォータニオンのグラスマン積

2つのクォータニオンppqqがそれぞれ2つの回転P\bold{P}Q\bold{Q}を表すとする。

このとき、クォータニオンの積pqpqは、回転Q\bold{Q}の後に回転P\bold{P}を適用する複合回転を表す。

pq=(pv,ps)(qv,qs)=(pvqs+qvps+pv×qv,psqspvqv)\begin{aligned} pq &= (\bold{p}_v, p_s)(\bold{q}_v, q_s) \\ &= (\bold{p}_v q_s + \bold{q}_v p_s + \bold{p}_v \times \bold{q}_v, p_s q_s - \bold{p}_v \cdot \bold{q}_v) \end{aligned}

このように定義した積をグラスマン積という。

クォータニオンの共役

クォータニオンpp共役pp^*は、ベクトル部分の符号を反転させたものである。

p=(pv,ps)p^* = (-\bold{p}_v, p_s)

クォータニオンの逆数

クォータニオンpp逆数p1p^{-1}と表し、元のクォータニオンを乗算したときに、スカラーの1となるクォータニオンとして定義する。

pp1=0i+0j+0k+1=1pp^{-1} = 0\bold{i} + 0\bold{j} + 0\bold{k} + 1 = 1

最初の3つの成分はsin(0)=0\sin(0) = 0より、最後の成分はcos(0)=1\cos(0) = 1より導かれる。

ところで、sin(θ)=sin(θ)\sin(-\theta) = -\sin(\theta)であるため、ベクトル部分の符号を反転させたもの(共役)が、逆回転を表すクォータニオンだと考えられる。

数学的には、クォータニオンの逆数は、元のクォータニオンの共役を、元のクォータニオンのノルムの2乗で割ったものとして求められる。

p1=pp2p^{-1} = \frac{p^*}{|p|^2}

3D回転を表すクォータニオンでは、ノルムは常に1であるので、想像通り逆数は共役と等しくなる。

ノルムの計算は2乗での除算というコストが高い処理であるため、それを避けられるのは好都合である。

積の逆数と共役

クォータニオンの積の共役は、個々のクォータニオンの共役の、逆順の積に等しい。

(pq)=qp(pq)^* = q^* p^*

同様に、クォータニオンの積の逆数は、個々のクォータニオンの逆数の、逆順の積に等しい。

(pq)1=q1p1(pq)^{-1} = q^{-1} p^{-1}

ベクトルを回転させる

ベクトルv\bold{v}に対応するクォータニオンは、ベクトルの成分をベクトル部分に、スカラーの0をスカラー部分に持つクォータニオンである。

v=(vx,vy,vz,0)\bold{v} = (v_x, v_y, v_z, 0)

ベクトルv\bold{v}をクォータニオンqqで回転するためには、次の式を計算する。

rotate(q,v)=qvq1rotate(q, \bold{v}) = q \bold{v} q^{-1}

クォータニオンは必ず単位長になるので、これはクォータニオンの共役を使うのに等しい。

rotate(q,v)=qvqrotate(q, \bold{v}) = q \bold{v} q^*

クォータニオンの連結

クォータニオンq1q_1q2q_2q3q_3がそれぞれ回転Q1\bold{Q}_1Q2\bold{Q}_2Q3\bold{Q}_3を表すとする。

まず回転Q1\bold{Q}_1を適用した後に回転Q2\bold{Q}_2を適用し、最後に回転Q3\bold{Q}_3を適用したい場合、回転後のベクトルは次のように計算できる。

v=q3q2q1v(q3q2q1)1=q3q2q1vq11q21q31\begin{align*} \bold{v}' &= q_3 q_2 q_1 \bold{v} (q_3 q_2 q_1)^{-1} \\ &= q_3 q_2 q_1 \bold{v} q_1^{-1} q_2^{-1} q_3^{-1} \\ \end{align*}

クォータニオンの行列表現

クォータニオンq=(qv,qs)=(x,y,z,w)q = (\bold{q}_v, q_s) = (x, y, z, w)とすると、回転行列R\bold{R}は次のように求めることができる。

R=(12y22z22xy2zw2xz+2yw2xy+2zw12x22z22yz2xw2xz2yw2yz+2xw12x22y2)\bold{R} = \begin{pmatrix} 1 - 2y^2 - 2z^2 & 2xy - 2zw & 2xz + 2yw \\ 2xy + 2zw & 1 - 2x^2 - 2z^2 & 2yz - 2xw \\ 2xz - 2yw & 2yz + 2xw & 1 - 2x^2 - 2y^2 \\ \end{pmatrix}

回転の線形補間

回転の最も計算量が少ない補間は、4次元ベクトルの線形補間Lerpをクォータニオンに対して実行することである。

2つのクォータニオンqAq_AqBq_Bがそれぞれ回転AABBを表すとすると、次のようにAAからBBの中間のtt%の回転を求めることができる。

lerp(qA,qB,t)=qA(1t)+qBtqA(1t)+qBt=normalize(((1t)qAx+tqBx(1t)qAy+tqBy(1t)qAz+tqBz(1t)qAw+tqBw)T)\begin{align*} lerp(q_A, q_B, t) &= \frac{q_A (1 - t) + q_B t}{|q_A (1 - t) + q_B t|} \\ &= normalize\left(\begin{pmatrix} (1 - t)q_{Ax} + tq_{Bx} \\ (1 - t)q_{Ay} + tq_{By} \\ (1 - t)q_{Az} + tq_{Bz} \\ (1 - t)q_{Aw} + tq_{Bw} \\ \end{pmatrix}^T\right) \end{align*}

lerp演算は一般的にベクトル長を維持しないため、補間された結果のクォータニオンは再び正規化する必要がある。

回転の球面線形補間

lerpは球面に沿ってではなく、球の弦に沿って補間する。

このため、パラメータttが一定の率で変化しても、一定の角速度を持たない(終点ではゆっくりで、途中では速くなる)回転アニメーションが生まれてしまう。

球面線形補間Slerpは、球面上を一定の角速度で回転するように補間する。

Slerpを求める公式はLerpの公式と同じだが、重み(1t)(1 - t)ttが、2つのクォータニオンがなす角の正弦を含むwpw_pwqw_qに置き換えられている。

slerp(qA,qB,t)=wpp+wqq\begin{align*} slerp(q_A, q_B, t) &= w_p p + w_q q \\ \end{align*}

ここで、

wp=sin((1t)θ)sin(θ)wq=sin(tθ)sin(θ)θ=cos1(qAqB)\begin{align*} w_p &= \frac{\sin((1 - t)\theta)}{\sin(\theta)} \\ w_q &= \frac{\sin(t\theta)}{\sin(\theta)} \\ \theta &= \cos^{-1}(q_A \cdot q_B) \end{align*}