OpenGL 入門

−MesaGL と GLUT による3次元グラフィックスへの第一歩−

情報処理教育センター

舟橋 健司

sample image


このページは、「OpenGL 入門('99/5/6)」の講習会資料です。 オリジナルドキュメントから本文は変っていません。 センターの名前は、2000年4月より情報メディア教育センターに、 2006年4月より情報基盤センターになっています。
[注] 実装によっては ogl05.c までのプログラムがうまく表示されない場合が あります。その場合は display() 関数の最後で glFlush() をコールする ことにより表示されます(3.3 ogl05.c-2 の方法も可でしょう)。
[注2] (ogl-k.c) PI が define されていない場合には、プログラムの 最初の方に「#define PI (3.141592)」を入れるか、 コンパイル時に「-DPI=3.141592」とオプション指定してください。
[注3] Mac OS X でコンパイルする場合には、まず「#include <GL/glut.h>」を 「#include <GLUT/glut.h>」に変更する(GL/gl.h、GL/glu.h はそのまま)か、 または、ディレクトリ /usr/local/include/GL を作成しそこに /System/Library/Frameworks/GLUT.framework/Versions/A/Headers/glut.h の シンボリックリンク glut.h を作成してください。その上で、 cc -framework OpenGL -framework GLUT -framework Foundation FILENAME.c でコンパイルしてください。



目次


0。はじめに

講習会「OpenGL 入門」は、計算機 (ここでは UNIX) の経験があり、C 言語で プログラムを組んだことがある人、そしてグラフィックスに興味のある人を対 象としています。また、本資料は講習会のためのものであり、資料のみで学習 するには不十分な点があります。


1。OpenGL とは

従来からグラフィックスライブラリとして、各社から独自の特色を前面に出し たものが存在しました。その一つにシリコングラフィックス社(SGI) の GL (OpenGL と区別して IrisGL と呼ばれることがある) があります。IrisGL は、 狭い意味でのグラフィックス機能だけではなく、ウィンドウの制御、マウスや キーボードイベント処理も含んだものでした。このウィンドウシステムの制御 には、Unix の世界では X Window が標準的に用いられています。また、パー ソナルユースとして普及しているWindows95/98/NT や MacOS では、それぞれ の OS (いわゆるウィンドウシステムを含んでいる) 独自の方法が用いられて います。そこで、グラフィックス機能だけを抜きだし標準化したものが OpenGLです。OpenGL は多くの場合、専用のグラフィックスハードウェア (ラ スタエンジン、ジオメトリエンジンなど) を利用するように作られているため、 高速なグラフィックス表示が可能です。公開されている OpenGL の仕様をもと に、CPU だけで処理を行うように作成されたフリーウェアとして MesaGL があ ります。ウィンドウシステムの制御を放棄した代わりに、これを引き受ける AUX (補助) ライブラリなるものが導入されました。しかし、これはウィンド ウシステムの制御から (一時的に?)解放し、OpenGL の学習に専念できるよう にしたもの、という位置づけであり、機能的にも多くの問題を含んだものでし た。この AUX を改良したものがGLUT (OpenGL Utility Toolkit) です。ただ し、本格的な GUI ソフトの開発には、Motif などを用いる必要があるでしょ う。

講習会では、MesaGL と GLUT のインストールされた Linux によりグラフィッ クスの体験をしてもらいます。また、以下で OpenGL と言う場合には実際には MesaGL を指している場合があります。


2。とりあえず描いてみる

2.1 コンパイルの仕方

OpneGL、GLUT を利用する場合には、さまざまなライブラリをリンクする必要 があります。これらのリンクの順序にも前後関係があるため、興味のある方は 各自で勉強してください。コンパイルは次の命令を filename.c を ogl01.c などに置換え、コマンドラインで実行してください。

% gcc filename.c -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm

毎回、これだけをタイプするのは大変ですね。簡単にする方法としてalias を 作る、shell スクリプトを作る、Makefile を作るなど考えられますが、ここ では、oglcc という名前で以下の一行の内容のの shell スクリプトを作るこ とにします。このファイルは実行可能 (chmod +x oglcc) である必要がありま す。

/*-oglcc---------------------------------------*/
gcc $* -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm
/*----------------------------------------*/

% oglcc filename.c
とコンパイルすると、a.out という名前の実行ファイルが作成されるので、
% a.out
で実行してみてください。以下では、表示されたウィンドウがアクティブな状 態で ESC キーを押すと終了するようにしてあります。

また、今回は時間短縮のため各ファイルを実際に作成してもらう代わりに、
% /u/adm/kenji/bin/cpogl
と実行することにより必要なファイルをコピーしてください。

2.2 ウィンドウと直線

詳しい説明は後回しにして、実践あるのみ、まずは何か画面に出してみなけれ ば始まりません。以下のプログラム ogl01.c を保存、コンパイル、実行して みましょう。

/*-ogl01.c---------------------------------------*/
#include <GL/glut.h>

void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_LINE_LOOP);
  glColor3d(1.0, 0.0, 0.0);
  glVertex2d(-0.7, -0.7);
  glVertex2d(0.7, -0.7);
  glVertex2d(0.0, 0.7);
  glEnd();
}

void keyboard(unsigned char key, int x, int y)
{
  if (key == '\033') exit(0);  /* '\033' は ESC の ASCII コード */
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA);
  glutCreateWindow(argv[0]);
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
}
/*----------------------------------------*/

うまく実行できましたか? まず、プログラムの1行目を見てみましょう。 「#include <GL/glut.h>」とあるのは、GLUT を使います、ということです。 「#include <GL/gl.h>」、「#include <GL/glu.h>」も必要なのですが、 これらは glut.h の中で include されているので、1行で済ませることができます。

イベント駆動型のプログラムを初めて見る人は、display() という関数が一体 どこで呼ばれるのか不思議に思うかも知れません。これは、ウィンドウが生成 された、隠れていたウィンドウが現れた、などの「イベント」が発生したとき に、登録された手続きが実行されるプログラムです。

では、関数名のプリフィックスが "glut" となっている GLUT の関数について 説明します。

void glutInit(int *argcp, char **argv)
GLUT および OpenGL 環境を初期化します。引数には main の引数をそのまま渡します。
void glutInitDisplayMode(unsigned int mode)
ディスプレイの表示モードを設定します。mode に GLUT_RGBA を指定した場合 は、色の指定を RGB で行えるようにします。他にインデックスカラーモード (GLUT_INDEX) も指定できます。
int glutCreateWindow(char *name)
ウィンドウを開きます。引数 name はそのウィンドウの名前の文字列で、タイ トルバーなどに表示されます。戻り値は開いたウィンドウの識別子です。
void glutDisplayFunc(void (*func)(void))
引数 func は開いたウィンドウ内に描画する関数へのポインタです。ウィンド ウが開かれたり、他のウィンドウによって隠されたウィンドウが再び現れたり して、ウィンドウを再描画する必要があるときに、この関数が実行されます。
glutKeyboardFunc(void (*func)(unsigned char key, int x, int y))
引数 func には、キーがタイプされたときに実行する関数のポインタを与えま す。この関数の引数 key にはタイプされたキーの ASCII コードが渡されます。 また x と y にはキーがタイプされたときのマウスの位置が渡されます。 c.f. Shift や Ctrl のようなモディファイア(修飾)キーを検出するには glutGetModifiers()、ファンクションキーのような文字キー以外のタイプを検 出するときは glutSpecialFunc() を使います。
void glutMainLoop(void)
これは無限ループです。この関数を呼び出すことで、プログラムはイベントの 待ち受け状態になります。

次に、関数名のプリフィックスが "gl" となっている OpenGL の関数について 説明します。OpenGL はステートマシンと呼ばれ、状態を保持しながら各処理 を行っていきます。具体的には、赤い線を描く場合に「赤色で線を描け」と命 令するのではなく、「これから描く色は赤」と命令しておき、以後「線を描け」 と命令します。

void glClearColor(GLclampf R, GLclampf G, GLclampf B, GLclampf A)
glClear(GL_COLOR_BUFFER_BIT) でウィンドウを塗りつぶす際の色を指定しま す。RGBA はそれぞれ赤、緑、青色の成分の強さを示す GLclampf 型 (一般に は float 型と等価)の値で、 0〜1 の間の値を持ちます。A はα値と呼ばれ、 透明度として扱われます。
void glClear(GLbitfield mask)
ウィンドウを塗りつぶします。 mask には塗りつぶすバッファを指定します。 OpenGL が管理する画面上のバッファには、色を格納するカラーバッファの他、 隠面消去に使うデプスバッファなど、いくつかのものがあり、これらが一つの ウィンドウに存在しています。mask に GL_COLOR_BUFFER_BIT を指定したとき は、カラーバッファだけが塗りつぶされます。
void glBegin(GLnum mode)
void glEnd(void)
図形を描くには、glBegin()〜glEnd() の間にその図形の各頂点の座標値を設 定する関数を置きます。glBegin() の引数 mode には描画する図形のタイプを 指定します。
GL_POINTS	点を打ちます。
GL_LINES	2点を対にして、その間を直線で結びます。
GL_LINE_STRIP	折れ線を描きます。
GL_LINE_LOOP	折れ線を描きます。始点と終点の間も結ばれます。
GL_TRIANGLES / GL_QUADS
		3/4点を組にして、三角形/四角形を描きます。
GL_TRIANGLE_STRIP / GL_QUAD_STRIP
		一辺を共有しながら帯状に三角形/四角形を描きます。
GL_TRIANGLE_FAN	一辺を共有しながら扇状に三角形を描きます。
GL_POLYGON	凸多角形を描きます。
void glColor3d(GLdouble r, GLdouble g, GLdouble b)
glColor3d() はこれから描画するものの色を指定します。引数の型は GLdouble 型(一般に double と等価)で、左から赤 (R)、緑 (G)、青 (B) の 強さを 0〜1 の範囲で指定します。
void glVertex2d(GLdouble x, GLdouble y)
2次元の座標値を設定するのに使います。引数の型は GLdouble です。
c.f. 関数のサフィックスは、引数の型、数をあらわしています。
2: 2-4: 値の数、次元。
d: d: GLdouble, f: GLfloat, i: GLint, s: GLshort, etc.
v: ポインタ渡し。ex. gl*2dv(GLdouble *v), GLdouble v[2]

さて、時間に余裕のある人は、
glClearColor(1.0, 1.0, 1.0, 0.0);
glColor3d(1.0, 0.0, 0.0);
の色の指定を変更してみてください。また、
glVertex2d(-0.7, -0.7);
の頂点 (vertex) の指定を変更したり、増やしたりしてみてください。 思った通りに表示されましたか?

2.3 塗りつぶしてみる

プログラム ogl01.c の関数 display() を、以下のように変更してみてください。

/*-ogl02.c---------------------------------------*/
#include <GL/glut.h>

void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_POLYGON);
  glColor3d(1.0, 0.0, 0.0);
  glVertex2d(-0.7, -0.7);
  glColor3d(0.0, 1.0, 0.0);
  glVertex2d(0.7, -0.7);
  glColor3d(0.0, 0.0, 1.0);
  glVertex2d(0.0, 0.7);
  glEnd();
}
/*-以下、ogl01.c と同じ-*/
/*----------------------------------------*/

実行する前にどのように表示されるか考えてみましょう。 glBegin(GL_LINE_LOOP); が glBegin(GL_POLYGON); に 変更されています。直線ではなく、多角形 (塗りつぶされている) になること が分かります。次に、各点に色が指定されています。この場合、多角形の内部 は頂点の色から補間した色で塗りつぶされます。言葉で説明しても分からない 場合は、実際に実行してみましょう。

2.4 座標軸を考える

まず ogl02.c を実行し、ウィンドウのサイズを変更してみましょう。表示内 容もそれにつれて拡大縮小していますね。これを表示内容の大きを変えずに表 示領域のみを広げるようにします。また、ウィンドウが最初に表示される位置、 大きさも指定してみましょう。

/*-ogl03.c---------------------------------------*/
/*-ここまで、ogl02.c と同じ-*/
void resize(int w, int h) /*-関数追加-*/
{
  glViewport(0, 0, w, h);
  glLoadIdentity();
  glOrtho(-w / 200.0, w / 200.0, -h / 200.0, h / 200.0, -1.0, 1.0);
}

int main(int argc, char *argv[])
{
  glutInitWindowPosition(100, 100);	/*-追加-*/
  glutInitWindowSize(320, 240);		/*-追加-*/
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA);
  glutCreateWindow(argv[0]);
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutReshapeFunc(resize);		/*-追加-*/
  glutMainLoop();
  return 0;
}
/*----------------------------------------*/
void glViewport(GLint x, GLint y, GLsizei w, GLsizei h)
ビューポート (開いたウィンドウの中で描画が行われる領域、正規化デバイス 座標系の (-1, -1), (1, 1) を対角線とする矩形領域がここに表示される) を 設定します。最初の2つの引数 x、y にはその領域の左下隅の位置、w には幅、 h には高さをデバイス座標系、すなわちディスプレ以上の画素数で指定します。
void glLoadIdentity(void)
変換行列を初期化します。座標変換は行列の積であらわされますから、変換行 列に初期値として単位行列を設定します。
void glOrtho(GLdouble l, GLdouble r, GLdouble b, GLdouble t, GLdouble n, GLdouble f)
ワールド座標系を正規化デバイス座標系に平行投影 (orthographic projection) する行列を変換行列に乗じます。引数には、l に表示領域の左端 の位置、r に右端の位置、b に下端の位置、t に上端の位置、n に前方面の位 置、f に後方面の位置を指定します。これは、ビューポートに表示される空間 の座標軸を設定します。
void glutInitWindowSize(int w, int h)
新たに開くウィンドウの幅と高さを指定します。
void glutInitWindowPosition(int x, int y)
新たに開くウィンドウの位置を指定します。
glutReshapeFunc(void (*func)(int w, int h))
引数 func には、 ウィンドウがリサイズされたときに実行する関数のポイン タを与えます。 この関数の引数にはリサイズ後のウィンドウの幅と高さが渡 されます。

3。やっと3次元グラフィックス

3.1 線画を表示してみる

さて、やっとお待ちかねの3次元グラフィックスです。実はこれまでのプログ ラムでも、見かけは2次元の図形でしたが、OpenGL の内部では実際には3次 元の処理を行っています。すなわち画面表示に対して垂直に Z 軸が伸びてい て、その3次元空間の XY 平面への平行投影像を表示していたのです。

次のプログラム ogl04.c を見てみましょう。8つの3次元座標を配列として 定義しています。頭の中で想像してみてください、立方体の8つの頂点だと分 かりますか? 次に1組み2つ、12組みの数字が定義してあります、これは、 0〜7番目の頂点のうちの2つを結んだ稜線(辺)をあらわしています。関数 glVertex3dv は、前述したように GLdouble 型の3次元座標をポインタとして 受け取ります。それではコンパイル、表示してみましょう。

Fig.1
/*-ogl04.c---------------------------------------*/
#include <GL/glut.h>

void display(void)
{
  int i;
  GLdouble vertex[8][3] = {
    { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 },
    { 1.0, 1.0, 0.0 }, { 0.0, 1.0, 0.0 },
    { 0.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 },
    { 1.0, 1.0, 1.0 }, { 0.0, 1.0, 1.0 }
  };
  int edge[12][2] = {
    { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 },
    { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 },
    { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }
  };
  
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3d(0.0, 0.0, 0.0);
  glBegin(GL_LINES);
  for (i = 0; i < 12; i++) {
    glVertex3dv(vertex[edge[i][0]]);
    glVertex3dv(vertex[edge[i][1]]);
  }
  glEnd();
}

void keyboard(unsigned char key, int x, int y)
{
  if (key == '\033') exit(0);
}

void resize(int w, int h)
{
  glViewport(0, 0, w, h);
  glLoadIdentity();
  glOrtho(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA);
  glutCreateWindow(argv[0]);
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutReshapeFunc(resize);
  glutMainLoop();
  return 0;
}
/*----------------------------------------*/

うまく表示されましたか? 期待していたものとは違っていたかもしれません。 これは、正面から見た立方体を平行投影しているためです。では透視投影にし てみましょう。関数 resize をプログラム ogl04.c-2 に示すように修正して ください。デフォルトでは視点が原点になり立方体と重なってしまうので、視 点座標系を平行移動し、稜線が重ならないように回転移動しています。うまく 表示できたら、同じように ogl04.c-3 に示すように修正してください。今度 は視点、および視線方向を指定することにより立方体らしく見えるようにして います。

Fig.2 Fig.3
/*-ogl04.c-2--------------------------------------*/
void resize(int w, int h)
{
  glViewport(0, 0, w, h);
  glLoadIdentity();
  gluPerspective(30.0, 1.0, 1.0, 10.0);
  glTranslated(0.0, 0.0, -5.0);
  glRotated(5.0, 1.0, 1.0, 0.0);
}
/*----------------------------------------*/
/*-ogl04.c-3--------------------------------------*/
void resize(int w, int h)
{
  glViewport(0, 0, w, h);
  glLoadIdentity();
  gluPerspective(30.0, 1.0, 1.0, 10.0);
  gluLookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}
/*----------------------------------------*/

関数名のプリフィックスが "glu" となっている関数がでてきました。 これら、GL Utility ライブラリについても説明します。

gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
変換行列に透視変換の行列を乗じます。 最初の引数 fovy は視野角であり、 度で表します。2つ目の引数 aspect は画面の縦横比です。3つ目の引数 zNear と4つ目の引数 zFar は表示を行う奥行き方向の範囲で、zNear は手前 (前方面)、zFar は後方(後方面)の位置を示します。この間にある物体が 描画されます。
void gluLookAt(GLdouble ex, GLdouble ey, GLdouble ez, GLdouble cx, GLdouble cy, GLdouble cz, GLdouble ux, GLdouble uy, GLdouble uz)
この最初の3つの引数 ex, ey, ez は視点の位置、次の3つの引数 cx, cy, cz は目標の位置、最後の3つの引数 ux, uy, uz は、ウィンドウに表示され る画像の「上」の方向を示すベクトルです。
glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z)
変換行列に回転の行列を乗じます。引数はいずれも GLdouble型で、1つ目の 引数 angle は回転角、残りの3つの引数 x, y, z は回転軸の方向ベクトルで す。
glTranslated(GLdouble x, GLdouble y, GLdouble z)
変換行列に平行移動の行列を乗じます。引数はいずれも GLdouble 型、3つの 引数 x, y, z は現在の位置からの相対的な移動量をしていします。

時間に余裕のある人は、描く図形や視点をうまく変更してみてください。 期待した表示ができましたか?

3.2 アニメーション

3次元データによる立方体は表示できましたが、あまり3次元CGらしくあり ません。そこで今度はこの立方体を動かしてみましょう。イベント駆動型のプ ログラムでアニメーションを実現する考え方の一つに、「暇なときに再描画す る」という方法があります。

次に考えなければならないのは、座標変換です。これまでは深く考えずに説明 してきました。例えば、立方体の CG を描く場合には、

「モデリング変換」	立方体の空間内の位置、向き
「ビューイング変換」	その空間を見る視点、視線方向
「投影変換」		スクリーンへの投影方法

を考える必要があります。立方体を回す場合には「モデリング変換」を変更す れば良いわけです。また、止まっている立方体の周りを (見る側が) 動きたい のならば「ビューイング変換」を変更していけば良いのです。OpenGL では、 「モデル−ビュー変換」と「投影変換」を別々に考えられるようになっていま す。次のプログラム ogl05.c で立方体を回してみてください。

Fig.4
/*-ogl05.c---------------------------------------*/
#include <GL/glut.h>

void idle(void)
{
  glutPostRedisplay();
}

void display(void)
{
  int i;
  static int r = 0; /* 立方体の回転角 */
  GLdouble vertex[8][3] = {
    { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 },
    { 1.0, 1.0, 0.0 }, { 0.0, 1.0, 0.0 },
    { 0.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 },
    { 1.0, 1.0, 1.0 }, { 0.0, 1.0, 1.0 }
  };
  int edge[12][2] = {
    { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 },
    { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 },
    { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }
  };
  
  glClear(GL_COLOR_BUFFER_BIT);

  /* モデルビュー変換行列の初期化、設定 */
  glLoadIdentity();
  gluLookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
  glRotated((GLdouble) r, 0.0, 1.0, 0.0);
  
  glColor3d(0.0, 0.0, 0.0);
  glBegin(GL_LINES);
  for (i = 0; i < 12; i++) {
    glVertex3dv(vertex[edge[i][0]]);
    glVertex3dv(vertex[edge[i][1]]);
  }
  glEnd();

  if (++r >= 360) r = 0;
}

void resize(int w, int h)
{
  glViewport(0, 0, w, h);
  /* 投影変換行列の初期化、設定 */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(30.0, 1.0, 1.0, 10.0);
  /* モデルビュー変換行列に変更 */
  glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
  if (key == '\033') exit(0);
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA);
  glutCreateWindow(argv[0]);
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutIdleFunc(idle);
  glutReshapeFunc(resize);
  glutMainLoop();
}
/*----------------------------------------*/
void glutPostRedisplay(void)
再描画イベントを発生させます。このイベントの発生が発生すると、 glutDisplayFunc() で指定されている描画関数が実行されます。なお、再描画 が開始されるまでの間にこのイベントが複数回発生しても、この描画関数は1 度だけ実行されます。
void glutIdleFunc(void (*func)(void))
引数 func には、プログラムが「何もすることがない」ときに実行する関数の ポインタを指定します。引数の関数はプログラムが「暇なとき」に繰り返し実 行されます。引数に NULL を指定すると関数の指定を解除できます。
void glMatrixMode(GLenum mode)
設定する変換行列を指定します。引数 mode がGL_MODELVIEW ならモデルビュー 変換行列、GL_PROJECTION なら投影変換行列を指定します。

3.3 ダブルバッファ

立方体はうまく回りましたか? 回ったけれどちらついて綺麗ではない? プロ グラム ogl05.c では「見えている面」を一旦消去し、次の絵を描いているた め、ちらついてしまいます。そこで「2枚の面」を用意し、「見えない側の面」 に対して描画を行い、一瞬で「表と裏を入れ換える」ダブルバッファリングと いう方法でアニメーションを実現してみましょう。次に示すように、関数 display() の中に「glutSwapBuffers();」の1行を追加し、main() の中の 「glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);」の引数を修正してくだ さい。

/*-ogl05.c-2--------------------------------------*/
:
void display(void)
:
  glEnd();
  glutSwapBuffers(); /* 追加 */
  if (++r >= 360) r = 0;
:
int main(int argc, char *argv[])
:
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); /* 変更 */
:
/*----------------------------------------*/
int glutSwapBuffers(void)
2つのバッファを交換します。

さて、また時間に余裕のある人は、
glRotated((GLdouble) r, 0.0, 1.0, 0.0);
の回転軸を変更してみましょう。うまく回りましたか?

3.4 隠面消去

立方体がくるくるとスムーズに回るようになりました。ちょっと欲を出して、 面を張ってみましょう。前のプログラムでは2つの頂点を結んで稜線を指定し ましたが、今度は4つの頂点を結んで面を描くことにします。一般に、ポリゴ ン(多面体)を指定する場合(表から見て)左回り、とおぼえておいてください。 このとき、面の違いを分かりやすくするために、各面を違う色で表示してみま す。表示してみた絵をじっと見てみましょう。

/*-ogl06.c---------------------------------------*/
#include <GL/glut.h>

void idle(void)
{
  glutPostRedisplay();
}

void display(void)
{
  int i, j;
  static int r = 0;
  GLdouble vertex[8][3] = {
    { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 },
    { 1.0, 1.0, 0.0 }, { 0.0, 1.0, 0.0 },
    { 0.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 },
    { 1.0, 1.0, 1.0 }, { 0.0, 1.0, 1.0 }
  };
  int face[6][4] = {
    { 0, 3, 2, 1 }, { 1, 2, 6, 5 }, { 4, 5, 6, 7 },
    { 0, 4, 7, 3 }, { 0, 1, 5, 4 }, { 2, 3, 7, 6 }
  };
  GLdouble color[6][3] = {
    { 1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0 },
    { 1.0, 1.0, 0.0 }, { 0.0, 1.0, 1.0 }, { 1.0, 0.0, 1.0 }
  };
  
  glClear(GL_COLOR_BUFFER_BIT);

  glLoadIdentity();
  gluLookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
  glRotated((GLdouble)r, 0.0, 1.0, 0.0);
  
  glColor3d(0.0, 0.0, 0.0);
  glBegin(GL_QUADS);
  for (j = 0; j < 6; j++) {
    glColor3dv(color[j]);
    for (i = 0; i < 4; i++) {
      glVertex3dv(vertex[face[j][i]]);
    }
  }
  glEnd();

  glutSwapBuffers();

  if (++r >= 360) r = 0;
}

void resize(int w, int h)
{
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(30.0, 1.0, 1.0, 10.0);
  glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
  if (key == '\033') exit(0);
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
  glutCreateWindow(argv[0]);
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutIdleFunc(idle);
  glutReshapeFunc(resize);
  glutMainLoop();
}
/*----------------------------------------*/

なんだかおかしいですね。あまりじっと見つめていると、変な気分になってき そうです ;-p 。これは、面の前後関係を無視して、とにかく重ね書き(塗り) しているために、後ろにあり見えないはずの面でも後で描いたものが見えてし まっているためです。そこで、 Z バッファ (デプスバッファ) というものを 使います。これは、「画面」上の各点に対して、現在までにどのくらいの奥行 き (デプス) の対象が描画されているかを記憶しておくバッファ/手法です。 これにより、今描こうとしている対象よりも「手前」の対象が描画されている 場合は「見えない」と判断し上塗りを行いません。プログラム ogl06.c を、 ogl06.c-2に示すように、2行の修正と2行の追加を行ってください。

Fig.5
/*-ogl06.c-2--------------------------------------*/
:
void display(void)
:
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT /* 修正 */);
  glEnable(GL_DEPTH_TEST); /* 追加 */
:
  glEnd();
  glDisable(GL_DEPTH_TEST); /* 追加 */
  glutSwapBuffers();
:
int main(int argc, char *argv[])
:
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH /* 修正 */);
:
/*----------------------------------------*/

4。シェーディング

4.1 ライティングとマテリアル

実際に存在するものが、なぜ「そのように見える」のかを考えてみましょう。 まず見る人がいるからです (もしかするとカメラかもしれません)。これは既 に考えてきました。視点と視線方向です。そこに物があるから? 確かにそう ですね。物の形状についても扱ってきました。「色」についてはどうでしょう か。前のプログラムでは立方体にカラフルな色を付けてみましたが、単に塗り つぶしただけ、という感じでしたね。色について考える前に、もう一つ大事な もの、光があります。物が見えるためには、物に光があたり、そのうちのいく つかの成分が反射してくることにより、様々な色に見えるのです。物の実際に 見えている「色」を決めるのは、光源(の色、位置、方向など)と物の材質 (マ テリアル) なのです。白い立方体でも光が弱ければ灰色に、あるいは黒っぽく 見えるでしょう。光が緑色ならば、立方体も緑に見えるでしょう。それでは次 のプログラム ogl07.c を実行してみてください。

Fig.6
/*-ogl07.c---------------------------------------*/
#include <GL/glut.h>

void idle(void)
{
  glutPostRedisplay();
}

void display(void)
{
  int i, j;
  static int r = 0;
  GLdouble vertex[8][3] = {
    { 0.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 },
    { 1.0, 1.0, 0.0 }, { 0.0, 1.0, 0.0 },
    { 0.0, 0.0, 1.0 }, { 1.0, 0.0, 1.0 },
    { 1.0, 1.0, 1.0 }, { 0.0, 1.0, 1.0 }
  };
  int face[6][4] = {
    { 0, 3, 2, 1 }, { 1, 2, 6, 5 }, { 4, 5, 6, 7 },
    { 0, 4, 7, 3 }, { 0, 1, 5, 4 }, { 2, 3, 7, 6 }
  };
  GLdouble normal[6][3] = {
    { 0.0, 0.0,-1.0 }, { 1.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0 },
    {-1.0, 0.0, 0.0 }, { 0.0,-1.0, 0.0 }, { 0.0, 1.0, 0.0 }
  };
  GLfloat lpos[4] = {5.0, 5.0, 0.0, 1.0};
  GLfloat lcol[4] = {0.5, 1.0, 0.5, 1.0};
  GLfloat mcol[4] = {1.0, 0.5, 0.5, 1.0};
  
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  
  glLoadIdentity();
  gluLookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
  glRotated((GLdouble)r, 0.0, 1.0, 0.0);
  
  glBegin(GL_QUADS);
  for (j = 0; j < 6; j++) {
    glNormal3dv(normal[j]);
    for (i = 0; i < 4; i++) {
      glVertex3dv(vertex[face[j][i]]);
    }
  }
  glEnd();

  glDisable(GL_LIGHT0);
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);
  glutSwapBuffers();

  if (++r >= 360) r = 0;
}

void resize(int w, int h)
{
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(30.0, 1.0, 1.0, 10.0);
  glMatrixMode(GL_MODELVIEW);
}

void keyboard(unsigned char key, int x, int y)
{
  if (key == '\033') exit(0);
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
  glutCreateWindow(argv[0]);
  glClearColor(1.0, 1.0, 1.0, 0.0);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutIdleFunc(idle);
  glutReshapeFunc(resize);
  glutMainLoop();
}
/*----------------------------------------*/

OpenGL には、いくつかの光源が用意されています。光源の数はシステムによっ て違いがあります。また、光が物体の面(微小平面)にあたった場合、光がどの 方向に反射するかは、その法線の方向により決定されます。そのため、面を描 画する際に法線を指定する必要があります。

まず、くるくる回る立方体を良く見て、どちらから光があたっているか考えて ください。正面手前、あなたの頭のあたりに光源があると思います。では、次 に示す「追加1」の1行を加えてみてください。光源が立方体の右上にあるこ とが分かりますか? 同じように「追加2」で光源の色を緑に変えることがで きます。立方体の材質をとくに指定していない場合は「白」なので、立方体が 緑に見えます。「追加3」では立方体の材質を「赤」に変えることができます。 「追加2」と「追加3」をともに加えた場合は、黄色(やまぶき色 ;-< ?)に見 えるでしょう。

/*-ogl07.c-2--------------------------------------*/
:
void display(void)
:
  gluLookAt(3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
  glLightfv(GL_LIGHT0, GL_POSITION, lpos);		/* 追加1 */
  glLightfv(GL_LIGHT0, GL_DIFFUSE, lcol);		/* 追加2 */
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mcol);	/* 追加3 */
  glRotated((GLdouble)r, 0.0, 1.0, 0.0);
:
/*----------------------------------------*/
void glLightfv(GLenum light, GLenum pname, const GLfloat *params)
光源のパラメータを設定します。最初の引数 light に設定する光源の番号 (GL_LIGHT0〜GL_LIGHTn、n はシステムによって異なります)です。2つ目の 引数 pname は設定するパラメータの種類です。ここに GL_POSITION を指定す ると光源の位置を設定します。また GL_DIFFUSE を指定すると光源の拡散反射 光強度(色)を設定します。最後の引数 params は、pnameに指定したパラメー タの種類に設定する値です。pname が GL_POSITION あるいは GL_DIFFUSE の ときは、params は4つの要素を持つ GLfloat 型の配列で、それぞれ光源の位 置および拡散反射光強度を指定します。光源が (x, y, z) の位置にあるとき、 params の各要素には (x/w, y/w, z/w, w) を設定します。通常 w = 1 として 点光源の位置を設定しますが、w = 0 であれば (x, y, z) 方向の平行光線の 設定になります。また光源の拡散反射光強度が (R, G, B) なら params の各 要素には (R, G, B, 1) を設定します。
void glMaterialfv(GLenum face, GLenum pname, const GLfloat *params)
物体の材質パラメータを設定します。引数 faceには GL_FRONT、GL_BACK およ び GL_FRONT_AND_BACK が指定でき、それぞれ面の表、裏、あるいは両面に材 質パラメータを設定します。設定できる材質 pname には GL_AMBIENT(環境光 に対する反射係数)、GL_DIFFUSE(拡散反射係数)、GL_SPECULAR(鏡面反射 係数)、GL_EMISSION(発光係数)、GL_SHININESS(ハイライトの輝き)、あ るいはGL_AMBIENT_AND_DIFFUSE(拡散反射係数と鏡面反射係数の両方)があり ます。引数 params は1つ(GL_SHININESS)または4つの要素を持つ GLfloat 型の配列で、4つの要素を持つ場合は、色の成分 RGB および A に対する係数 を指定します。

4.2 スムーズシェーディング

前のプログラムでは、一つの面に対して一つの法線を設定していました。平面 には法線は一つしかないから当り前だといわれてしまうと困るのですが、実際 には一度設定した法線が(この場合)4つの頂点に適用されていただけなのです。 数学的な面の法線とは若干意味が違ってきますが、各頂点を指定する場合に別々 の法線を指定しておくことが可能です。すると、その点における「色」は光源 とその法線により決定されます。描かれるポリゴン(面)は、各頂点はそれぞれ 指定された法線情報により見かけの色が決まり、ポリゴンの内部は各頂点の色 を補間した形で埋め尽くされます。局面を多数の面で近似し、適切な法線を与 えて描画することにより、あたかも滑らかな局面のように表示できるのです。

Fig.7

5。さいごにもう少し…

5.1 変換行列の階層構造

これまで詳しく説明せずに、変換行列という言葉を用いてきました。次の式 (行列の積)を見てください。

+  +   +                +   + +
|x'|   | cos(a) sin(a) 0|   |x|
|y'| = |-sin(a) cos(a) 0| * |y|
|z'|   |   0      0    1|   |z|
+  +   +                +   + +

点(x', y', z') は点(x, y, z) を、Z軸回りに角度 a 回転したものです。こ の行列が変換行列です。しかし、1次変換では平行移動が表現できません。そ こで同次座標という座標表現を用います(詳しくは各自勉強してください)。次 式によりベクトル(l, m, n) の平行移動も表現できます。

+  +   +                  +   + +
|x'|   | cos(a) sin(a) 0 l|   |x|
|y'| = |-sin(a) cos(a) 0 m| * |y|
|z'|   |   0      0    1 n|   |z|
|1 |   |   0      0    0 1|   |1|
+  +   +                  +   + +

この行列が変換行列です。そして、オブジェクトの移動だけではなく、視点の 変更や、スクリーンへの投影変換も同様に変換行列で表現されます。視点の設 定をした後に、オブジェクトa を平行移動(変換行列 T) し、オブジェクトb は平行移動(同 T)と回転移動(変換行列 R)、さらにオブジェクトc は回転移動 (変換行列 R2) する場合、各オブジェクトに対してそれぞれ変換行列を求める のではなく、次のように実現します。

  1. 視点情報を含んでいない変換行列の保存
  2. 視点の設定後、変換行列の保存
  3. 平行移動(T)後、オブジェクトa の描画
  4. さらに回転移動(R)後、オブジェクトb の描画
  5. 視点情報を含んだ変換行列を復帰
  6. 改めて回転移動(R2)後、オブジェクトc の描画
  7. 視点情報を含んでいない変換行列を復帰

最初に保存しておき、最後に復帰させるのは、次回の描画のためです。このよ うに階層化することにより、他の物体の上に乗っていて一緒に動くものや、 「肩が動けば当然手の平も動く」状況を容易に表現することが可能です。簡単 なサンプルプログラム ogl-k.cを参考にしてください。 このサンプルでは、前述したスムーズシェーディングによる円柱の描画も行っ ています。プログラムを見ると実際には多角柱であることが分かります、各自 で分割数を変更してみてください。また、マウスボタンでの視点の変更ができ るようにしてあります、GLUT によるマウスの扱い方の参考にしてください。

5.2 本当のさいごに

本講習会では OpenGL の第一歩程度の内容について扱って来ました(もしかす ると「半歩」程度かも知れません :-p)。変換行列の話などからも分かるかと 思いますが、本格的に利用しようとすると、数学的、幾何学的な知識が必要と なってきます。「金属」のような物体、「プラスチック」のような物体を表現 したい場合には、物理学的、光学的な知識が必要となってきます。また、数多 くある OpenGL の機能の1つにテクスチャマッピングがあります、OpenGL 対 応のグラフィックハードウエアがある場合にはその鮮やかさとスピードに驚く ことでしょう(いや、昨今のゲームに慣れた皆さんは、「なんだ、こんなもの か」と思うかも知れませんね ;-<)。以下に、参考となるサイト、書籍をあげ ます、各自で頑張ってみてください。

5.3 参考 URL、書籍

OpenGL, MesaGL:
http://www.ssec.wisc.edu/~brianp/Mesa.html 
http://www.sgi.com/Technology/OpenGL/Windows/index.html
ftp://ftp.microsoft.com/softlib/MSLFILES/opengl95.exe
GLUT:
http://reality.sgi.com/mjk_asd/glut3/glut3.html

------------------------------------------------------------------------
書籍名: OpenGL Reference Manual (日本語版)
	The Official Reference Document for OpenGL, Release 1
著者 : OpenGL ARB (Architecture Review Board)
出版社: 星雲社
価格 : 8,500円
サイズ: B5変形判 388頁
発行 : 1993/11/15 アジソン・ウェスレイ
ISBN : 4-7952-9644-8
http://www.sgi.com/Technology/openGL/opengl.html
http://www.opengl.com/andoh/opengl/openglfaq.html
	OpenGLの公式の関数リファレンス・マニュアル。いわゆる青本。

書籍名: OpenGL Programming Guide (日本語版)
	The Official Guide to Learning OpenGL, Release 1
著者 : OpenGL ARB (Architecture Review Board)
出版社: 星雲社
価格 : 11,000円
サイズ: B5変形判 516頁
発行 : 1993/12/20 アジソン・ウェスレイ
ISBN : 4-7952-9645-6
ftp://ftp.sgi.com/pub/opengl/
	OpenGLの公式のプログラミング・ガイド。いわゆる赤本。

書籍名: OpenGL プログラミング・ガイドブック
著者 : 相川恭寛
出版社: 技術評論社
価格 : 4,300円
サイズ: B5判 399頁
発行 : 1995/12/10
ISBN : 4-7741-0237-7
付録 : CD-ROM
	IRIX用 aux, glut ライブラリ サンプル, モデル & テクスチャデータ
	IRIX用, Windows NT用、参考書。