ポリゴンの表示
幾何学の世界へ
さて、いよいよ OpenGL を使って図を表示するプログラムを書いてみたいと思います
そこで、多くの 3D プログラマを悩ませるのが幾何学/Geometryの存在です
3次元空間のオブジェクトを扱うためには、幾何学の知識がどうしても必要になります
これを理解できなければ「なぜそうなるのか」という本質を理解することができません
まず、3次元の座標において、2次元と大きく異なるのは視点です
コンピュータの2次元座標は、左上、または左下が (0 , 0) となりました
しかし、3次元の世界では視点そのものが移動できてしまいます
たとえ被写体が動かなくても、カメラを移動させれば移るものが変化するということです
そのため、OpenGL の座標は柔軟であり、コマンドによって変化させることができます
従来の2次元座標のように座標系を設定するようなことも可能です
この方法は、後ほど詳しく解説しましょう
極めて重要なことですが、OpenGL は内部で4つの座標情報から位置を割り出します
4つの情報とは、X 座標、Y 座標、Z 座標、そして W からなる (x, y, z, w) で構成されます
これを 同次座標 と呼びます
OpenGL は3次元射影による幾何学の同次座標内で動作しているのです
線分やポリゴンは、複数の頂点/Vertexからなり、頂点は同次座標で位置を表します
3次元のユークリッド空間(x, y, z)T は、同次頂点(x, y, z, 1.0)Tになり
2次元のユークリッド空間(x, y)T は、(x, y, 0.0 , 1.0)Tとなります
w が 0 以外であれば、同次座標は3次元の点 (x/w, y/w, z/w)T に対応します
例えば、同次座標(0.5, 1, 0, 0.1)はユークリッド点(5 , 10)に対応しています
つまり、3次元の頂点では w を 1 に、2次元では z を 0 に、w を 1 に指定します
OpenGL では、2次元でも3次元でも、内部的にはすべての頂点が3次元であると仮定します
OpenGL における描画は、幾何学的プリミティブの頂点郡として行われます
プリミティブとは、点、線、ポリゴン、ビットマップ、画像のいずれかを指します
幾何学的プリミティブは、幾何学的オブジェクトと考えても間違いではないでしょう
OpenGL で描画を行うには、まず、このプリミティブを定義しなければなりません
ただし、プリミティブは頂点情報にすぎないということも意識してください
プリミティブの定義が描画とイコールするわけではありません
OpenGL は最終的にプリミティブを2次元の画像に変換しなければなりません
頂点情報は3次元ですが、コンピュータがこれを表示するデバイスは2次元だからです
(SF のような3次元ディスプレイが存在すれば、変換作業は必要ないかもしれない)
この、プリミティブを画像に変換する処理をレンダリングと呼んでいます
幾何学的プリミティブを記述するためには、何はともあれ頂点を指定しなければなりません
頂点データを指定するには、最初に glBegin() 関数を呼び出します
void glBegin(GLenum mode);
mode には、頂点で構成されるプリミティブの形式を指定します
これは、次のいずれかの定数を指定しなければなりません
定数 | 解説 |
GL_POINTS | 各頂点を単独の点として扱う 頂点 n は、点 n を意味し n この点が描画される |
GL_LINES | 2つの頂点をペアとし、それぞれのペアを独立した線分として扱う |
GL_LINE_STRIP | 最初の頂点から最後の頂点まで、線分を連結して描画する |
GL_LINE_LOOP | すべての頂点を線分で連結する |
GL_TRIANGLES | 3つの頂点をペアとし、それぞれ独立した三角形として扱う |
GL_TRIANGLE_STRIP | 連結した三角形のグループを描画する
|
GL_TRIANGLE_FAN | 最初の頂点を軸に、連結した三角形のグループを描画する |
GL_QUADS | 4つの頂点をペアとし、それぞれ独立した四角形として扱う |
GL_QUAD_STRIP | 連結した四角形のグループを描画する |
GL_POLYGON | 単独の凸ポリゴンを描画する |
glBegin() 関数を使った後、頂点を指定するいくつかの関数を使うことができます
頂点を記述する処理が終了すれば glEnd() 関数を使って終了します
void glEnd(void);
これらの処理はワンセットなので、覚えておきましょう
頂点を記述するには glVertex() 関数を使います
void glVertex2d(GLdouble x , GLdouble y);
void glVertex2f(GLfloat x , GLfloat y);
void glVertex2i(GLint x , GLint y);
void glVertex2s(GLshort x , GLshort y);
void glVertex3d(GLdouble x , GLdouble y , GLdouble z);
void glVertex3f(GLfloat x , GLfloat y , GLfloat z);
void glVertex3i(GLint x , GLint y , GLint z);
void glVertex3s(GLshort x, GLshort y, GLshort z);
void glVertex4d(
GLdouble x, GLdouble y,
GLdouble z, GLdouble w
);
void glVertex4f(
GLfloat x, GLfloat y,
GLfloat z, GLfloat w
);
void glVertex4i(
GLint x, GLint y,
GLint z, GLint w
);
void glVertex4s(
GLshort x, GLshort y,
GLshort z, GLshort w
);
void glVertex2dv(const GLdouble *v);
void glVertex2fv(const GLfloat *v);
void glVertex2iv(const GLint *v);
void glVertex2sv(const GLshort *v);
void glVertex3dv(const GLdouble *v);
void glVertex3fv(const GLfloat *v);
void glVertex3iv(const GLint *v);
void glVertex3sv(const GLshort *v);
void glVertex4dv(const GLdouble *v);
void glVertex4fv(const GLfloat *v);
void glVertex4iv(const GLint *v);
void glVertex4sv(const GLshort *v);
x、y、z、w には、それぞれ頂点の位置を表す座標を指定します
z を省略した場合は 0.0 が、w を省略した場合は 1.0 が設定されます
v には、頂点の位置をあらわす配列へのポインタを指定します
例えば glVertex2iv() であれば x と y 座標の2つの要素を保有する int 型へのポインタです
さて、ここで OpenGL のコマンドに対する命名規則を解説しましょう
上記したように、glVertex() は引数型によって様々にオーバーロードされています
C 言語では同名の関数を宣言することができないため OpenGL では関数名に接尾子を設けています
glVertex の直後の数字は引数の数を表し、その後の文字は引数型を表しています
引数型は次のような関係になっています
接尾子 | 対応する型 |
b | GLbyte |
ub | GLubyte, GLboolean |
s | GLshort |
us | GLushort |
i | GLint, GLsizei |
ui | GLuint, GLenum, GLbitfield |
f | GLfloat, GLclampf |
d | GLdouble, GLclampd |
glVertex() 関数を使って頂点を設定する場合
必ず glBegin() 関数の後、glEnd() 関数の前に glVertex() 関数を呼び出さなければなりません
それ以外の位置で glVertex() 関数を呼び出しても、動作は保障されません
座標は、画面中央を (0 , 0) とするワールド座標系となっています
これは、カメラのレンズから見える世界を想像するとわかりやすいでしょう
また、何らかの頂点を原点にする座標系を、ワールド座標に対してモデル座標と呼びます
視点や座標変換については、これから少しずつ解説していきます
デフォルトでは、カメラは原点に配置され、負の z 軸方向を指しています
2次元グラフィックスと異なり、どんなに大きなオブジェクトを描画しても
カメラを引いて遠くから見れば小さくなることを忘れないで下さい
逆に、どんなに小さなものでも近くで見れば大きく見えます
最初の視点では、X、Y座標共に ±1 で画面から外に出てしまいます
つまり、左上隅は(-1, 1, 0, 1)、右下隅は (1, -1, 0, 1) となります
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>
void disp(void ) {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glVertex2f(0 , 0);
glVertex2f(-1 , 0.9);
glVertex2f(1 , 0.9);
glVertex2f(0 , 0);
glVertex2f(-1 , -0.9);
glVertex2f(1 , -0.9);
glEnd();
}
int main(int argc , char ** argv) {
glutInit(&argc , argv);
glutInitWindowPosition(100 , 50);
glutInitWindowSize(500 , 500);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
glutCreateWindow("Kitty on your lap");
glutDisplayFunc(disp);
glutMainLoop();
return 0;
}
このプログラムでは、glBegin() 関数から glEnd() 関数までの間に
glVertex() 関数を使って2次元の三角形を2つ表示しています
glBegin() 関数では GL_TRIANGLES を指定しため、プリミティブは三角形となります
3つの頂点を1組に、現在設定されている色で頂点が保存されます
glClear() 関数でカラーバッファを削除していますが、glClearColor() は使っていません
デフォルトでは、glClear() 関数は黒色でバッファを塗りつぶします
逆に、頂点のデフォルトカラーは白です
そのため、白い三角形が黒い背景に描画されているのです
このプログラムでは、3次元のワールドをカメラから除いていると思ってください
ウィンドウのサイズが変更されても、見えるものが変わることはありません
頂点の色は glColor() 関数で設定することが可能です
この関数も,多くの型を受け取ることができるように設計されています
void glColor3b(GLbyte red, GLbyte green, GLbyte blue);
void glColor3d(GLdouble red, GLdouble green, GLdouble blue);
void glColor3f(GLfloat red, GLfloat green, GLfloat blue);
void glColor3i(GLint red, GLint green, GLint blue);
void glColor3s(GLshort red, GLshort green, GLshort blue);
void glColor3ub(GLubyte red, GLubyte green, GLubyte blue);
void glColor3ui(GLuint red, GLuint green, GLuint blue);
void glColor3us(GLushort red, GLushort green, GLushort blue);
void glColor4b(
GLbyte red, GLbyte green ,
GLbyte blue , GLbyte alpha
);
void glColor4d(
GLdouble red, GLdouble green,
GLdouble blue, GLdouble alpha
);
void glColor4f(
GLfloat red, GLfloat green,
GLfloat blue, GLfloat alpha
);
void glColor4i(
GLint red, GLint green,
GLint blue, GLint alpha
);
void glColor4s(
GLshort red, GLshort green,
GLshort blue, GLshort alpha
);
void glColor4ub(
GLubyte red, GLubyte green,
GLubyte blue, GLubyte alpha
);
void glColor4ui(
GLuint red, GLuint green,
GLuint blue, GLuint alpha
);
void glColor4us(
GLushort red, GLushort green,
GLushort blue, GLushort alpha
);
void glColor3bv(const GLbyte *v);
void glColor3dv(const GLdouble *v);
void glColor3fv(const GLfloat *v);
void glColor3iv(const GLint *v);
void glColor3sv(const GLshort *v);
void glColor3ubv(const GLubyte *v);
void glColor3uiv(const GLuint *v);
void glColor3usv(const GLushort *v);
void glColor4bv(const GLbyte *v);
void glColor4dv(const GLdouble *v);
void glColor4fv(const GLfloat *v);
void glColor4iv(const GLint *v);
void glColor4sv(const GLshort *v);
void glColor4ubv(const GLubyte *v);
void glColor4uiv(const GLuint *v);
void glColor4usv(const GLushort *v);
red は赤、green は緑、blue は青要素を表す RGB の色値を指定します
alpha は、現在の色に対する新規のアルファ値を指定します
v は、それぞれの色の値を含む配列へのポインタを指定します
色を表すには様々な方を使えますが、浮動少数なら 0 〜 1 までのパーセントで色の強さを表し
整数型であれば、その整数型の最大値が最も色が強い状態を表しています
色が設定されれば、その後に記述された頂点の色は現在設定されている色となります
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>
void disp( void ) {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3ub(0xFF , 0 , 0);
glVertex2f(0 , 0);
glColor3f(0 , 0 , 1);
glVertex2f(-1 , 0.9);
glVertex2f(1 , 0.9);
glColor3i(2147483647 , 0 , 0);
glVertex2f(0 , 0);
glColor3b(0 , 127 , 0);
glVertex2f(-1 , -0.9);
glVertex2f(1 , -0.9);
glEnd();
}
int main(int argc , char ** argv) {
glutInit(&argc , argv);
glutInitWindowPosition(100 , 50);
glutInitWindowSize(500 , 500);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
glutCreateWindow("Kitty on your lap");
glutDisplayFunc(disp);
glutMainLoop();
return 0;
}
このプログラムでは、頂点に様々な色を設定しています
例題として、glColor() 関数の引数型も、様々なものを利用しています
多くの場合、float 型の浮動少数か、unsigned char の整数型を使うべきでしょう
3D プログラミングでは、色も従来の2次元とは性質が異なってきます
3次元において、照明やオブジェクトの材質によって反射する色が変化するためです
これも、詳しくは後ほど解説していくことにしましょう
因みに、方形は使用頻度が高いため glRect() 関数としてまとめられています
GL_POLYGON や GL_QUADS を使えば、同様の処理が実現できますが
これらをまとめて glRect() 関数が行ってくれます
void glRectd(
GLdouble x1, GLdouble y1,
GLdouble x2, GLdouble y2
);
void glRectf(
GLfloat x1, GLfloat y1,
GLfloat x2, GLfloat y2
);
void glRecti(
GLint x1, GLint y1,
GLint x2, GLint y2
);
void glRects(
GLshort x1, GLshort y1,
GLshort x2, GLshort y2
);
void glRectdv(const GLdouble *v1, const GLdouble *v2);
void glRectfv(const GLfloat *v1, const GLfloat *v2);
void glRectiv(const GLint *v1, const GLint *v2);
void glRectsv(const GLshort *v1, const GLshort *v2);
x1 と y1 には、方形の頂点のひとつとなる X 座標と Y 座標を
x2 と y2 には、方形の対角の頂点を指定します
v1 には頂点のひとつの X と Y 座標を保持する配列へのポインタを
v2 には、方形の対角の頂点へのポインタをそれぞれ指定します
glRect(x1 , y1 , x2 , y2) は、次のコマンド群と等しいと考えることができます
glBegin(GL_POLYGON);
glVertex2(x1 , y1);
glVertex2(x2 , y1);
glVertex2(x2 , y2);
glVertex2(x1 , y2);
glEnd();
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>
void disp( void ) {
glClearColor(1 , 1 , 1 , 0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0 , 0 , 1);
glRectf(-0.8 , 0.8 , 0.8 , -0.8);
}
int main(int argc , char ** argv) {
glutInit(&argc , argv);
glutInitWindowPosition(100 , 50);
glutInitWindowSize(400 , 300);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
glutCreateWindow("Kitty on your lap");
glutDisplayFunc(disp);
glutMainLoop();
return 0;
}
このプログラムでは glRect() 関数を使って方形を描画しています
これは glVertex() 関数でも実現できますが、このほうがスマートです