エフアンダーバー

個人開発の記録

【Unity】 Quaternionの回転順の話

Unityも2020.1になるとのことなので、ひとつ今更な話でもしようかと。

UnityEngine.Quaternionoperator *を見ると 次のように説明されています

Combines rotations lhs and rhs.

Rotating by the product lhs * rhs is the same as applying the two rotations in sequence: lhs first and then rhs, relative to the reference frame resulting from lhs rotation. Note that this means rotations are not commutative, so lhs * rhs does not give the same rotation as rhs * lhs.

和訳するとこんな感じ。

lhs と rhs の回転を結合します。

lhs * rhs という積による回転は二つの回転を順に適用するのと同じです。 まず lhs を、次に lhs の回転結果の座標系に対して rhs を適用します。 これは回転が非可換であることを意味し、そのため lhs * rhs と rhs * lhs は同じ回転にならないことに注意してください。

日本語版の記述。

lhs と rhs の回転を組合わせます。

lhs * rhs の積の値で回転させることは、lhs を最初に、それから rhs というように 2 つの回転を順番に行うことと同じです。 つまり、回転は、非可換性で、lhs * rhs は rhs * lhs と異なる回転を表すということに気を付けてください。

……まあ、よくあることですが間違ってますね。回転順が逆になっています。

間違い自体はずいぶん前から気づいていたんですが、 昨年あたりにこれに関するコメントをもらったのと、 最近またそれっぽい検索ワードが記録されていたので記事にしてみることにしました。

正直、この記事書き始めるまで英語の記述から間違っているんだと思っていましたが、 よく読んでみると英語の方は間違っているとは言えないみたいですね(かなりアレな記述には違いないと思いますが)。

検証

何はともあれ、まずは本当に間違っているのかどうか検証します。 クォータニオンは結構ややこしいので勘違いという可能性も無きにしも非ず。

検証コードは次の通り。

var v = Vector3.one;
var q1 = Quaternion.AngleAxis(10f, Vector3.forward);
var q2 = Quaternion.AngleAxis(20f, Vector3.up);

var u = q2 * (q1 * v);
var q2q1v = (q2 * q1) * v;
var q1q2v = (q1 * q2) * v;

Assert.That(q2q1v == u);
Assert.That(q1q2v != u);

やはり、右側の回転が先ですね。 まあ、そうでないと括弧の位置によって結果が異なるというひどい事態に陥ってしまうので。

一応、Quaternionには回転順がZ→X→Yと明記されているEuler関数があるので、こちらでも確かめてみます。

var euler = Quaternion.Euler(10f, 20f, 30f);
var rx = Quaternion.AngleAxis(10f, new Vector3(1f, 0f, 0f));
var ry = Quaternion.AngleAxis(20f, new Vector3(0f, 1f, 0f));
var rz = Quaternion.AngleAxis(30f, new Vector3(0f, 0f, 1f));

var ryxz = ry * rx * rz;
var rzxy = rz * rx * ry;

Assert.That(ryxz == euler);
Assert.That(rzxy != euler);

右側が先で問題ないですね。

混乱の原因

これで終わりでもいいんですが、もう少しだけ考察を。

この回転順の間違い、逆にしてしまうとまったく別の回転となってしまうため、 使えば間違いに気付きそうなものですが、意外と気付かない人が少なくないようです。 何故なのかという話。

シーンビューの操作順との混同

Unityで3Dゲーム制作を始めた初心者からすると、 おそらくTransformの内部処理というのはまったく想像のつかない世界だと思います (なぜGameObjectではなくTransformが階層を持っているのかも謎なんじゃないかと)。 そのため、回転を理解する最も大きな助けとなるのはシーンビューの回転ツールのはずです。

デフォルトのLocalモードの場合、回転ツールでは例えばY軸で回転させると、回転軸としてのX軸やZ軸も同時に回転します。 そのため、Y→Xの順で回転させると、先のY軸回転で回転したX軸によって次の回転がなされます。

一方で、クォータニオンの回転における回転軸は常に固定です。 Y→Xの順であっても、X→Yの順であっても、それぞれの回転における回転軸は同じです(もちろん回転結果は異なります)。

では、回転ツールでY→Xの順で回転させたのと同じクォータニオンを得るにはどうしたらいいのかというと、 X→Yの順で回転するようにそれぞれのクォータニオンを合成します。 Y軸回転で回転したX軸によって回転させると考えるのではなく、あらかじめX軸で回転させておいた結果をY軸で回転させると考えます。

つまり、シーンビューでの操作順とクォータニオンの回転順はまったく逆になるのですが、 そもそもシーンビューでしか回転を認識していないと、頭の中でシーンビューを想像して回転させるので、 操作順と回転順を混同してしまうのではないかと思います。

演算子の適用順との混同

現在は削除されてしまったようですが、以前あるサイトで演算子の適用自体を回転の適用と勘違いしている人がいました。 つまり、q1 * q2 * q3(q1 * q2) * q3と同じだから、左側の回転が先に適用されると考えたようです。

確かに*は左結合の演算子なので、*の計算は左から行われますが、 これは回転を合成して新しい回転を生成する計算なので、回転の適用順とはまったく関係ありません。

回転適用q * v*が掛け算じゃないのでアレですが、 本来*は掛け算を表す演算子なはずなので結合法則くらいは普通成り立ちます。 (q1 * q2) * q3q1 * (q2 * q3)の計算結果は同じで、回転順や回転結果が変わってしまうことはありません。

余談:クォターニオン

少し短い記事になってしまったので全く関係ない余談でも。

クォータニオン周りで調べているとちょくちょく言われているのが、 Quaternionは発音的に「クォターニオン」じゃないかという話*1。 発音記号を見ると、確かに『タ』の後に長音が来そうな気がします。

んで、実際にWeb上でいくつかの発音を聞いてみたところ、 やはり『タ』の後に長音があるように聞こえるものがある一方で、 『クォ』の後に長音があるように聞こえるものもありました。 おそらく、『クォ』という長音っぽい発音に加えて、『タ』にアクセントがあるので前の音が伸ばされているように感じるのだと思います。

じゃあどっちを使うべきかというと従来通り「クォータニオン」かな、と思います。 そもそもカタカナ発音で伝わらない英単語というのは山ほどあるので、 カタカナで発音を正確に表現しようとするよりも日本語として発音しやすい方がいいというのがひとつ。 あとはすでに浸透しすぎた単語を変えようとすると違和感が強いというのがあります。 「クォターニオン」は「ティーム」とか「キャメラ」とか言われてる感じ。

ちなみに、さらに話が逸れますが、プログラミング関係で訳語の表記ゆれが激しい言葉として"Garbage Collection"があります。 「ガベージコレクション」だったり、「ガーベジコレクション」だったり、「ガーベッジコレクション」だったり、 「ガーベージコレクション」のこともあります。 で、発音的にはどれがいいのかというと、多分どれでもなくて、「ガービッジコレクション」じゃないかと。 まあ、結局は面倒くさくて「GC」って書くんですが……。

おわりに

今までも何度か記事に書いたことがありますが、Unityの翻訳は結構誤訳が多いです(総量が多いからというのもありますが)。 特に今回のような、わかりやすい日本語にしようとした結果、大事な単語を入れ忘れて意味が変わってしまっているケースが多い印象です。 ドキュメントも人が書くものである以上バグはあるので、あまり信じすぎない方がいいですね。

久しぶりに好き勝手書き散らかした記事書きました。 最近は真面目にいろいろ調べながら書くことが多かったので、こういうてきとーなの書くと謎の開放感があります。 しばらくはこんな感じでゆるく記事を書きたい所存。



執筆時のUnityのバージョン:2019.4.0f1

*1:もっと言うと「クァターニアン」じゃないかとも。クトゥルフ神話に出てきそうですね。