シミュレーション屋のためのOpenGL入門

プログラミング

1999.8.29

目次

1. プログラム例

2. プログラムの解説

3. コンパイルと実行方法


1. プログラム例

プログラム例を使ってOpenGLによるプログラムの流れを見てみます。 プログラムは少々長いのでこちら(プログラム例) に置きました。

このプログラムは大きさ3.0の正八面体が x=-4.0〜4.0、y=-3.2〜3.2、z=-5.0〜5.0の範囲をくるくる回りながら動き、 境界に達すると移動方向、回転方向を逆にするというものです。

実行結果をMPEGにした物をここに置きました。 実際のプログラムは無限ループですが、MPEGアニメーションは1000ステップで切っています。 このMPEGアニメーションはあまり綺麗でないです。 本当はこんな絵が描かれます。 なお、実行画面をファイルに落す方法はこちらにあります。

[この章の目次に戻る]

2. プログラムの解説

OpenGLによるプログラミング方法と命令を簡単に解説してみました。 私が必要と思われる最小限のことだけを書いた上に 意訳している部分が多いので本来の意味は「青本」(OpenGL Reference Manual) やマニュアルのウェブサイトを見て調べて下さい。 何が書いてあるのか意味不明なところも多いですが、 一度はこれらを読んでみることをお勧めします。

[この章の目次に戻る]

2.1 プログラムの流れ

OpenGLによるプログラミングはほとんどほぼ同じ手順で行えます。 私の書いた簡単(でもないですが)な例を見ながらまずプログラムの流れを掴ん で下さい。

まず、main文から見て行きます。 OpenGLに関する命令は
110:  glutInit(&argc, argv);
111:  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
112:  glutInitWindowPosition(0, 0);
113:  glutInitWindowSize(WSIZE_X, WSIZE_Y);
114:  glutCreateWindow(argv[0]);
115:  myInit();
116:
117:  glutDisplayFunc(myDisplay);
118:  glutReshapeFunc(myReshape);
119:  glutIdleFunc(myIdle);
120:  glutMainLoop();
です。

glutで始まる関数はOpenGL(GLUT)で用意されているものです。 OpenGLで提供されている関数はgl、glu、glutで始まり、 定数はGL_で始まり、変数型はGLで始まります。

では、110行目から順に見て行きましょう。
110:  glutInit(&argc, argv);
111:  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
「おまじない」と思って下さい。

112:  glutInitWindowPosition(0, 0);
113:  glutInitWindowSize(WSIZE_X, WSIZE_Y);
114:  glutCreateWindow(argv[0]);
ウィンドウに関する命令です。 画面の左上(0, 0)に幅WSIZE_X、高さWSIZE_Yのウィンドウを開きます。 このウィンドウ内にOpenGLコマンドによって描画されます。

115:  myInit();
画面描写に関するイニシャライズをしています。 もちろん、OpenGLにはmyInit()という関数はありません。 プログラムリストの22行目〜50行目がmyInit()です。 主に光源に関する設定とどの範囲をどの方向から見るかを設定しています(以下の章を参照して下さい)。

117:  glutDisplayFunc(myDisplay);
118:  glutReshapeFunc(myReshape);
119:  glutIdleFunc(myIdle);
120:  glutMainLoop();
この4つの関数はこの順番で実行されるというものではありません。 あるイベントが起こったときにどの関数が実行されるかを定義しています。
glutDisplayFunc(myDisplay);
は描画イベントが起こったときに呼び出される 関数がmyDisplay()であることを意味しています。
myDisplay()は正八面体を描いている部分です。 glClear()で画面を消去し、glMaterialfv()で物体の色を決めています。 glPushMatrix()は今の座標を記憶させています。 次に、物体の位置を決めます。 glTranslatef()で平行移動、glRotatef()で回転します。 glutSolidSphere()は本来は球を描く命令なのですが、球の近似精度を落して (経度方向に4分割、緯度方向に2分割)八面体を描くのに使いました。 glPopMatrix()で元の座標を思い出してもらいます。 そして、最期にglutSwapBuffers()で実際に画面への描画を行っています。 この命令(本当はglFlush()。glutSwapBuffers()は内部でglFlush()を呼んでい ます)があるまでは画面の更新は行われません。
glutReshapeFunc(myReshape); 
は再描画イベントが起こったときに呼び出され る関数がmyReshape()であることを意味しています。 マウスでウィンドウのサイズが変えられたりするとmyReshape()が呼び出され る訳です。
このプログラムでやっていることはまずコンピュー タのウィンドウのサイズをglViewport()によって設定しています。 次に、計算空間の方で表示される範囲をglOrtho()によって設定し、それを見 る方向をgluLookAt()で設定しています。 glMatrixMode()は描画する物体の座標系で考えているのか(GL_MODELVIEW)、座 標系そのものを扱っているのか(GL_PROJECTION)を決めるものです。
glutIdelFunc(myIdle); 
は何もイベントが起こっていないときに呼び出される 関数がmyIdle()であることを意味しています。 つまり、通常の状態ではmyIdle()が繰り返し呼び出されていることになります。
myIdle()では正八面体の中心座標値が境界に達して いたら増分を負にし、さらに回転方向も逆にしています。 ここでmyDisplay()を呼び出し、正八面体の描画を行っています。 最期に、正八面体の座標値の増減、回転を行っています。
glutMainLoop();
の場所で上で行った定義に基づいてイベントが起こるのを待ち続けます。

OpenGLプログラムの全体の流れは理解できたでしょうか? ここで簡単にプログラムの命令も説明しましたが、以下の節でもう少し詳しく 解説します。

[この章の目次に戻る]

2.2 どの範囲をどこから見るか

これはものすごく重要であるのにも関わらず、どういう訳だか解説本ではあま りきちんと説明がなされていません。
glMatrixMode(GL_PROJECTION);
glOrtho(left, right, bottom, top, near, far);
gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz);
の順番で実行します(glOrtho()とgluLookAt()の順番を換えてはいけません)。 glOrtho()、gluLookAt()の引数はすべてGLdoubleです。
先にgluLookAt()の説明をします。 (eyex, eyey, eyez)の座標から(centerx, centery, centerz)の方向を見ます。 (centerx, centery, centerz)が画面の中心となります。 (upx, upy, upz)は画面上で上となる方向です。 (centerx-eyex, centery-eyey, centerx-eyez)と (upx, upy, upz)が直交していなくても良いかどうかは不明です。
次にglOrtho()について説明します。 glOrtho()は描画する範囲を決めます。 gluLookAt()で定義した視線の方向に沿って幅(right - left)、 高さ(top - bottom)、奥行き(far - near)の直方体が表示される範囲となります。 gluLookAt()で定義した(centerx, centery, centerz)は near の面の中心となります。 つまり、(centerx, centery, centerz)を中心に幅±(right - left)/2、 高さ±(top - bottom)/2、奥行き(far - near)の範囲が描かれます。
注:glOrtho()は絶対座標で(left, bottom, near)〜 (right, top, far)の範囲を表示するという意味ではありません。

glOrtho();とgluLookAt();はイニシャライズ関数とglutReshapeFunc()で呼び出される関数との両方で実行するようにしましょう。

[この章の目次に戻る]

2.3 光

例ではmyInit();で光(ライト)に関する定義をしています。 光に関しては
glLightfv(light, pname, param);
で定義します。
lightはライトの番号を指定します。 GL_LIGHT0、GL_LIGHT1、...、GL_LIGHT7と指定します(指定できる最大の数は動作環境によって違いますが、最低でも0〜7の8個は利用できます)。

pnameは光源パラメータを指定します。色々と指定できますが、
pname内容param
GL_AMBIENT環境光RGBA
GL_DIFFUSE拡散光RGBA
GL_SPECULAR鏡面光RGBA
GL_POSITIONライトの位置XYZW
だけ指定すれば十分でしょう。
「環境光」とは光の乱反射して来た成分と思えば良いでしょう。 光が当たっていようがいまいがこの環境光によって照らされます。
「拡散光」とは物体からの散乱光です。 物体に光が当たっていればあらゆる方向に拡散する光です。
「鏡面光」とは物体に当たって直接反射して来る光の成分です。

この4つの光源パラメータに対応するparamはGLfloat型の4成分ベクトル(配列)です。 環境光、拡散光、鏡面光はRGBAで指定します。 RGBAは赤、緑、青、アルファ値で、0.0〜1.0の値をとります。 アルファ値を指定する必要がなければ1.0にしておけば良いでしょう。
色は配列変数を使わなくても、GLfloat型以外でも指定できます(
その場合、glMaterialに続く2文字が変わります)。
ライトの色は白またはグレイにしておけば良いでしょう。

ライトの位置は4番目の値が0.0以外ならば最初の3成分はライトの座標を指定します。 最期の値が0.0ならば最初の3成分の方向からの平行光線となります。

最期に
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
を実行してライティングをオンにし、LIGHT0を有効にします。

[この章の目次に戻る]

2.4 物の色

glMaterixfv(face, pname, param);

faceは物体の面を指定します。 GL_FRONTが表、GL_BACKが裏、GL_FRONT_AND_BACKが両面です。

pnameは光源パラメータを指定します。色々と指定できますが、 GL_AMBIENT_AND_DIFFUSEで環境光と拡散光による色をまとめて指定してしまえば良いでしょう。
他に、GL_AMBIENT(環境光)、GL_DIFFUSE(拡散光)、GL_SPECULAR(鏡面光) 、GL_EMISSION(放射光)、GL_SHININESS(鏡面光の指数)、GL_COLOR_INDEXES(環境光、拡散光、鏡面光の色指標)が指定できます。

paramはRGBAで記述した配列変数(GLfloat型)です。

[この章の目次に戻る]

2.5 物体の描画

ここに挙げた例ではglutで用意されている球を描く関数を使わせてもらいましたが、 OpenGLには線や面を描く命令が用意されています。 ここでは面を描く命令の説明をします。 線を描く命令もありますが、 3次元では線は意味をなさないのでここでは説明しません。

(x0, y0, z0)、(x1, y1, z1)、(x2, y2, z2)の3点を通り、 法線ベクトルが(nx, ny, nz)の三角形を描く場合は、
glBegin(GL_TRIANGLES);
  glNormal3f(nx, ny, nz);
  glVertex3f(x0, y0, z0);
  glVertex3f(x1, y1, z1);
  glVertex3f(x2, y2, z2);
glEnd();
とします。 glBegin(GL_TRIANGLES);とglEnd();の間に法線ベクトルと頂点座標を指定する命令を書きます。 glNormal3f(nx, ny, nz);が法線ベクトルの指定です。 glVertex3f(x, y, z);が頂点の指定です。

四角形の場合はglBegin(GL_QUADS);になります。 また、glVertex3f()の数が4つになります。
4つの頂点からどうやって1つの平面を描いているのか謎です。 暇な方はソースを読んで下さい。

[この章の目次に戻る]

2.6 物体の位置

決まった場所に描いた物体を別の場所に移動させたり、 拡大・縮小することができます。 glTranslatef()、glRotatef()、glScale()がその命令です。

glTranslatef(x, y, z);
x、y、z方向にそれぞれx、y、z平行移動します。

glRotatef(θ, x, y, z);
原点(0, 0, 0)と(x, y, z)を結んだ方向を軸とし、θ度回転します。

glScale(x, y, z);
x、y、z方向にそれぞれx、y、z倍します。

これらの命令は重ね合わせることができます。 例えば、
glRotatef(30.0, 0.0, 0.0, 1.0);
glTranslatef(1.0, 2.0, 3.0);
gltranslatef(4.0, 5.0, 6.0);
とすると、まず、物体をz軸の回りに30.0度回転させ、 x方向に1.0+4.0=5.0、y方向に2.0+5.0=7.0、z方向に3.0+6.0=9.0移動させます。

これらの命令は普通
glPushMatrix();
(座標を保存)と
glPopMatrix();
(保存していた座標に戻す)の間に挟んで使用します。

[この章の目次に戻る]

3. コンパイルと実行方法

以下のようなMakefileを作ってmakeすると簡単です。
CC = cc
CFLAGS = -lm
INCDIR = /usr/local/include
LIBDIR = /usr/local/lib
XLIBDIR = /usr/X11R6/lib
XLIBS = -L$(XLIBDIR) -lX11 -lXext -lXmu -lXt -lXi -lSM -lICE
GL_LIBS = -L$(LIBDIR) -lglut -lMesaGLU -lMesaGL $(XLIBS)
TARGET = prg

.c:
        $(CC) -o $(TARGET) -I$(INCDIR) $(CFLAGS) $< $(GL_LIBS)

all: $(TARGET)
プログラムのソースファイルの名前はprg.cとしています。 また、ライブラリの場所や名前はシステムに合わせて書き換える必要があります。
注:$(CC)の前の8個のスペースはタブです。 スペースではエラーになります。

[この章の目次に戻る]
[全体の目次に戻る]


岩瀬康行( iwase@sci.hiroshima-u.ac.jp)