OpenGLプログラミングの解説資料 |
ogl01.c などはそのままでは線描画が行われません.実装では描画量が少ない場合はフラッシュしてやらないといけないようです.以下のように追加します.
glBegin(GL_LINE_LOOP);
...
...
glEnd();
glFlush(); <== 追加
この追記をしたプログラムであれば問題なく描画されます.
ogl05.cのサンプルなどは、デジタルラボのコンソールで実行しますと液晶の反応速度が描画速度について行けず、画像が流れ気味になります.ウィンドウを大きくするなりして確認してください.
% gcc filename.c -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm
/*-oglcc---------------------------------------*/
gcc $* -L/usr/X11R6/lib -lglut -lGLU -lGL -lXmu -lXi -lXext -lX11 -lm
/*----------------------------------------*/
% oglcc filename.c
とコンパイルすると、a.out という名前の実行ファイルが作成されるので、
% a.out
で実行してみてください.以下では、表示されたウィンドウがアクティブな状
態で ESC キーを押すと終了するようにしてあります.
詳しい説明は後回しにして、実践あるのみ、まずは何か画面に出してみなけれ ば始まりません.以下のプログラム 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();
glFlush();
}
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 の関数について 説明します.
次に、関数名のプリフィックスが "gl" となっている OpenGL の関数について 説明します.OpenGL はステートマシンと呼ばれ、状態を保持しながら各処理 を行っていきます.具体的には、赤い線を描く場合に「赤色で線を描け」と命 令するのではなく、「これから描く色は赤」と命令しておき、以後「線を描け」 と命令します.
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 凸多角形を描きます.
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) の指定を変更したり、増やしたりしてみてください.
思った通りに表示されましたか?
プログラム 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();
glFlush();
}
/*-以下、ogl01.c と同じ-*/
/*----------------------------------------*/
実行する前にどのように表示されるか考えてみましょう. glBegin(GL_LINE_LOOP); が glBegin(GL_POLYGON); に 変更されています.直線ではなく、多角形 (塗りつぶされている) になること が分かります.次に、各点に色が指定されています.この場合、多角形の内部 は頂点の色から補間した色で塗りつぶされます.言葉で説明しても分からない 場合は、実際に実行してみましょう.
/*-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;
}
/*----------------------------------------*/
/*-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();
glFlush();
}
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-2.c に示すように修正して ください.デフォルトでは視点が原点になり立方体と重なってしまうので、視 点座標系を平行移動し、稜線が重ならないように回転移動しています.うまく 表示できたら、同じように ogl04-3.c に示すように修正してください.今度 は視点、および視線方向を指定することにより立方体らしく見えるようにして います.
/*-ogl04-2.c--------------------------------------*/
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-3.c--------------------------------------*/
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 ライブラリについても説明します.
時間に余裕のある人は、描く図形や視点をうまく変更してみてください. 期待した表示ができましたか?
次に考えなければならないのは、座標変換です.これまでは深く考えずに説明 してきました.例えば、立方体の CG を描く場合には、
「モデリング変換」 立方体の空間内の位置、向きを考える必要があります.立方体を回す場合には「モデリング変換」を変更す れば良いわけです.また、止まっている立方体の周りを (見る側が) 動きたい のならば「ビューイング変換」を変更していけば良いのです.OpenGL では、 「モデル−ビュー変換」と「投影変換」を別々に考えられるようになっていま す.次のプログラム ogl05.c で立方体を回してみてください.
「ビューイング変換」 その空間を見る視点、視線方向
「投影変換」 スクリーンへの投影方法
/*-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();
glFlush();
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();
}
/*----------------------------------------*/
/*-ogl05-2.c--------------------------------------*/
:
void display(void)
:
glEnd();
glFlush();
glutSwapBuffers(); /* 追加 */
if (++r >= 360) r = 0;
:
int main(int argc, char *argv[])
:
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); /* 変更 */
:
/*----------------------------------------*/
さて、また時間に余裕のある人は、
の回転軸を変更してみましょう.うまく回りましたか?
/*-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();
glFlush();
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行の追加を行ってください.
/*-ogl06-2.c--------------------------------------*/
:
void display(void)
:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT /* 修正 */);
glEnable(GL_DEPTH_TEST); /* 追加 */
:
glEnd();
glFlush();
glDisable(GL_DEPTH_TEST); /* 追加 */
glutSwapBuffers();
:
int main(int argc, char *argv[])
:
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH /* 修正 */);
:
/*----------------------------------------*/
/*-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();
glFlush();
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-2.c--------------------------------------*/
:
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);
:
/*----------------------------------------*/
これまで詳しく説明せずに、変換行列という言葉を用いてきました.次の式 (行列の積)を見てください.
+ + + + + +点(x', y', z') は点(x, y, z) を、Z軸回りに角度 a 回転したものです.こ の行列が変換行列です.しかし、1次変換では平行移動が表現できません.そ こで同次座標という座標表現を用います(詳しくは各自勉強してください).次 式によりベクトル(l, m, n) の平行移動も表現できます.
|x'| | cos(a) sin(a) 0| |x|
|y'| = |-sin(a) cos(a) 0| * |y|
|z'| | 0 0 1| |z|
+ + + + + +
+ + + + + +この行列が変換行列です.そして、オブジェクトの移動だけではなく、視点の 変更や、スクリーンへの投影変換も同様に変換行列で表現されます.視点の設 定をした後に、オブジェクトa を平行移動(変換行列 T) し、オブジェクトb は平行移動(同 T)と回転移動(変換行列 R)、さらにオブジェクトc は回転移動 (変換行列 R2) する場合、各オブジェクトに対してそれぞれ変換行列を求める のではなく、次のように実現します.
|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|
+ + + + + +
GLUT/freeglutによるOpenGL入門 工学社 税込2160円CG・画像処理の入門的な本(教科書的な本): ビジュアル情報処理−CG・画像処理入門− CG−ARTS協会 (画像情報教育振興協会) 税込3132円