※2010/11/22 Bugfix サンプルプログラムを修正しました。

頂点バッファオブジェクトを使ってみる!★ 1.はじめに…
今回は,頂点バッファオブジェクトについて調べて,試してみました。
※2010/11/22 Bugfix サンプルプログラムを修正しました。 ![]() ★ 2.頂点バッファオブジェクト
いままでは,glVertex3fv()とか使ってfor文でぐるんぐるん回しながら描画していたわけですが,さすがに面倒くさくなってきました。またパフォーマンスを考えるとあんまし実用的なやり方では内容に思えます。
DirectXなんかでは頂点バッファを使って描画するのが当たり前なのですが,実はOpenGLでも同じようなことができるようです。 そんなわけで,頂点バッファオブジェクト(VBO : Vertex Buffer Object)を使ってDirectXライクに,描画してみます。OpenGL 1.5からBuffer Objectstというのが使えるようになりました。これは,色々な配列をグラフィックスメモリに置いてやって効率を上げてみましょうというものだそうです。 頂点配列を使って描画する場合はデータがクライアントメモリ上にあるため,PCI Expressなどを通してビデオメモリに転送しなければなりませんが,VBOはビデオメモリにデータを置くのでデータを転送する必要がないそうで す。とくに膨大なポリゴンデータを扱う場合などはパフォーマンスの向上が期待できます。 VBOについてはすでにImagireさんのサイトで取り扱われているのですが,インデックスバッファを使っているようではないので,今回はインデックスバッファが使えるかどうかを試してみました。 実装の仕方についてですが,結構手探りでプログラムを組んだのであっているかどうかわかりません。一応glutReportErrors()関数でエラーが引っかからなかったので大丈夫だとは思いますが・・・正直あっているかどうかは自信がありません。 まずは,変数を用意しておきます
00045: GLuint vertexBuffer;
00046: GLuint indexBuffer;
00047: struct CUSTOM_VERTEX
00048: {
00049: float r, g, b, a;
00050: float nx, ny, nz;
00051: float x, y, z;
00052: };
00053: CUSTOM_VERTEX vertices[] = {
00054: // 色 / 法線 / 頂点
00055: { 1.0f, 0.0f, 0.0f, 1.0f, -0.577f, 0.577f, 0.577f, -1.0f, 1.0f, 1.0f },
00056: { 1.0f, 0.0f, 0.0f, 1.0f, -0.577f, -0.577f, 0.577f, -1.0f, -1.0f, 1.0f },
00057: { 1.0f, 0.0f, 0.0f, 1.0f, 0.577f, 0.577f, 0.577f, 1.0f, 1.0f, 1.0f },
00058: { 1.0f, 0.0f, 0.0f, 1.0f, 0.577f, -0.577f, 0.577f, 1.0f, -1.0f, 1.0f },
00059: { 1.0f, 0.0f, 0.0f, 1.0f, 0.577f, 0.577f, -0.577f, 1.0f, 1.0f, -1.0f },
00060: { 1.0f, 0.0f, 0.0f, 1.0f, 0.577f, -0.577f, -0.577f, 1.0f, -1.0f, -1.0f },
00061: { 1.0f, 0.0f, 0.0f, 1.0f, -0.577f, 0.577f, -0.577f, -1.0f, 1.0f, -1.0f },
00062: { 1.0f, 0.0f, 0.0f, 1.0f, -0.577f, -0.577f, -0.577f, -1.0f, -1.0f, -1.0f },
00063: };
00064: GLuint indices[] = {
00065: 0, 2, 3, 1,
00066: 2, 4, 5, 3,
00067: 4, 6, 7, 5,
00068: 6, 0, 1, 7,
00069: 6, 4, 2, 0,
00070: 1, 3, 5, 7,
00071: };
用意したのは,頂点バッファとインデックスバッファの管理するためのGLuint型のvertexBufferという変数とindexBufferという変数です。CUSTOM_VERTEXという構造体は各頂点ごとに持たせるデータですね。あとでglInterleavedArrays()でGL_C4F_N3F_V3Fを指定するのでfloat型で色・法線・頂点の順で定義しておきます。verticesとindexesは実際の頂点ごとのデータとインデックスデータです。つぎに,Initialize()関数のなかでBuffer Objectの作成などを行っていきます。
00148: // GLEWの初期化
00149: if ( glewInit() != GLEW_OK )
00150: {
00151: cout << "Error : GLEWの初期化に失敗\n";
00152: exit(0);
00153: }
00154:
00155: // 頂点バッファの作成
00156: glGenBuffers(1, &vertexBuffer);
00157: glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
00158: glBufferData(GL_ARRAY_BUFFER, num_vertices * sizeof(CUSTOM_VERTEX), vertices, GL_STATIC_DRAW);
00159:
00160: // インデックスバッファの作成
00161: glGenBuffers(1, &indexBuffer);
00162: glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
00163: glBufferData(GL_ELEMENT_ARRAY_BUFFER, num_indices * 4, indices, GL_STATIC_DRAW);
00164:
00165: // バインドしたものをもどす
00166: glBindBuffer(GL_ARRAY_BUFFER, 0);
00167: glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
00168:
まず,GLEWの初期化を済ませて,glGenBuffer()関数でIDを取得して,glBindBuffer()でバッファを結びつけます。glBindBuffer()の第一引数は頂点配列データの場合GL_ARRAY_BUFFERを指定し,インデックス配列の場合はGL_ELEMENT_ARRAY_BUFFERを指定します。つづいて,glBufferDate()関数ですが, 第一引数はGL_ARRAY_BUFFERか,GL_ELEMENT_ARRAY_BUFFERを指定します。 第二引数はバッファオブジェクトのサイズを指定します。 第三引数はデータへのポインタを指定します。 第四引数は, GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, GL_STATIC_COPY, GL_STATIC_DRAW, GL_STATIC_READ, GL_STREAM_COPY, GL_STREAM_DRAW, GL_STREAM_READ を指定できます。データがしばしば変わる場合はGL_DYNAMIC_DRAW,データがあんまし変わらない場合はGL_STATIC_DRAWを指定しておけばいいと思います。今回はデータを変えないのでGL_STATIC_DRAWを指定しました。 続いて,描画命令の呼び出し部分です。 00256: // バッファをバインド 00257: glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); 00258: glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); 00259: 00260: // 00261: glInterleavedArrays(GL_C4F_N3F_V3F, 0, NULL); 00262: 00263: // 描画 00264: glDrawElements(GL_QUADS, sizeof(indexes), GL_UNSIGNED_INT, NULL); 00265: 00266: // バインドしたものをもどす 00267: glBindBuffer(GL_ARRAY_BUFFER, 0); 00268: glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);まず,glBindBuffer()でバッファをバインドします。次に,glInterleavedArrays()を呼びます。この関数を呼ぶ目的は,同時に複数の頂点配列を有効・無効にして、1つの集積された配列に含まれるすべての頂点データを示しているアドレスを指定するためです。 この関数の第一引数のformatには次が指定できます。 GL_V2F GL_V3F GL_C4UB_V2F GL_C4UB_V3F GL_C3F_V3F GL_N3F_V3F GL_C4F_N3F_V3F GL_T2F_V3F GL_T4F_V4F GL_T2F_C4UB_V3F GL_T2F_C3F_V3F GL_T2F_N3F_V3F GL_T2F_C4F_N3F_V3F GL_T4F_C4F_N3F_V4F Cはカラー,Tはテクスチャ座標,Nは法線ベクトル,Vは頂点座標を示し,数字は要素数を示しており,Fはfloat型,UBはunsigned byteを意味しています。 第二引数のstrideには,配列内でのバイトのオフセットを指定します。 第三引数のpointerには,インターリーブ配列内での位置を指定するポインタを指定します。 あとは,glDrawElement()関数を呼び出して一気に描画します。glDrawElements()関数の第一引数にはglBegin()関数の引数で指定するのと同じようにプリミティブの種類を指定します。第二引数にはレンダリング要素の数を指定します。第三引数にはインデックスデータのデータ型を指定します。ほとんどの場合はunsigned short型かunsigned int型でインデックスデータを定義すると思うので,GL_UNSIGNED_SHORTまたはGL_UNSIGNED_INTを指定しておけばいいと思います。第四引数にはインデックス指標を保存する配列へのポインタを指定します。しかし今回はBuffer Objectを使うので,第四引数はヌルポインタを渡しておきます。 一応こんな感じで描画できることはできたんですが,合っているんですかね?合っていないかもしれないので注意してください。 ★ Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。 プログラムの作成にはMicrosoft Visual Studio 2008 SP1 Professionalを用いています。 |