Saturday, February 6, 2016

DirectXとOpenGLとrow majorとcolumn majorのまとめ

DirectXの行列は、移動が4行目に来ます。
   

対して、OpenGLはちょうどDirectXの行列を転置したもので、移動が4列目に来ます。
 

ところが、行列をfloat型16個の配列として見てみると、両者のメモリレイアウトは全く同じです。





例えばXMMatrixLookAtRHで生成したビュー行列をgluLookAtと置き換える正常に動作します。OpenGLはcolumn major(列優先)で、DirectXはrow major(行優先)なので、数学的に1回、メモリレイアウトで1回、2回転置してメモリレイアウトが同じになったと見ることもできます。

では両者は同じかと言うと、数学的な定義とメモリレイアウトは別の話なので注意が必要です。頂点v、ワールド(モデル)行列W、ビュー行列V、射影行列Pの計算式はOpenGLとDirectXで逆の並びになっています。

DirectX:

OpenGL:


この並びはシェーダーを書く時の慣習にもなっていて、多くの参考書はこの順で書かれているはずです。両者のメモリレイアウトは同じはずなので、掛け算の順番が違うということは、GLSLはcolumn major、HLSLはrow majorの並びと推測できます。

ところが、GLSLもHLSLもcolumn majorがデフォルトになっています。OpenGLはC/C++側もGLSLもcolumn majorなので良いのですが、DirectXはC/C++側がrow major、HLSLがcolumn majorと異なっています。

しかし、これはおかしいです。row majorではないと、

として正しい結果が得られないはずだからです。どうも、DirectXはシェーダに行列を渡す時点で転置させる習慣があったようで、結果としてこの順で掛け算して正しくなっていました。おそらく、DirectX9時代はD3DXあたりで勝手にやってくれたのでプログラマが気にする必要が無かったのだと思います。

しかし、DirectX11はConstant Bufferの配置までプログラマに委ねられているので、理解して使う必要があります。実は、DirectX11ではOpenGLと同じように、

の順で掛けると正しい結果になります。あるいはHLSLで行列を宣言するときにrow_majorを指定すると、

この従来の順で計算しても正しくなります。