座標系の考え方と生成・配置

ここでは、動きのある3D表現を扱う上で欠かせない「座標系」の考え方と、 プログラム内での生成・配置方法について解説します。 「座標系」と言うといかにも数学的なイメージで難しく感じるかもしれませんが、 VCSSL Graphics3Dでは座標系を直感的かつ簡単に扱えるように工夫されています。

- 目次 -

座標系とは

座標系と位置ベクトル

これまで、立体の移動やポリゴンの頂点位置指定などにおいて、 空間中の一点の位置を指定するのに、 ( X, Y, Z )の3つの数値の組を使ってきました。 このような数値の組を位置ベクトルと呼びます。 3つの数値はそれぞれX成分、Y成分、Z成分と呼びます。

実は位置ベクトルだけでは、空間中の位置を指定するのには不十分です。 というのも、位置ベクトルは、「座標系」という基準があって、 はじめて意味を持つからです。

例えば位置ベクトル( 1, 2, 3 )の指し示す位置は、 原点つまり( 0, 0, 0 )に対応する位置から、 X軸方向へ1移動し、Y軸方向へ2移動し、そしてZ軸方向へ3移動した位置です。 このように、空間中の位置を指定するには、 位置ベクトルのX,Y,Z成分の他に、基準として「原点の位置」と 「X,Y,Z軸の方向」が必要になります。 これら2つの基準要素をまとめて、「座標系」と呼びます。

座標系によるベクトルの成分表示図
位置ベクトル( 1, 2, 3 )の指し示す位置
原点からX軸方向へ1移動し、Y軸方向へ2移動し、そしてZ軸方向へ3移動した位置が(1, 2, 3 )。

位置ベクトルの成分は座標系によって異なる

空間中のある位置を指し示す位置ベクトルは、 座標系が変わると、成分の値も変わります。

例として、ある座標系から見て、 位置ベクトル( 1, 2, 3 )が指し示す位置を考えてみましょう。 この位置を別の座標系から見ると、 その位置ベクトルは、下図のように( 0, 5, 3 )となるかもしれません。

このような不一致が起こる理由は2つあります。 第一に、両者は原点の位置が違います。 つまり両者にとって ( 0, 0, 0 ) が指し示す位置がすでに異なるのです。 そして第二に、両者のX,Y,Z各軸が向いている方向も異なりますから、 仮に原点の位置が同じであったとしても、 位置ベクトルのX,Y,Z成分は全く異なる値になってしまいます。

異なる座標系から見たベクトルの成分表示図
同じ位置を、別の座標系から見る
同じ位置でも、別の座標系から見ると、位置ベクトルの成分が異なる。

このような不一致が起こる理由は2つあります。 第一に、両者は原点の位置が違います。 つまり両者にとって ( 0, 0, 0 ) が指し示す位置がすでに異なるのです。 そして第二に、両者のX,Y,Z各軸が向いている方向も異なりますから、 仮に原点の位置が同じであったとしても、 位置ベクトルのX,Y,Z成分は全く異なる値になってしまいます。

立体を動かす仕組み

座標系は、立体を動的に動かしたい場合に使用します。

例えば、まず適当な座標系を用意し、 立体をその座標系の ( 1, 2, 3 ) に配置します。 この立体を動かしたい場合、座標系のほうを動かします。 すると、座標系に配置された立体も一緒に動いて見えます。 なぜなら座標系が動くと、 その座標系を基準にした ( 1, 2, 3 ) が示す場所も動くからです。

座標系による移動の概念図
座標系の移動
座標系が動くと、一緒に位置ベクトルの指し示す位置も動く。

同様に、立体を回転させたい場合は、座標系を回転させます。 すると座標系のX,Y,Z各軸の位置関係が回転しますから、 ( 1, 2, 3 ) の示す場所も回転し、結果として立体も一緒に回転します。

座標系による回転の概念図
座標系の回転
座標系が回転すると、一緒に位置ベクトルの指し示す位置も回転する。

これらの操作は、要するに、立体を動かすにあたって 「 立体の位置情報を書き換える代わりに、 位置の基準(=座標系)を動かした 」 という事です。

座標系を動かさずに、立体の位置である( 1, 2, 3 ) の成分そのものを書き換えても、移動や回転は可能です。 実際にこれまでに用いてきた moveModel / movePolygon関数や、rotModel / rotPolygon関数は、 内部でそのような処理を行っています。

しかし、複数のモデルを一緒に動かしたい場合などには、 座標系を動かすほうが遥かに簡単に処理を行えます。 例えば「自動車」を動かしたい場合には、 それを構成するタイヤモデルやボディモデル、 運転手モデルなどを一つずつ動かすのはとても面倒です。 そこで適当な座標系を用意し、 自動車の部品を全てそこに配置してやれば、 あとは座標系だけを動かせば済みます。

座標系の種類

座標系には、大別して以下の3つの種類があります。

ビュー座標系 / スクリーン座標系

ビュー座標系は、パソコンの画面に張り付いた形で存在する、特別な座標系です。 一般には、画面右方向にX軸、画面上方向にY軸、 画面手前方向にZ軸が取られます(画面奥にZ軸を取るものも存在します)。 3D仮想空間を動かしても、この向きは常に変わりません。

ビュー座標系の概念図
ビュー座標系
パソコン画面に張り付いた位置に存在する座標系。画面右がX軸、画面上がY軸、画面手前がZ軸。

つまり、ビュー座標系のX-Y平面はパソコンの画面に一致しており、 ゆえに現実世界と仮想世界を繋ぐ座標系と言えるでしょう。 また、3D仮想空間の視点(ビュー、カメラ)に張り付いた座標系と言う事もできます。 ビュー座標系は、カメラ座標系と呼ばれる事もあります。

パソコン画面に描画される3Dコンピュータグラフィックス映像は、 3D仮想空間世界の全ての立体を、ビュー座標系のX-Y平面に、 (遠近感を付けた上で)投射したものと言えます。 この、投射されて2次元になったX-Y平面の事を、 スクリーン座標系と呼ぶ事があります。

ワールド座標系

ビュー座標系に加えて、もう一つ特別な座標系が存在します。 それがワールド座標系です。ワールド座標系は、 ビュー座標系上に、1個だけ配置されています (逆に、ワールド座標系上にビュー座標系が配置されていると見る場合もあります)。

そして、立体モデルは通常、ビュー座標系ではなくワールド座標系上に配置されます。 このようにワールド座標系を介する事で、 視点を変更する際の処理が、ビュー座標系とワールド座標系の位置関係を動かすだけで済み、 非常に簡単になります。

ワールド座標系の概念図
ワールド座標系
通常、立体はビュー座標系に直接配置されるのではなく、ワールド座標系に配置される。

加えて、3D仮想空間上において視点に左右されない絶対的な座標が 定義できるという利点もあります。 例えば3Dシューティングゲームなど、 広大な舞台において複雑に運動する物体を多数制御しなければならないような場合など、 地面に固定された絶対的な座標系を基準として処理を記述するのが好ましい事がよくあります。 このような場合に、ワールド座標系は良い基準として機能します。

ローカル座標系

さて、ビュー座標系でもワールド座標系でも無い、 プログラマが自由な用途で使用する座標系は、一般にローカル座標系と呼ばれます。 ローカル座標系は必須では無いので、 プログラマが必要に応じて必要な数だけ宣言し、用意します。

例えば 「 動く街 」 を表現したいなら、 まず野原や山などの動かない立体はワールド座標系上に配置し、 電車やバス・飛行機などの動く立体にはローカル座標系をそれぞれに用意して、 その上に配置します。

ローカル座標系の概念図
ローカル座標系
ビュー座標系やワールド座標系とは異なり、ローカル座標系はプログラマが自由に用意して使用する。

座標系の生成

一般的な座標系(ローカル座標系)

一般的な座標系(ローカル座標系)を生成するには、 newCoordinate関数を使用します。

- 関数仕様 -

int newCoordinate ( )

この関数は座標系を生成し、 その座標系に固有のIDを割り振って返します。

特別な座標系(ワールド座標系、ビュー座標系)

ワールド座標系やビュー座標系などの特別な座標系は、 レンダラー側で自動的に生成され、確保されています。 これらの座標系にもやはりIDが割り振られており、 getWorldCoordinate関数やgetViewCoordinate関数でそのIDを取得する事ができます。

- 関数仕様 -

int getWorldCoordinate ( int rendererID )
int getViewCoordinate ( int rendererID )

引数rendererIDには、レンダラーのIDを指定します。 関数が2つありますが、上の関数はワールド座標系のIDを、 下の関数はビュー座標系のIDを返します。

座標系の配置

座標系は、他の座標系の上に配置して使用します。 この配置先となる座標系を親座標系と呼びます。

座標系は何階層でも多重配置が可能ですが、 座標系の親をたどっていくと必ずビュー座標系または ワールド座標系まで繋がっていなければなりません。

座標系を他の座標系の上に配置するには、 mountCoordinate関数を使用します。 特にワールド座標系の上に配置する場合には、 以下のように引数を指定します。

- 関数仕様 -

int mountCoordinate ( int childID, int rendererID )

引数childIDには、ワールド座標系上に配置したい座標系(子座標系)のIDを指定します。 続く引数rendererIDには、レンダラーのIDを指定します。

座標系を、ワールド以外の座標系に配置するには、 もう一つ引数を追加し、配置先座標系(親座標系)のIDを指定します。

- 関数仕様 -

int mountCoordinate ( int childID, int rendererID, int parentID )

引数childIDに配置したい座標系(子座標系)のIDを、 parentIDに配置先の座標系(親座標系)のIDを指定します。 つまりchildIDの座標系を、parentIDの座標系の上に配置します。 真ん中の引数rendererIDには、レンダラーのIDを指定します。

プログラム例

実際にローカル座標系を生成し、ワールド座標系の上に配置してみましょう。 区別しやすくするために、ローカル座標系には小さめの座標軸モデルを、 ワールド座標系には大きめの座標軸モデルを配置します。 以下のように記述し、実行してみてください。

Sample.vcssl

このプログラムを実行すると、黒い画面に座標軸モデルが表示されます。 座標軸モデルは大きいものと小さいものが重なっており、 大きいものがワールド座標系の上に、 小さいものがローカル座標系の上に配置されています。

実行結果、2つ重なった座標軸モデルの図
実行結果
大きな座標軸と、小さな座標軸が重なって表示される。

この状態で、ローカル座標系を移動させたり回転させたりすると、その上に配置された、小さい方の座標軸モデルの位置や向きも一緒に変化します。 座標系の移動や回転については、これから扱います。