Vertex Buffer Object(VBO)について

VBOとは

OpenGL - Vertex arrayはOpenGLのAPIの呼び出しを減らすことで, パフォーマンスを大きく改善することができるものの, 頂点データは描画呼び出し毎にクライアント側のメモリからサーバ(GPU)側のメモリ(ビデオカードのメモリ)に転送されるという問題もあります. 一方,GPU側で完全に格納されたデータで呼び出しのたびに実行されるものとしてディスプレイリストがあります. ディスプレイリストではAPI呼び出しのオーバーヘッドをなくすことで高速な描画を可能としています. しかし,ディスプレイリストは一度コンパイルされれば,ディスプレイリスト内のデータを変更することはできません.

これらの問題を解決するのが Vertex Buffer Object(VBO) です. VBOは頂点データを格納するGPU側のメモリに置かれたバッファです. クライアント側のメモリ上の配列と同じように扱え, glVertexPointerなどに直接渡すことができます. また,ディスプレイリストと違い,クライアント側のメモリスペースにバッファをマッピングすれば, クライアントからの読込みや変更が可能となります.

VBO作成手順

VBOの作成にはGL拡張をいくつか使います. GLEWなどをインクルード,リンクするようにしてください(参照:GLEWについて). VBOの作成はテクスチャを作成するのと同じような手順です.

  1. glGenBuffersでバッファ生成
    void glGenBuffers(GLsizei n, GLuint *buffers);
    nに生成する数を指定し,bufferに生成したVBO名が返ってくる.
  2. glBindBufferでバッファをバインド
    void glBindBuffer(GLenum target, GLuint buffer);
    targetにはVBOに何のデータ(頂点,インデックス,ピクセル)を格納されているのかを指定する (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER). bufferはVBO名.
  3. glBufferDataでクライアントからバッファを転送
    void glBufferData(GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage);
    targetは2と同じで,sizeには転送するサイズ,dataにはクライアントのデータ領域のポインタを指定する. もしdataにNULLが指定されたらデータは転送されない.usageは格納されたデータの使われ方で, GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY, GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY, GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, GL_DYNAMIC_COPY のどれかを指定する.
    • STATIC : VBOデータ変更なし.(modified once and used many times)
    • DYNAMIC : VBOデータが頻繁に変更される.(modified repeatedly and used many times)
    • STREAM : VBOデータが毎フレーム変更される.(modified once and used once or a few times)
    • DRAW : VBOデータはアプリケーションから転送され,OpenGL描画に用いられる.(application -> GL)
    • READ : VBOデータはGLから転送され,アプリケーションで読み込まれる.(GL -> application)
    • COPY : VBOデータはGLから転送され,OpenGL描画に用いられる.(GL -> GL)

他に範囲データ転送やVBO破棄命令などがある.

  • glBufferSubData
    void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data);
  • glDeleteBuffers
    void glDeleteBuffers(GLsizei n, const GLuint * buffers);

VBOを使った描画例

まず,VBOを作成します.

// VBO作成
	vector<Vec3> vertices;// = { 0, 0, 0,  1, 0, 0,  1, 1, 0,  0, 1, 0 };
	vertices.push_back(Vec3(0, 0, 0));
	vertices.push_back(Vec3(1, 0, 0));
	vertices.push_back(Vec3(1, 1, 0));
	vertices.push_back(Vec3(0, 1, 0));
	vector<int> indices;// = { 0, 1, 2,  0, 2, 3 };
	indices.push_back(0);
	indices.push_back(1);
	indices.push_back(2);

	indices.push_back(0);
	indices.push_back(2);
	indices.push_back(3);


	glGenBuffers(1, &vrtVBO);
	glBindBuffer(GL_ARRAY_BUFFER, vrtVBO);
	glBufferData(GL_ARRAY_BUFFER, vertices.size()*3*sizeof(double), (GLdouble*)&vertices[0], GL_STATIC_DRAW);

	glGenBuffers(1, &idxVBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idxVBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size()*sizeof(int), &indices[0], GL_STATIC_DRAW);

この処理は頂点,インデックスデータが変わらなければ1回だけでいいです.

次に描画時には,

glBindBuffer(GL_ARRAY_BUFFER, vrtVBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idxVBO);

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_DOUBLE, 0, 0);

	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

	glDisableClientState(GL_VERTEX_ARRAY); 

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

glBindBufferでVBOをバインドして,あとはVertex arrayと同じですが, データの場所のポインタには0を指定します(オフセット値0ということ).

使い終わったら削除します.

glDeleteBuffers(1, &vrtVBO);
	glDeleteBuffers(1, &idxVBO);

VBOデータの変更

VBOはクライアント側のメモリスペースにバッファをマッピングすれば,データの修正が可能となります. データの修正は以下の手順で行います.

  • glBindBufferで修正したいバッファをバインド
  • glMapBufferで修正したいバッファのポインタを取得
    void* glMapBuffer(GLenum target, GLenum access);
    targetはglBindBufferで指定したものと同じ,accessはバッファオブジェクトをマップしたデータポインタの アクセス権限を指定する(GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE). 返値でデータへのポインタを取得する.もしバッファオブジェクトのデータストアをマップできなければ,NULLが返ってくる.
  • データを修正
  • glUnmapBufferでデータをアンマップする.
    GLboolean glUnmapBuffer(GLenum target);
    マップしたバッファオブジェクトが使用される前にアンマップしなければならない.

データの修正の例は以下.

void ModifyVBO(void)
{
	glBindBuffer(GL_ARRAY_BUFFER, vrtVBO);
	double *ptr = (double*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

	static double ang = 0.0;

	if(ptr){
		for(int i = 1; i <= 2; ++i){
			ptr[3*i]   = cos(RX_TO_RADIANS(ang));
			ptr[3*i+2] = sin(RX_TO_RADIANS(ang));
		}
		glUnmapBuffer(GL_ARRAY_BUFFER);

		ang = (ang < 360) ? ang+1 : 0.0;
	}

	glBindBuffer(GL_ARRAY_BUFFER, 0);
}


トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2024-03-08 (金) 18:06:05