はじめに
行列における列優先,行優先と言った場合,データ保持形式と数学的表現の2つの視点があります.
初学者は時に混同して説明してしまいがちですが,それぞれ別の概念であり,各項目における行・列の相反が成立する可能性がありますので,分けて認識しましょう.
本記事では,3次元幾何計算の文脈において,それらがどのように異なるのか,互いの変換はどう行うかを解説します.ついでに,列優先,行優先と合わせて語られることのある右手系,左手系についても言及します.
データ保持形式における列優先,行優先
Column-major order, Row-major orderと書いた場合,こちらの意味になります.
本記事では,列優先データ順,行優先データ順と呼ぶことにします.
行列をプログラム内でデータ保持する場合,データアクセスの効率を考慮してメモリの連続領域にデータが置かれるのが多数です.そのデータの順が,行列において行と列のどちらが優先方向になるか,という意味になります.
例えば,行列$H$の中身のデータが$data_{H}=[v_1, v_2, v_3, v_4, v_5, v_6]$のようにメモリに並んでいた場合,列優先データ順,行優先データ順はそれぞれ
H_{col}=\left[
\begin{matrix}
v_1 & v_3 & v_5 \\
v_2 & v_4 & v_6
\end{matrix}
\right]
,\;\;
H_{row}=\left[
\begin{matrix}
v_1 & v_2 & v_3 \\
v_4 & v_5 & v_6
\end{matrix}
\right]
となります.列優先の場合は縦の,行優先の場合は横の方向に優先順位があります.
列優先データ順:
OpenGL, Eigen(デフォルトの場合), PhysX, MATLAB(デフォルトの場合), R
行優先データ順:
DirectX, OpenCV, OpenVR, numpy
なお,Eigenは行列定義の際にRowMajorを指定することで,内部のデータ順を行優先にすることができ,ColMajorが指定された行列にコピーしようとするとよしなに行・列順を維持するように列優先行列にしてくれます.
数学的表現における列優先,行優先
こちらは,数学的表現における話題になります.
本記事では,列優先表現,行優先表現と呼ぶことにします.
ベクトルを,列ベクトル,行ベクトルのどちらで表現するか,という点を基本に考えていきます.
ベクトルの形:
v_{col}=\left(
\begin{matrix}
a_1 \\
a_2 \\
a_3
\end{matrix}
\right)
,\;\;
v_{row}=\left(
\begin{matrix}
a_1 & a_2 & a_3
\end{matrix}
\right)
並進回転行列の形:
\left[R|T\right]_{col}=\left(
\begin{matrix}
r_{x} & r_{xy} && r_{xz} && t_{x} \\
r_{yx} & r_{y} && r_{yz} && t_{y} \\
r_{zx} & r_{zy} && r_{z} && t_{z} \\
0 & 0 && 0 && 1
\end{matrix}
\right)
,\;\;
\left[R|T\right]_{row}=\left(
\begin{matrix}
r_{x} & r_{yx} && r_{zx} && 0 \\
r_{xy} & r_{y} && r_{zy} && 0 \\
r_{xz} & r_{yz} && r_{z} && 0 \\
t_{x} & t_{y} && t_{z} && 1
\end{matrix}
\right)
合成変換:
H^{(1)} \circ H^{(2)}:\;\;\left\{
\begin{array}\\
p'_{col}&=&H^{(2)}_{col}\cdot H^{(1)}_{col} \cdot p_{col}\\
p'_{row}&=&p_{row} \cdot H^{(1)}_{row} \cdot H^{(2)}_{row}
\end{array}\right.
なお,列ベクトル,行ベクトルは,それぞれ一列ベクトル,一行ベクトルと考えると理解しやすいです.
学問の分野によってどちらが用いられるかが異なるようです.数学においては,基本的には列優先表現であるようです.行優先表現を使用する分野もあるようです1.
分かりやすくまとまっていますので,こちらの記事もご参照ください.
列優先表現:
OpenGL, OpenCV, OpenVR, PCL, PhysX, numpy, MATLAB, R
行優先表現:
DirectX
主な特徴 | 列優先表現 | 行優先表現 |
---|---|---|
合成変換 | 前から掛ける | 後ろから掛ける |
並進回転行列の形 | 右上が並進要素 | 左下が並進要素 |
マジョリティ | メジャー? | マイナー? |
3DCG | OpenGL | DirectX |
列優先表現にすると,ベクトルが縦一列に書けるので,紙に手書きするときに数式の横幅がかさ張らないという利点があります.数式をそのままプログラムに転記できるという点で,数学的歴史,資産の重厚な列優先行列が使われることが多いようです.
行優先表現にすると,合成関数の適用順に行列を後ろから掛けることになります.処理順に記述できたり,積代入演算子(*=)が違和感なく使えるようになるので,プログラミングのしやすさでは行優先に軍配が上がるでしょう.ベクトルをワープロ文章で書く際にも,いちいち転置の記号を書かずに済みます.プログラミング的に色々と都合がよいのは行優先表現のようです.
列優先,行優先と右手系,左手系
Tipsになりますが,混同されることもあるようなので言及しておきます.
行・列優先と右手・左手系は別の概念です.それぞれ別にどちらであったかを常に考慮する必要があります.
右手系,左手系とは,座標軸の決め方の概念です.
三次元空間における右手系,左手系とは,X軸を親指,Y軸を人差し指,Z軸を中指として,それぞれの指を垂直にしたときに,その座標系が右手になる系か,あるいは左手になる系か,ということです.
グッドのハンドサインをして,X→Y軸への回転方向を人差し指~小指にしてみた時に,親指がZ軸方向になる手,としても確認できます.
wikipediaの右手系のページの図がわかりやすいです.
右手方向がX軸,頭上方向がY軸…などは,さらに別の話です.OpenGLと同様の座標系の取り方をして特に「右手系」というケースがあるみたいですが,右手系・左手系とは上の話題です.単に右手系と言っただけでは座標系の取り方は確定しません.同じ右手系同士でも,回転や並進などが発生している場合がありますので,よく確認しましょう.
列優先,行優先と相互変換
幾何計算系のライブラリを移行or統合する場合は,以上の3点においての整合性を維持する必要があります.
データ順,数学的表現のそれぞれにおいて相反がある場合,その都度転置を行ってから,データをコピーします.
例えば,行優先データ順・列優先表現(OpenCV)の行列のデータを,列優先データ順・列優先表現(OpenGL)の行列のデータ領域にコピーする場合,転置が必要になります.一方で,行優先データ順・行優先表現(Direct3D)の行列のデータを,列優先データ順・列優先表現(OpenGL)の行列のデータ領域にコピーする場合,転置は不要です(2度転置を行うので,戻る).
また,右手系・左手系の変換は適切に軸フリップが必要になるでしょう.
混乱しやすいので,重々注意し,初期の段階からしっかり設計するように意識するとよいと思われます.
おわりに
本記事では,3つの混同する可能性のある概念について,分けて考える必要があること,変換にあたっての考え方について述べました.
混乱をきたしていた3DGC,幾何計算の初学者の悩みが解消することを期待します.
ところで,計算速度的な比較は,アーキテクチャがどの方式に対して最適化しているかによるので,汎用的な意味での優劣はつかないと思われます...
MicrosoftとKhronosがけんかしているのがいけないきがする.
※対応ライブラリの情報募集中です.
Tips
- PyGLM <-> numpy.array
データ順: ColMajor / RowMajor
数式表現: RowMajor / RowMajor
なので,変換時は転置が必要そうに思われますが,PyGLMがデータ順の差を埋めてくれています.
したがって,下記のように転置を挟まずに相互変換可能です.np.asarray(glm.mat3([[1,4,7],[2,5,8],[3,6,9]])) glm.mat3(np.array([[1,2,3],[4,5,6],[7,8,9]],dtype=float32))