目次
OpenGL で Direct3D 座標系を使う (またはその逆)
GPU が Shader によりプログラマブルになったことで、 レンダリングに関する多くのルールをプログラマが決められるようになりました。
ライティングの演算とマテリアルのバリエーションから、アニメーションや変形といったジオメトリまで、 ShaderModel が進むにつれて固定機能の割合が減少しています。 特に最新の ComputeShader では Rendering Pipeline からも開放されて、 使い方は完全にプログラマに委ねられています。
従来は上位 API で定義されていた座標系も、Shader 世代の GPU ではあまりい意味を持っていません。 OpenGL で Direct3D 座標系を使うこともできますし、その逆も可能です。
昔から Windows を使用しており、Direct3D のコードやデータを溜め込んでいる場合は D3D 座標系を使いたいかもしれません。 逆に DCC ツールとの互換性や、Mobile Device での動作を考えると OpenGL に合わせた方が何かと都合が良くなります。
- 座標系を自由に決められるメリット
- API に依存しないで幅広いプラットフォームへの移植性を高めることができる
- 開発したライブラリを共有することができる
- データの互換性、リソース資産の蓄積
- DCC ツールの座標系に合わせて開発できる (GL座標系が多いし Z-Up のものもある)
- 必要なことと問題点
- 数値演算系のライブラリやシェーダーコードは独自に用意する必要がある。
- サンプルのコードはそのまま流用できない。
- Clip 座標系と Screen 座標系の扱いは完全に置き換えられない。
GPU ハードウエア的には両 API に対応するのが当たり前なので、一部の GPU を除いてどちらでも問題ありません。
OpenGL と Direct3D の座標系の違い
Direct3D | OpenGL | 座標系の変更 | ||
---|---|---|---|---|
3D 座標系 | LH 左手系 | RH 右手系 | 可能 | Cライブラリと Shader が自由に決定可能 |
Clip (device) 座標系 | Z/w: 0.0 ~ 1.0 | Z/w: -1.0 ~ 1.0 | API 依存あり | Driver 内部に組み込まれているため Shader で変換が必要 |
Screen 座標系 | 左上原点 | 左下原点 | API 依存あり | API で決められているため、呼び出し時に変換が必要。Viewport/Scissor 等 |
UV 座標系 | 左上原点 | 左下原点 | 可能 | 自由に決定可能。データは上下が反転しているが API 上は違いがない。 |
Tangent 座標系 | 未定義(右手系) | 未定義(右手系) | 可能 | テクスチャデータと Shader で好きな様に定義できるが、RH のデータが多い |
3D 座標系
D3D (LH) と OpenGL (RH) では、Y-Up+, X-Right+ のとき、Z 軸の向きが反対になります。
- OpenGL (RH) : 奥に向かって Z値が小さくなる (カメラが原点 0 なら遠くは負の座標を持つ)
- Direct3D (LH) : 奥に向かって Z値が大きくなる (カメラが原点 0 なら遠くは正の座標を持つ)
Maya などの DCC ツールは OpenGL で作られているため、OpenGL と同じ RH 座標系が用いられており馴染みがあります。
ハードウエア的には最終的に depth buffer には 0~1.0 の値が格納されるため、符号の向きは D3D の LH 座標系に一致します。 OpenGL は Rendering Pipeline の内部で Z の符号反転が行われていますが、 Shader によって直接 GPU リソースを読み書きできるようになったために、場合に応じてこの両者を使い分ける必要があります。
例えば Shadow Map を生成する場合は OpenGL の RH 座標系の Projection Matrix を用いますが、 Shadow Map を Sampling する場合は、Shadow Matrix で Z の符号を反転して Z 範囲を -1.0~1.0 から D3D と同じ 0~1.0 に変更しなければなりません。 Direct3D LH 座標系では Mapping 生成時と Sampling 時の Matrix は、uv 範囲を変更するだけで済みます。 とはいえ Matrix に畳み込めるため正直どちらでも構わないのが実情です。
基本的には Z を変更するだけですが、 Projection Matrix の生成や Frustum の Culling で違いが生じます。 特に Frustum と AABB の位置関係は注意。
座標だけでなく Animation の回転も Z 成分が逆になります。
Clip (Device) 座標系
x/w | y/w | z/w | |
---|---|---|---|
OpenGL | -1.0 ~ 1.0 | -1.0 ~ 1.0 | -1.0 ~ 1.0 |
Direct3D | -1.0 ~ 1.0 | -1.0 ~ 1.0 | 0.0 ~ 1.0 |
Vertex Shader の出力が Clip 座標系になります。 上の表は w 除算後のもので、実際は -w~w を出力します。
OpenGL は x/w, y/w, z/w が対称になるため、LSPSM のような Projection 方向の入れ替えがわかりやすくなります。 Direct3D はよりハードウエアに近い値を取ります。
z の範囲が異なるため、Direct3D と OpenGL では Projection Matrix に互換性がありません。 プラットフォーム依存を回避するためには、Projection Matrix を事前に変換するか、または Shader 内で座標調整が必要です。
Projection Matrix を変換する場合は下記のようなります。
struct Matrix44 { float _11, _12, _13, _14; float _21, _22, _23, _24; float _31, _32, _33, _34; float _41, _42, _43, _44; }; // Direct3D Projection Matrix to OpenGL void D3DtoGLProjection( Matrix44& mat ) { _13= 2.0f * _13 - _14; _23= 2.0f * _23 - _24; _33= 2.0f * _33 - _34; _34= 2.0f * _43 - _44; } // OpenGL Projection Matrix to Direct3D void GLtoD3DProjection( Matrix44& mat ) { _13= (_13 + _14) * 0.5f; _23= (_23 + _24) * 0.5f; _33= (_33 + _34) * 0.5f; _43= (_43 + _44) * 0.5f; }
↑同様の変換は、OpenGL で Shadow map を用いる場合にも行っていることになります。 Depth Map sampling 時は Z 範囲が 0~1.0 になるため。
Shader 側で調整する場合は、Projection Matrix の相互変換は不要です。
Shader で Clip 座標系の違いを吸収する場合は下記の通り。 OpenGL の GLSL で Direct3D 用の Projection Matrix を利用した場合の例です。
// GLSL uniform mat4 PView; in vec3 POSITION; main() { vec4 opos= vec4( POSITION.xyz, 1.0 ) * PView; opos.z= 2.0 * opos.z - opos.w; // ← 追加コード gl_Position= opos; }
UV 座標系
API 上は制限がなく、あくまでデータがどちらのルールで作られているか次第になります。
D3D と OpenGL では原点位置が異なりますが、外部で作られたデータを用いる場合はプログラム的には特に手を加える必要はありません。
- D3D : v 原点を上とみなす。メモリには 上から下に向かって画像が格納されているとみなす。
- GL : v 原点を下とみなす。メモリには 下から上に向かって画像が格納されているとみなす。
つまり D3D も GL も、v 座標が 0 の場合は Texture Memory の先頭からデータを読み込むことになるからです。 D3D と GL は uv 座標の v だけでなく Texture Image の画像自体も上下反転しています。
ただし、動的に Rendering して生成した Texture の場合は GL と D3D は上下反転しているので要注意です。 次の Screen 座標系とも関係しますが、Viewport や Scissor 、Framebuffer は左下原点とみなしてレンダリングが行われるため。
Screen 座標系
OpenGL の Framebuffer (Renderbuffer) は左下原点とみなします。 関連する API 郡、例えば Viewport や Scissor 範囲の設定も左下原点です。 固定機能なので自由に座標系を決めることができません。
また Texture へのレンダリングを行った場合も左下原点とみなすため、 生成されたイメージは Direct3D とは上下逆の画像になります。
Tangent 座標系 (Normal Map 座標系)
Normal Map の座標系は、生成に使用したツールに依存する場合がほとんどです。 多くの DCC ツールが OpenGL を用いているため、 ObjectSpace の Normal Map であっても右手座標系 (RH) が用いられている場合が多くなります。
よって Direct3D でも Normal Map だけ RH が用いられることが少なくありません。 符号反転は Shader 内で低コストに実現できるため、Normal Map の座標系の違いはあまり問題になりませんでした。
また Normal Map はデータ圧縮のため、地形データなどは Tangent Space でリピートやミラーを併用する場合があります。 そもそもこの場合の座標系は Polygon Face 毎に異なっており一定ではありません。
Matrix (Normal, Binormal, Tangent) で表現する場合は座標系の自由度が高いこと、 API と関係なく Shader 内部の事情にすぎないことから、特に問題なく相互利用が可能となります。 なお、Quaternion が用いられている場合は軸(座標系)が固定になるので要注意。