この演習では,これらのような機能をどのように使えばいいのか,を学んでいきます.

コマンド には,幾何学的オブジェクトを指定するものや,処理ステージにおけるオブジェクトの取り扱い方法を制御するものなどがあります.全てのコマンドをパイプラインに通して直ちに実行するとは限らず, ディスプレイリスト にコマンドを一旦保持してから,後で処理することも可能です.
エバリュエータ では,多項式近似の曲線と曲面といった幾何学形状を処理します. OpenGL は,幾何学的プリミティブを頂点の集合として処理するのですが,それは, 頂点単位の演算とプリミティブの形成 というステージで行われます.この二つのステージは,幾何学的な処理をする部分です.
続いて,ラスタライズ, フラグメント単位の演算 では,フレームバッファ(ラスタスキャン型CRTの場合に,画像の情報を一時的に収めておく場所)にためる値に関係する計算をします.ここでの演算には,フレームバッファのzバッファ値の条件つき更新や,入力された色ともともと保存されていた色との混合,マスク処理などが含まれます.
入力データは頂点データに限らず,ビットマップ形式のような画像も入力可能で,そういう形式のデータは,テクスチャマッピングに利用する画像とみなされ,前述のステージは通らず, ピクセルの演算 ステージでピクセルとして処理されます.この結果は,テクスチャメモリ にラスタライズ用に保存,または,ただちにラスタライズされます.その後は,あたかも幾何学的データから生成されたものであるかのように,フレームバッファに結合されます.
以上が大まかな流れですが,やってみないとよくわからないと思いますので,早速サンプルプログラムを書いて,コンパイルして,実行してみましょう.
まず,この演習用のディレクトリを作成しましょう.ホームディレクトリ(/usr/people/s0x10xx/ など)にファイルがバラバラと置いてあるのは,一般的に,あまり感心されません.ディレクトリ名は何でもいいですが,次の例では Graphics Science Seminar の頭文字をとって,gss という名前にしています.
では,作成したディレクトリへ移動しましょう.
% mkdir gss
OpenGL と GLUT のライブラリを利用してコンパイルするには,cc(あるいはgcc) または CC コマンドに以下のようなオプションをつけて実行します.
% cd gss
でも,毎回,こんなに長いコマンドを入力するのは面倒なので,以下のどれかの方法で,ラクをしてください.
% cc program.c -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm
そうすると,以降ログインした時には,以下のコマンドを入力するだけですみます.
alias ccgl 'cc \!* -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm'
なお,書き加えた直後だけ,以下のコマンドを実行し,書き加えた alias を有効にしてください.
% ccgl program.c
この方法は,カレントディレクトリを気にしないで,どこででも実行できるので便利です.
% source ~/.cshrc
そのあと chmod コマンドで,ファイルのモードを変更して,実行可能にします.
#!/bin/sh exec cc "$@" -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm
以降はこの ccgl コマンドを使ってコンパイルできます。
% chmod +x ccgl
ただし,ccgl ファイルが,カレントディレクトリ,または,コマンドパスに記述されているディレクトリに存在しない場合は,パスをちゃんと入力する必要があります.
% ccgl program.c
このような記述の Makefile のあるディレクトリで make コマンドを実行すると, program.c がコンパイルされて a.out という実行ファイルが生成されます.
LIBS = -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm a.out: program.c --Tab-->cc program.c $(LIBS)
このコマンドは emacs の中からも M-x compile で起動できます.
% make
Makefile には,ファイルの生成規則を記述します. make は実行すると,Makefile 中の最初の生成規則を探します.上のファイルの場合,a.out の行がそれになります.この行には a.out というターゲットを生成するのに program.c が必要だという依存関係を記述しており,その次の行に実際に a.out を生成するための手続きを記述しています.
ターゲットが複数あるときは以下のように記述します.
この場合の最初の生成規則は all の行で, all を生成するには prog1 と prog2 が必要だという依存関係を記述しています. make は prog1 と prog2 の両方の生成が完了した時点で終了します.特定のターゲットだけを生成したいときは,そのターゲット名を make の引数に指定します.
LIBS = -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm all: prog1 prog2 prog1: prog1.c --Tab-->cc prog1.c -o prog1 $(LIBS) prog2: prog2.c --Tab-->cc prog2.c -o prog2 $(LIBS)
Makefile を使うと,プログラムファイル名を変更するたびに,2行ほど追加記述しないといけないように思えますが, make コマンドにはあらかじめ約束事があり,それに従う処理の記述は省略できます.詳しくは,床井先生が書かれた Makefile の補足を参照してください.
% make prog1
GLUT を利用した OpenGL プログラムの最も基本的なものは,ウィンドウを開くだけ,の,次のようなプログラムです.このソースプログラムを適当なファイル名(たとえば,prog01.c)で作成し,コンパイルして出来上がった実行プログラム(a.out)を実行してみましょう.
/* prog01.c */ /* 空のウィンドウを開く */ #include <GL/glut.h> /* 描画 */ void display(void) { } int main(int argc, char** argv) { /* 初期化 */ glutInit(&argc, argv); /* ウィンドウの生成 */ glutCreateWindow(argv[0]); /* 描画ルーチンの設定 */ glutDisplayFunc(display); /* 無限ループ */ glutMainLoop(); return 0; }
![]()
- #include <GL/glut.h>
- GLUT ライブラリを利用する場合のインクルードファイルです.環境に応じて,glut.h が他に必要なヘッダーファイルをインクルードしてくれるので,ソースコードは異なる環境で共通に利用できます.
- glutInit(int *argcp, char **argv)
- GLUT および OpenGL 環境を初期化します. argcp には main 関数の引数を示す argc のアドレスを渡します. argv は main 関数の argv そのものです. GLUT で解釈された引数は配列から消去され, X Window で使われるオプション -display, -geometry などはここで処理されます.プログラム自身で処理すべき引数があるときは,この後で処理します.
- int glutCreateWindow(char *titleString)
- OpenGL による図形等を描画するための台紙(?)となるウィンドウをこの関数で開きます.引数 titleString によって,そのウィンドウのタイトルバーなどに表示されるウィンドウの名前の文字列を指定します. なお,戻り値は開いたウィンドウの識別子です.
- void glutDisplayFunc(void (*func)(void))
- 引数 func は開いたウィンドウ内に描画する関数へのポインタです.ウィンドウが開かれたり,他のウィンドウによって隠されたウィンドウが再び現れたりして,ウィンドウを再描画する必要があるときにこの関数が実行されます.この関数内で図形などの描画を行います.
- void glutMainLoop(void)
- これは無限ループです.この関数を呼び出すことで,プログラムはイベントの待ち受け状態になります.
このように,プログラムは,
最初に display() が実行されるのは,初めてウィンドウが開いたとき,すなわち, glutMainLoop() が glutCreateWindow() の指示を受けてウィンドウの生成を完了したときになります.また,その後も,このウィンドウがほかのウィンドウに隠され再び現れたときのように,ウィンドウの再描画が必要になったときに実行されます.
上のプログラムでは display() の中身として何も記述していないので, display() が呼び出されても何も仕事をしません.試しにこのウィンドウを移動したり,他のウィンドウで隠したりしてみてください.ウィンドウの中の表示はおかしなものになっていると思います.
このように複数の(オーバーラップ可能な)ウィンドウが使用できるウィンドウシステム環境に対応したプログラムでは,処理の流れは時間軸に沿って「プログラムの始めから終りへ」ではなく,何かこと(事象)が起るたびに「プログラムの各部がランダムに」実行されます.従って,このプログラムのスタイルも,「事象」に対して,その「対処方法」を登録していくというものになります.一般に,この事象をイベント と呼び,対処方法の手続きをハンドラ, イベントハンドラと呼びます.
このプログラムには,「プログラムを終了する方法」を組み込んでいないので,終了するには,実行したウィンドウで Ctrl-C をタイプするか, OpenGL ウィンドウのタイトルバーの左のボタンをクリックして「閉じる」か「中止」を選んでください.
prog01.c では,関数 display() に何も記述していなかったので,ウィンドウの枠だけ出現し,ウィンドウの中身はでたらめだったと思います.「基本的な流れ」に登場する残りの関数を使って,ウィンドウを塗りつぶしてみましょう. prog01.c に太字のところを追加し,コンパイルしてプログラムを実行してください.
/* prog01.c */ /* 青く塗りつぶしたウィンドウを開く */ #include <GL/glut.h> /* 描画 */ void display(void) { glClear(GL_COLOR_BUFFER_BIT); glFlush(); } void init(void) { glClearColor(0.0, 0.0, 1.0, 0.0); } int main(int argc, char** argv) { /* 初期化 */ glutInit(&argc, argv); /* ウィンドウの生成 */ glutInitDisplayMode(GLUT_RGBA); glutCreateWindow(argv[0]); /* OpenGL 初期化ルーチンの呼出し */ init(); /* 描画ルーチンの設定 */ glutDisplayFunc(display); /* 無限ループ */ glutMainLoop(); return 0; }
- void glutInitDisplayMode(unsigned int mode)
- ディスプレイの表示モードを設定します. mode として,次のような値を指定します.
例えば,RGBモードで隠面消去してアニメーションをするときは,
- RGBモードか,カラーインデックスモードか (GLUT_RGBS or GLUT_INDEX)
- ダブルバッファを使うかどうか (GLUT_SINGLE or GLUT_DOUBLE)
これは,アニメーションをさせるときに重要なポイントです.- 隠面消去をするかどうか (GLUT_DEPTH)
glutInitDisplayMode(GLUT_RGBA | GLUT_DOBULE | GLUT_DEPTH);
というように指定します.このような設定は,演習をすすめていくうちにでてきますが,ここでは,GLUT_RGBA モードだけを指定します.mode に GLUT_RGBA を指定した場合は,色の指定を RGB(赤緑青,光の3原色)で行えます.色の話は,別の機会にもう少し詳しく説明します.- void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
- glClear(GL_COLOR_BUFFER_BIT) でウィンドウを初期化する際の色を指定します. red,green,blue はそれぞれ赤,緑,青色の成分の強さを示す GLclampf 型(float 型と等価)の値で,0〜1 の間の値を持ちます. 1.0 が最も明るく,この3つに (0.0, 0.0, 0.0)を指定すれば黒色, (1.0, 1.0, 1.0) を指定すれば白色になります.上の例ではウィンドウは青色で塗りつぶされます。最後の alpha はα値と呼ばれ,OpenGL では不透明度として扱われます(0.0 で透明,1.0 で不透明).ここではとりあえず 0.0 にしておいてください。
- void glClear(GLbitfield mask)
- ウィンドウを塗りつぶします. mask には塗りつぶすバッファを指定します. OpenGL が管理する画面上のバッファ(メモリ)には,色を格納するカラーバッファの他,隠面消去に使うデプスバッファ,凝ったことをするときに使うステンシルバッファ,カラーバッファの上に重ねて表示されるオーバーレイバッファなど,いくつかのものがあり,これらが一つのウィンドウに重なって存在しています. mask に GL_COLOR_BUFFER_BIT を指定したときは,カラーバッファだけが塗りつぶされます.
- glFlush(void)
- glFlush() はまだ実行されていない OpenGL の命令を全て実行します. OpenGL は関数呼び出しによって生成される OpenGL の命令をその都度実行するのではなく,いくつか溜め込んでおいてまとめて実行します.このため,ある程度命令が溜まらないと関数を呼び出しても実行が開始されない場合があります. glFlush() はそういう状況でまだ実行されていない残りの命令の実行を開始します.頻繁に glFlush() を呼び出すと,描画速度が低下することもあります.
1.1 で示した「基本的な流れ」と,prog01.c で呼ばれている関数が実行される順とが一致していることを確認してください.
ウィンドウ内に線を引いてみます。 prog1.c を以下のように変更し、コンパイルしてプログラムを実行してください。
#include <GL/glut.h>
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_LINE_LOOP);
glVertex2d(-0.9, -0.9);
glVertex2d(0.9, -0.9);
glVertex2d(0.9, 0.9);
glVertex2d(-0.9, 0.9);
glEnd();
glFlush();
}
void init(void)
{
/* 変更なし */
}
int main(int argc, char *argv[])
{
/* 変更なし */
}
void glBegin(GLnum mode)
void glEnd(void)
void glVertex2d(GLdouble x, GLdouble y)
glBegin() の引数 mode に指定できる図形のタイプには以下のようなものがあります。詳しくは man glBegin を参照してください。
GL_POINTS
GL_LINES
GL_LINE_STRIP
GL_LINE_LOOP
GL_TRIANGLES / GL_QUADS
GL_TRIANGLE_STRIP / GL_QUAD_STRIP
GL_TRIANGLE_FAN
GL_POLYGON
OpenGL を処理するハードウェアは、実際には3角形しか塗り潰すことができません(モノによっては4角形もできるものもあります)。このため GL_POLYGON の場合は、多角形を3角形に分割してから処理します。従って、もし描画速度が重要なら GL_TRIANGLE_STRIP や GL_TRIANGLE_FAN を使うようプログラムを工夫してみてください。また GL_QUADS も GL_POLYGON より高速です。
線に色を付けてみます。 prog1.c を以下のように変更し、コンパイルしてください。プログラムを実行したら線は何色で表示されたでしょうか?
#include <GL/glut.h>
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1.0, 0.0, 0.0);
glBegin(GL_LINE_LOOP);
glVertex2d(-0.9, -0.9);
glVertex2d(0.9, -0.9);
glVertex2d(0.9, 0.9);
glVertex2d(-0.9, 0.9);
glEnd();
glFlush();
}
void init(void)
{
/* 変更なし */
}
int main(int argc, char *argv[])
{
/* 変更なし */
}
void glColor3d(GLdouble r, GLdouble g, GLdouble
b)
図形を塗りつぶしてみます。 GL_LINE_LOOP を GL_POLYGON に変更し、ついでに背景も白色に変更しましょう。変更したプログラムをコンパイルして実行してください。
#include <GL/glut.h>
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex2d(-0.9, -0.9);
glVertex2d(0.9, -0.9);
glVertex2d(0.9, 0.9);
glVertex2d(-0.9, 0.9);
glEnd();
glFlush();
}
void init(void)
{
glClearColor(1.0, 1.0, 1.0, 0.0);
}
int main(int argc, char *argv[])
{
/* 変更なし */
}
色は頂点毎に指定することもできます。 prog1.c を以下のように変更してください。コンパイルしてプログラムを実行すると、どういう色の付き方になったでしょうか?
#include <GL/glut.h>
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3d(1.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glColor3d(1.0, 0.0, 0.0); /* 赤 */
glVertex2d(-0.9, -0.9);
glColor3d(0.0, 1.0, 0.0); /* 緑 */
glVertex2d(0.9, -0.9);
glColor3d(0.0, 0.0, 1.0); /* 青 */
glVertex2d(0.9, 0.9);
glColor3d(1.0, 1.0, 0.0); /* 黄 */
glVertex2d(-0.9, 0.9);
glEnd();
glFlush();
}
void init(void)
{
/* 変更なし */
}
int main(int argc, char *argv[])
{
/* 変更なし */
}
多分、多角形の内部は頂点の色から補間した色で塗りつぶされたと思います。このプログラムは後で使用するので、 prog2.c というコピーを作っておいてください。
% cp prog1.c prog2.c
次回以降,このような基本的なプログラムへ,さまざまな関数を呼び出す記述を追加していくことで,CG のプログラムを発展させていきます.