RSS2.0

LWJGL で Hello world してみる(ポリゴン表示編)

Java ゲームプログラミングのはじめの一歩ということで、前回は LWJGL + Eclipse、maven の環境構築と四角形のポリゴン表示をしてみました。今回は前回サンプルとして載せたソースコードについて解説していこうと思います。

プログラムの骨組み

処理の基本的な流れとしては、

 1. ウインドウの生成
 2. ループしながらレンダリング
 3. 閉じるが押されたらウインドウを破棄

となります。
それでは順番に見ていきましょー。

ウインドウを生成する

これは LWJGL でのウインドウの作り方になります。
あ、ソースコードは前回のものをそのまま使ってますが、コメントは説明用に一部書き換えています。
try {
	// ウインドウの大きさを設定する
	Display.setDisplayMode(new DisplayMode(width, height));
	// ウインドウのタイトルバーの文字列を設定する
	Display.setTitle("hello world!");
	// ウインドウを生成する
	Display.create();
} catch(LWJGLException e) {
	// きちんと例外処理しようね
	e.printStackTrace();
	return;
}
Display クラスに設定用メソッドがあるので必要なものを設定していき、最後に Display.create() を呼び出して実際にウインドウを生成します。
LWJGL のダイナミックリンクライブラリがロードされるのは、Display.create() 内です。なので java.library.path が間違っていたりすると、このタイミングで例外が発生します。
例外処理として、return してメインメソッドから抜けるようにしています。このプログラムではウインドウ生成がメインメソッドの中で行われているので、ここで失敗したらもう何もすることはないって感じです。

OpenGL の初期設定をする

続いて OpenGL の初期設定をします。
//	ポリゴンの片面(表 or 裏)表示を有効にする
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
 
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height, 0, depth);
glMatrixMode(GL_MODELVIEW);
呼び出してる glEnable() や glOrtho() は OpenGL の API です。LWJGL は OpenGL の薄い Java 用ラッパーなので、API の名称や引数として渡す定数なんかはほぼ同じです。OpenGL のチュートリアルを見ながら LWJGL で実装する、なんてことが簡単にできます。

LWJGL ではこれらの API が org.lwjgl.opengl.GL11 クラスの static メソッド、static 変数として提供されています。冒頭で static import してるやつがそうですね。
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glOrtho;
import static org.lwjgl.opengl.GL11.glMatrixMode;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
...
GL11 の 11 は OpenGL のバージョンに対応しています。もっと新しいバージョンの OpenGL で追加された API は GL41 クラスとかにあります。

OpenGL では機能の有効化 / 無効化に glEnable() / glDisable() 関数を使います。GL_CULL_FACE という定数はポリゴンの片面表示機能で、これを有効化するために glEnable() の引数として GL_CULL_FACE を渡している、という感じです。
ポリゴンの片面表示については後ほど解説します。

あとの内容は、今はこんなもんかーと思っててください。
気になった方は glMatrixMode とかでググるといろいろ出てくると思います。

メインループ

メインループはこちら。
try {
	...
	while (!Display.isCloseRequested()) {
		//	画面を初期化する
		glClear(GL_COLOR_BUFFER_BIT);
 
		//	描画用の関数を呼び出す
		render();
		...
	}
} catch(Exception e) {
	//	例外処理は大事です
	e.printStackTrace();
} finally {
	//	ウインドウを破棄する
	Display.destroy();
}
Display.isCloseRequested() はウインドウの閉じるボタンが押されたりすると true を返すので、それまでずっと while ループでまわす感じです。
ウインドウの破棄には Display.destroy() を使います。

メインループをまるごと try 句で囲み、finally 句でウインドウを破棄するようにしています。どんな例外が起きても必ず破棄するようにしてるわけですね。ゲームプログラミングでは画像などのリソースを大量に使うことになるので、使ったリソースの削除タイミングは常に意識する必要があります。削除しないで貯め続けると重いゲームになったりメモリ不足で落ちちゃったりしますので。

glClear() は画面の表示内容などのバッファをクリアする関数です。
引数に GL_COLOR_BUFFER_BIT を指定しているのでオフスクリーンのクリアになりますが、これについては後ほどダブルバッファリングの話でもう一度解説します。

メインループの中で render() というメソッドを呼び出していますが、これがポリゴンを描く自前の関数になります。

ポリゴンを描いてみる

さてさて、つぎは render() 関数の中身を見ていきたいと思います。ポリゴンの表示処理ですねー。

四角形のポリゴンを描く

ポリゴンは、頂点の座標を指定していくことで表示できます。
//	次に指定する4つの座標を、描く四角形の頂点として認識させる
glBegin(GL_QUADS);

glVertex3f(width - 50, height- 50, 0); //	1 つめの頂点の座標を指定する
glVertex3f(50, height - 50, 0);		//	2 つめの頂点の座標を指定する
glVertex3f(50, 50, 0);				 //	3 つめの頂点の座標を指定する
glVertex3f(width - 50, 50, 0);		 //	4 つめの頂点の座標を指定する

//	四角形の表示おしまい
glEnd();
まず glBegin() 関数に GL_QUADS 定数を渡して呼び出すことで、glEnd() を呼び出すまでに指定した座標は四角形の頂点として扱われます。GL_QUADS の場合は順番に 4 つずつ指定する必要があるので、四角形を 2 つ描きたければ 8 回座標を指定することになります。

OpenGL の座標系

座標は x, y, z の3次元で表されます。
0ce949720fadc049690bdf908dd9e3e3a6df.png
前回のサンプルプログラムは四角形1枚なので奥行きは考慮してません(z 座標に常に 0 を指定している)が、立体の表示では奥行きが必要になってくるので、3次元で説明したいと思います。

OpenGL では、原点(0, 0, 0) は画面上での左下手前になります。
座標の値が x 軸方向に増えれば右へ、y 軸方向に増えれば上へ、z 軸方向に増えれば手前へ移動していくことになります。x 軸 y 軸については、これは数学の座標系といっしょですね。
コンピューターの座標系は左上に原点があることが多いです。MS ペイントや Photoshop、HTML、Java の Swing や AWT なんかでは原点は左上なので、それらの座標系と OpenGL の座標系では y 軸方向が逆さまになります。
また、z 軸は奥にいくほど値が小さくなっていくので注意してください。

座標を指定する

座標の指定には、glVertex3f() 関数を使います。引数は先頭から、座標の x 値、y 値、z 値で、すべて float 型になります。
//	座標 (width - 50, height -50, 0) を指定する
glVertex3f(width - 50, height- 50, 0);
他にも、引数を double 型で指定できる glVertex3d()、int 型で指定できる glVertex3i() という関数もあります。
z 座標を無視するのであれば、x 座標と y 座標のみを指定する glVertex2f()、glVertex2d()、glVertex2i() 等も用意されていますので、用途にあわせて使い分けてください。

座標の有効範囲

OpenGL の初期化の時に、glOrtho() 関数で視体積を設定しました。視体積とは、表示される座標の有効範囲のことです。
表示したい座標はこの中に含まれていなければなりません。

以下が glOrtho() 関数の定義です。
  void org.lwjgl.opengl.GL11.glOrtho(double left, double right, double bottom, double top, double zNear, double zFar)
    left : 原点から左側の、x 軸方向の最小有効座標
    right : 原点から右側の、x 軸方向の最大有効座標
    bottom : 原点から下側の、y 軸方向の最小有効座標
    top : 原点から上側の、y 軸方向の最大有効座標
    zNear : z 軸上にある、視体積の最前面までの距離
    zFar : z 軸上にある、視体積の最背面までの距離

さっきの図に書き込んでみるとこんな感じ。
b46ca0ce0cfbb04fa909ede08accae59ec78.png
これらの引数には負の値を設定することも可能です。
ただ、わかりづらくなるので right < left と逆転させた設定にはしないほうがいいです。

また、zNear / zFar は少し特殊な引数で、2つほど注意点があります。
1つめは、glOrtho() で指定した zNear / zFar は、実際の座標に変換される段階で -1 をかけられ、反転することです。
2つめは、zNear / zFar は距離なので(開始地点が zNear、有効範囲の長さが zFar - zNear になる)、有効座標としては zNear ~ zFar - 1 になることです。

サンプルプログラムではこう書いているので、
//	視体積(描画する領域)を定義する
glOrtho(0, width, 0, height, 0, depth);
座標の有効範囲は x が 0 ~ width, y が 0 ~ height, z が 0 ~ -(depth - 1) となります。

もし z 軸の有効座標を 0 ~ depth としたい場合、
glOrtho(0, width, 0, height, -depth, 1);
と設定する必要があります。

glOrtho() 関数を使うと、視体積は正射影となります。正射影では、z 軸方向に移動しても大きさは変わりません。遠近法が無視されてる感じです。
他にも透視法射影というのがあって、こちらは遠くの物ほど小さく見えるように表示されます。

また、視体積が広くなるほど表示処理は重くなります。

ポリゴンの表と裏

ポリゴンには表側と裏側があり、これは頂点座標の指定順序によって決まります。OpenGL では頂点が半時計周りになっている時、ポリゴンの表を見ていることになります。頂点が時計回りの場合は裏側ですね。
5d9399b30c82204a00086b909a714c286e2b.png
ポリゴンの表裏を間違えると、意図した見え方にならなかったり、テクスチャを貼った時に鏡文字になったりしてしまいます。

ポリゴンの表示面の設定

ポリゴンの表裏を意識できるように、サンプルプログラムでは片面表示を有効にしています。
//	ポリゴンの表示面の指定を有効にする
glEnable(GL_CULL_FACE);
//	ポリゴンの表示面を表(裏を表示しない)のみに設定する
glCullFace(GL_BACK);
glEnable() 関数に GL_CULL_FACE を渡すと、ポリゴンの表示面を設定できるようになります。
続いて glCullFace() 関数でポリゴンの表示しない方の面を指定します。引数に定数 GL_FRONT を渡すと表が、GL_BACK を渡すと裏が、GL_FRONT_AND_BACK を渡すと両面が表示されなくなります。

最初わたしは glCullFace(GL_BACK) を裏のみ表示するのだと勘違いしていて、ポリゴンの頂点を反時計回りにしてるのになんで見えないんだろうとはまってました。(>_<)

デフォルトでは GL_CULL_FACE は無効化されているので両面が表示され、表裏の区別がつきづらくなります。
表示面を減らすことのメリットとして、処理を軽くできるという点があります。
しかしその必要がなくても、ポリゴンの扱いに慣れるまでは練習の意味でも表のみ表示するように設定しておくといいかもしれません。

頂点座標に色を設定する

サンプルプログラムでは、各頂点座標を指定する前にそれぞれ色の設定を行っています。
glColor3f(1.0f, 0.5f, 0.5f);			//	次に指定する座標に RGB で色を設定する
glVertex3f(width - 50, height- 50, 0);  //	座標を指定する
ポリゴンの各頂点には色情報を設定でき、それによってポリゴンの内側が設定した色で塗りつぶされます。
色情報の設定には glColor3f() 関数を使っていて、引数には RGB の R 値, G 値, B 値 をそれぞれ float 型で渡しています。一般的な RGB では、値の範囲は 0 ~ 255 ですが、glColor3f() では 0f ~ 1f なので注意してください。また色情報のデフォルト値は黒(0, 0, 0)なので、1度も設定しないと背景色の黒と同じになって見えません。
glColor3f() にも glVertex3f() のように、引数の型などによっていろいろな亜種の関数があります。

頂点には別々の色情報を設定でき、頂点同士の間はグラデーション表示されます。FF7 はまだテクスチャがそんなに使われていなかった頃のゲームなので、色指定を駆使していろいろ綺麗にグラデーションして見せてましたねー。

これでポリゴンの表示については解説おわりです。

ダブルバッファリング

最後に、はじめの方で少し触れたダブルバッファリングのことを話したいと思います。
ポリゴンを複数表示する場合、

  1. 画面の初期化
  2. ポリゴン A を描く
  3. ポリゴン B を描く
  4. ポリゴン C を描く
  ...
  ループで 1 に戻る

というようなコードを書くと思いますが、例えばポリゴン A を描いたあとで PC のディスプレイが更新された場合、ポリゴン B とポリゴン C はまだ描かれていないことになります。PC ディスプレイの更新タイミングはハードウェアの仕様によるものなので、プログラムから制御することはできません。つまり、描かれるはずのポリゴンが見えないことがあり得るということです。処理は同じなのに見えたり見えなかったりで、ちらつきが発生するわけですね。

この問題を解決するために、ゲームや GUI ツールではダブルバッファリングという仕組みが実装されています。

ダブルバッファリングでは、画面に直接ポリゴンを描き込んでいくのではなく、画面と同じ大きさの絵(に対応するメモリ領域)をもう一枚用意しておき、そこにすべてのポリゴンを描きこんでいきます。そして最後に、その絵の内容をまるごと画面に反映するのです。
4ff1991206ba704830091a80bba6f9da3c9b.png
こうすることで、すべてのポリゴンが同時に画面へ表示されるため、すべてが表示されるか、まったく表示されないか(更新前の表示のまま)の2択になります。一部のポリゴンのみ表示されるという中途半端な状態が、プレイヤーには見えなくなるのです。

このもう一枚の絵のほうをオフスクリーン(オフスクリーンバッファ)と呼びます。

LWJGL でのダブルバッファリング

LWJGL ではデフォルトでダブルバッファリングされているので、特にこれを意識してコードを描く必要はありません。
//	オフスクリーンを初期化する
//	色情報はすべて 0(= 黒)に再設定される。
glClear(GL_COLOR_BUFFER_BIT);

//	オフスクリーンに描画する
render();
 
//	オフスクリーンをスクリーンに反映する
Display.update();
レンダリング処理はすべてオフスクリーンに対して行われるようになっており、あとは Display.update() を呼び出すだけでオフスクリーンの内容が画面に反映されます。簡単ですねー。

終わり!

さてさて、ゲームプログラミングの最初の一歩として必要かなーという内容は、これですべてになります。ポリゴン表示とかは目にめるので、やりはじめると楽しいですよね。
次回はいよいよ2次元の壁を超え、3次元の立体表示に突入してみたいと思いますー。

今日の Eclipse Tip

Eclipse ではデバッグ実行中にソースコードを書き換えて保存すると、即座に実行内容に反映されます。一回停止してからもう一回 F11 を押すとかしなくて OK です。コンパイラ言語なのにインタプリタみたい!
これはホットデプロイと呼ばれる機能です。座標とか色とかをちょこっと修正するのに便利なので、とりあえずソース書き換えたら Ctrl + s を押してください。

ただし、static 変数の値やメソッドの追加をした場合はクラス定義が変わってしまうため、再実行(再コンパイル)が必要になります。

  LWJGL  コメント (0)  2012/03/02 22:56:08


公開範囲:
プロフィール HN: ももかん
ゲーム作ったり雑談書いたり・・・していた時期が私にもありました。
カレンダー
<<2019, 7>>
30123456
78910111213
14151617181920
21222324252627
28293031123