RSS2.0

LWJGL の座標系についてまとめてみる

今回は、三次元のモデルの座標が PC のウインドウ上の座標に変換される手順について触れながら、LWJGL(OpenGL)で扱ういくつかの座標系について、そして OpenGL 系の記事では必ず出てくるモデルビュー変換・射影変換とは何だったのかについてより知っていただければと思います。

今まで扱ってきたプログラムの裏でどのようなことが行われているのかを掘り下げたものなので、サンプルプログラムはありません。べ、別に楽しようとかじゃなi(以下略

OpenGL や Direct3D では、三次元でモデリングしたモデルを PC のアプリケーションのウインドウに配置する際に、何回か座標系を変換する処理が行われています。
突き詰めてしまえば、モデルは点、点同士を結ぶ線、線同士を結ぶ面で構成されていますが、そのひとつひとつの点がどこにあり、それをどの角度・距離から見たものなのか等を考慮して、アプリケーションウインドウ内での表示位置を決めなければなりません。ある点の座標をウインドウ内での座標に変換するために、以下のような段階を追って座標系を変換していきます。

1. オブジェクト座標系(Object Space)

ポリゴンを貼り付けていってひとつの 3D モデルをモデリングする際に使用する座標系です。
objectSpace.png
立方体をモデリングする際に、例えば原点を立方体の中心に置いた座標系で頂点座標の指定を行ったとすると(実際には原点をどこに置いてもかまわないです)、この時の座標系がこの立方体のオブジェクト座標系となります。

2. ワールド座標系(World Space)

すべてのオブジェクト座標系を包括した座標系です。個々のモデルがどこに配置されるかを示す際に基準となる座標系で、モデル同士の位置関係を表す役割を持ちます。
worldSpace.png
例えば、モデル A の中心座標 OA がモデル B の中心座標 OB より x 軸の正の方向に 3 大きい時、OB のワールド座標系での座標を (1, 1, 1) とすると、OA のワールド座標系での座標は (4, 1, 1) となります。

個々のオブジェクト座標系をワールド座標系に変換する作業は、モデリング変換(Modeling Transform)と呼ばれています。

3. 視点座標系(Eye Space)

カメラの位置が原点となる座標系です。カメラから見てワールド座標系内のすべてのモデルが、どのように配置されているかを示します。
eyeSpace.png
視点座標系ではカメラの位置のほかに、カメラの視線を z 軸の負の方向、カメラの視野での上方向を y 軸の正の方向として定義しています。

ワールド座標系を視点座標系に変換する作業は、ビュー変換(View Transform)と呼ばれています。

モデルビュー変換

ここまでの、オブジェクト座標系を視点座標系まで変換するにはモデリング変換とビュー変換の2回の変換作業が行われますが、OpenGL ではこれらをまとめてモデルビュー変換と呼ばれています。オブジェクト座標系、ワールド座標系、視点座標系は、究極的には原点をどこに置いた座標なのかという違いによるものなので、ひとくくりにされているのです。
また、モデルビュー変換で使われるこれら3つの座標系では、x 軸の右方向、y 軸の上方向、z 軸の手前方向が正となります。

モデルビュー変換では演算の記憶領域となる行列スタックも同じものが使われていて、これはモデルビュー行列(ModelView Matrix)と呼ばれています。
LWJGL では以下のコードでモデルビュー変換を開始できますが、これはこれ以降で扱う行列スタックをモデルビュー行列に切り替えることを意味します。
glMatrixMode(GL_MODELVIEW);
モデルビュー行列はモデルビュー変換(モデリング変換、ビュー変換)で使われる行列スタックなので、オブジェクト座標系、ワールド座標系、視点座標系に対する処理が行えることになります。頂点座標を指定する glVertex3f()、頂点を並行移動する glTranslatef()、頂点を拡大・縮小する glScalef()、頂点を回転 glRotatef() などですね。

カメラを付け加える視点座標系については少し意味合いが異なってくるようにも見えますが、実際にはカメラとモデルの位置関係を、モデル基準で捉えるか、カメラ基準でとらえるかという違いなので、同じくモデルビュー行列に対する処理で実現することができます。実際に、(これまでのサンプルプログラムではまだ扱ってはいないのですが、)カメラの位置や視点、視野の上方向を設定する gluLookAt() メソッドは、内部的にはモデルビュー行列に対する演算や glTranslatef() メソッドで実装されています。

4. クリップ座標系(Clip Space)

乱暴に言ってしまうと、視点座標系に対して遠近法を適用した座標系です。視体積の定義で遠近法を適用する(透視射影)か、適用しない(正射影)かを設定することができますが、この遠近法によって座標の位置を補正する際に使われる座標系がクリップ座標系です。
視点座標系をクリップ座標系に変換する作業は、射影変換(Projection Transform)と呼ばれています。

射影変換

LWJGL(OpenGL)では、射影変換はモデルビュー変換とは独立して扱われています。射影変換では射影行列(Projection Matrix)と呼ばれる行列スタックに対して処理を行わなければなりません。LWJGL では以下のコードで射影行列へ切り替えることができます。
glMatrixMode(GL_PROJECTION);
既に立方体の回転の回でも触れていますが、射影変換中とモデルビュー変換中とで glLoadIdentity() メソッドを呼び出した際に初期化される内容が異なってくるのは、両者の行列スタックが独立しているためです。射影変換では射影行列を扱っているので、現在の行列スタックを初期化する glLoadIdentity() の効果はモデルビュー行列にまでは及びません。

もう一点、クリップ座標系では z 軸の方向が反転される(z 値に -1 がかけられる)ことに注意してください。
glOrtho(0, width, 0, height, 0, 100);
このようなコードで、正射影での設定を行う glOrtho() の引数 near に 0 を、far に 100 を指定した場合、モデルビュー変換での座標の有効範囲は near から数えて far - near なので、有効座標は 0 ~ -99 となります。

zNear < zFar を成立させた上で有効座標を 0 ~ 99 とするには、以下のような設定を行う必要があります。
glOrtho(0, width, 0, height, -99, 1);

5. 正規デバイス座標系(Normalized Device Space)

ここからは出来上がったモデルの座標をアプリケーションのウインドウ内の座標に変換していく作業になります。
クリップ座標系での座標を正規化し、値の範囲が(-1, -1, -1)~(1, 1, 1)の範囲に収まるように変換するのですが、この時の使われる座標系が正規デバイス座標系です。
アプリケーションウインドウの座標は OpenGL か DirectX かによって変わってくるため、一度正規化した上で最終的な環境に適用されることになります。
OpenGL では、この部分はハードウェアに実装されているらしく、特にコードを書く必要はありません。

クリップ座標系を正規デバイス座標に変換する作業は、Perspective Divide(訳語不明)と呼ばれています。

6. ウィンドウ座標系(Window Space)

実際のアプリケーションウインドウ内での座標を表す座標系です。OpenGL では画面左下が原点となります。
この分野はまだ触れたことはありませんが、画面分割などで利用されるので、いずれ改めて触れたいと思います。

正規デバイス座標系をウインドウ座標系に変換する作業は、ビューポート変換(Viewport Transform)と呼ばれています。

おわり

モデルビュー変換と射影変換の区切りがいまいちよくわからなかったのですが、モデルの座標をウインドウの座標に変換していく様子を順々に見ていくと、だいぶ見えてくるようになると思います。OpenGL の特徴である行列スタックが独立していることなどは、それ自体は OpenGL などの実装によるものなので、それとして受け入れるしかないのですが、実際の処理の様子や背景部分も知ると受け入れやすくなると思います。

私自身、LWJGL を始めた当初にいきなりモデルビュー変換とか射影変換なんかが出てきた時には右往左往していました。私は論よりコードな実践派なので、しばらく使ってみた今ならば当時理解できてなかったことなども見えてきて、振り返るいい機会になったかなーと感じています。
  LWJGL  コメント (2)  2012/05/14 12:08:00


公開範囲:
2014/02/13 17:10:59   ゲストさん (220.110.27.49) 公開範囲: すべて, 承認済み
理解しやすかったです。
2014/02/14 00:31:31   momokan 公開範囲: すべて, 承認済み
ありがとうございます!!
プロフィール HN: ももかん
ゲーム作ったり雑談書いたり・・・していた時期が私にもありました。
カレンダー
<<2019, 5>>
2829301234
567891011
12131415161718
19202122232425
2627282930311