頂点配列


頂点を配列として保存する

これまでは、glBegin() と glEnd() の間で頂点を指定するとき
glVertex() 関数を用いて、一つひとつの頂点を直接指定しました

しかし、定数を直接ハードコーディングするのは
保守性や整合性を維持するために、できる限り避けるべきであるという大原則があります
とくに、OpenGL の晴れ舞台であるゲームプログラミングでは
定数を描画や制御に関係するコードに書き込まないのは基本中の基本です

そこで、頂点を配列として扱うことができれば、かなりまとまった処理を実現できます
そうすれば、ディスプレイ コールバックでは配列から頂点を選択するだけとなるでしょう
また、複雑なポリゴンでは頂点を共有することも珍しくありません
このような場合にも、配列化された頂点は大いに役に立つはずです

頂点配列を使うためには、配列をセットする必要がありますが
その前に、まずは配列を有効化する必要があります
配列を有効にするには glEnableClientState() 関数を呼び出します
また glDisableClientState() 関数で無効化することも可能です

void glEnableClientState(GLenum array);
void glDisableClientState(GLenum array);

array には、有効化する配列を表す定数を指定します
ここには、次のいずれかの定数を指定することができます

定数対象配列と関連関数
GL_COLOR_ARRAYカラー配列。glColorPointer() を参照
GL_EDGE_FLAG_ARRAYエッジフラグ配列。glEdgeFlagPointer() を参照
GL_INDEX_ARRAY指標配列。glIndexPointer() を参照
GL_NORMAL_ARRAY法線配列。glNormalPointer() を参照
GL_TEXTURE_COORD_ARRAYテクスチャ座標配列。glTexCoordPointer() を参照
GL_VERTEX_ARRAY頂点配列。glVertexPointer() を参照

頂点配列を有効化するには、GL_VERTEX_ARRAY を glEnableClientState() に指定します
なぜ、この定数が glEnable() 関数で有効化することができないかというと
このような配列情報はサーバーではなく、クライアントに保存されるためです

次に、配列データを作成して保存する必要があります
ここで言う「配列」はプログラミング言語の配列ではなく OpenGL における配列です
頂点配列を定義するには glVertexPointer() 関数を使います
void glVertexPointer(
	GLint size , GLenum type , GLsizei stride ,
	GLsizei count, const GLvoid *pointer   
);
size には、頂点データのサイズを指定します
ここで言うサイズは、頂点データが保有する座標情報の数です
2次元であれば 2 を、Z 座標を含めるなら 3 を、w を含めるなら 4 を指定します

type には配列の型を指定します
ここには GL_SHORT、GL_INT、GL_FLOAT、GL_DOUBLE のいずれかを指定してください
これまでのように float 型で頂点データを扱うならば GL_FLOAT を指定します

stride には、連続する頂点間でのバイト・オフセットを指定します
ここに 0 を指定すれば、頂点の情報が配列ないに隙間なく収まっているということをあらわします
pointer には、座標情報を含む配列へのポインタを指定します

これで、頂点配列を定義することができました
あとは、glBegin() と glEnd() の間で、有効な配列から頂点データを取得するだけです
頂点データの書き込みは glArrayElement() 関数で行うことができます

void glArrayElement(GLint index);

index には参照する頂点配列のインデックスを指定します
これで、頂点をインデックス指定で制御することができるようになりました
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat vertex[] = {
	-0.9 , 0.9 , 0.9 , 0.9 , 0 , -0.9
};

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(2 , GL_FLOAT , 0 , vertex);

	glBegin(GL_POLYGON); {
		int i;
		for(i = 0 ; i < 3 ; i++) glArrayElement(i);
	} glEnd();

	glFlush();
}

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;
}
このプログラムは、これまでのように白抜きの三角形を描画するだけですが
重要なのは glBegin() と glEnd() の間です
頂点の指定を glVertex() ではなく glArrayElement() 関数で行っています

頂点配列が定義されていれば、プリミティブのレンダリングも一括することができます
glArrayElement() 関数は、頂点配列からインデックスを指定しましたが
頂点配列のインデックスを指定する指標を使ってプリミティブを指定できないかと考えるでしょう
これを実現するのが glDrawElements() 関数です
void glDrawElements(
	GLenum mode , GLsizei count ,
	GLenum type , const GLvoid *indices
);
mode には glBegin() 関数の引数で指定する、プリミティブの種類を指定します
ここに指定することができる定数は glBegin() 関数を参照してください
count にはレンダリング要素の数を指定します

type には indices の型を指定します
ここには GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT のいずれかを指定できます
例えば GLubyte 型であれば GL_UNSIGNED_BYTE を指定してください

indices にはインデックス指標を保存する配列へのポインタを指定します
関数はこの指標と定義されている配列に基づいて、プリミティブを定義します
glDrawElements() を使えば、glBegin()〜glEnd() を一行にまとめられます
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat vertex[] = {
	-0.9 , 0.9 , 0.9 , 0.9 , 0 , -0.9
};

void disp( void ) {
	GLubyte indices[] = { 0 , 1 , 2 };
	glClear(GL_COLOR_BUFFER_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(2 , GL_FLOAT , 0 , vertex);

	glDrawElements(GL_LINE_LOOP , 3 , GL_UNSIGNED_BYTE , indices);
	glFlush();
}

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;
}
このプログラムは、glBegin()〜glEnd() の代わりに glDrawElemnts() を使っています
glDrawElements() にはインデックス指標となる indices 配列を渡しています
関数はこの指標を元に、定義されている配列を参照してプリミティブを定義します

指標となる indices は、先頭から順に 0、1、2 という値を格納しています
これは、定義されている頂点配列のインデックスを表しています

指標配列を用意することが冗長であるならば glDrawArrays() を使えます
上のプログラムのインデックス指標は単純増加しているだけなので
このような場合は、glDrawArrays() 関数を使って特定範囲の頂点配列を指定することができます

void glDrawArrays(GLenum mode , GLint first , GLsizei count);

mode にはプリミティブの種類を指定します
これもやはり glBegin() 関数で指定する定数のいずれかです
first には、有効な配列内の開始指標を、count にはレンダリングする指標の数を指定します
つまり first から first+count-1 までの配列要素でプリミティブを構築します
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

const GLfloat vertex[] = {
	-0.9 , 0.9 , 0.9 , 0.9 , 0 , -0.9
};

void disp( void ) {
	glClear(GL_COLOR_BUFFER_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(2 , GL_FLOAT , 0 , vertex);

	glDrawArrays(GL_POLYGON , 0 , 3);
	glFlush();
}

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;
}
このプログラムもまた、単純な三角形を描画するだけですが
頂点配列と glDrawArrays() を使ってレンダリングしているところに注目してください



前のページへ戻る次のページへ