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

ここでは、動きのある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成分は全く異なる値になってしまいます。

立体を動かす仕組み

座標系は、立体を動的に動かしたい場合などに特に便利です。

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

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

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

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

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

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

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

座標系の種類

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

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

ビュー座標系は、「3D仮想空間の視点(ビュー、カメラ)に、常に張り付いている、特別な座標系」です。カメラ座標系と呼ばれる事もあります。

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

ところでビュー座標系は、現実世界側に居る我々にとっては、「パソコンの画面に、常に張り付いている座標系」と見なす事もできます。言うなれば、現実世界と仮想世界とを繋ぐ座標系とも言えるでしょう。一般には、画面右方向にX軸、画面上方向にY軸、 画面手前方向にZ軸が取られます(画面奥にZ軸を取るものも存在します)。 3D仮想空間を動かしても、この向きは常に変わりません。

ビュー座標系の概念図
ビュー座標系
視点に常に張り付いている座標系。別の見方では、パソコン画面に張り付いている座標系とも見れる。画面右がX軸、画面上がY軸、画面手前がZ軸。

ワールド座標系

ビュー座標系に加えて、もう一つ特別な座標系が存在します。 それがワールド座標系です。 ワールド座標系はその名の通り、3Dの世界(ワールド)の土台となる座標系です。

単に立体を3D描画したいだけなら、ビュー座標系上に直接、立体を配置するだけでも可能です。しかし、例えば立体が大量にあって、視点を操作したい場合の事を考えてみましょう。立体がビュー座標系上に直接配置されていると、「視点操作において、それぞれの立体の位置関係や角度がどう変わるべきか」というのを自力で計算し、位置や角度を変えなければなりません。これは難しいですね。

そこで、ビュー座標系からもう一枚、新しい座標系 = ワールド座標系を介して、立体をその上に配置してやるとどうでしょう。こうすると、視点を変更する際の処理が、ビュー座標系とワールド座標系の位置関係を動かすだけで済み、 非常に簡単になります。

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

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

ローカル座標系

さて、ビュー座標系でもワールド座標系でもない、 プログラマが自由な用途で使用する座標系は、一般にローカル座標系と呼ばれます。用途の典型例としては、立体を動的に移動させたり、「立体の群れ」をひとまとめにして動かしたり、等が挙げられます。 ローカル座標系は必須では無いので、 プログラマが必要に応じて必要な数だけ宣言し、用意します。

例えば、「街」のような3D仮想空間をイメージしてみましょう。山などの明らかに動かない立体は、ワールド座標系上に直接配置しても支障ありません。一方で電車やバス・飛行機などの「動く立体」は、ローカル座標系をそれぞれに用意して、 その上に配置した方が便利です。

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

座標系の生成

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

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

- 関数の形式 -

int newCoordinate ( )

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

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

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

- 関数の形式 -

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

引数rendererIDには、レンダラーのIDを指定します。

関数が2つありますが、上の関数はワールド座標系のIDを、 下の関数はビュー座標系のIDを返します。

座標系の配置

VCSSLにおける座標系は、他の座標系の上に配置して使用します。 この配置先となる座標系を「親座標系」と呼びます。逆に、親座標系に配置されている座標系の事は「子座標系」と呼びます。

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

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

- 関数の形式 -

int mountCoordinate ( int childID, int rendererID )

引数は以下の通りです:

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

- 関数の形式 -

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

引数は以下の通りです:

プログラム例

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


import graphics3d.Graphics3DFramework;
import Graphics3D;

// プログラムの最初に呼び出される関数
void onStart ( int rendererID ) {

	// 画面サイズや背景色の設定(省略可能)
	setWindowSize( 800, 600 );
	setBackgroundColor( 0, 0, 0, 255 );


	// ローカル座標系を生成
	int coord = newCoordinate( );

	// ローカル座標系をワールド座標系に配置
	mountCoordinate( coord, rendererID );

	// ローカル座標系上に座標軸モデルを配置
	int axis1 = newAxisModel( 1.5, 1.5, 1.5 );
	mountModel( axis1, rendererID, coord );

	// ワールド座標系上に座標軸モデルを配置
	int axis2 = newAxisModel( 3.0, 3.0, 3.0 );
	mountModel( axis2, rendererID );
}
Sample.vcssl

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

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

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