はじめに
- Element Array Bufferを用いて、図形の描画を解説する。
解説用ソースコード
以下のソースコードを元に、解説を進める。
GitHub - 02-render_with_ebo
複雑な図形の描画、無駄な頂点について
例えば、四角形を描画したい場合、glDrawArray()
を用いると三角形が2つ必要になる。頂点数でいえば6つ用意する必要があり、重複している無駄な頂点が2つ存在することになる。これはglDrawArray()
が頂点の配列の順番をみて、頂点の結び方を決定する仕様からきている。
Element Array Bufferで、描画に使う頂点を再利用
上記の問題点は、一つの頂点を一回の描画にしか使えないことだ。
これを解決する手段として、一つの頂点を複数の描画に再利用できる機能が存在する。それが、Element Array Bufferである。
Element Array Buffer
Element Array Buffer(EBO)は、VBOと同様バッファオブジェクトの一つ。
頂点に**インデックス属性(どのように頂点を結ぶか)**を付加することができるバッファである。インデックス属性は、一つの頂点に対して複数付加することができ、これにより描画する際に頂点の再利用が可能になる。
GL_ELEMENT_ARRAY_BUFFER
のバインドポイントで生成する。Index Buffer Objectとも呼ばれている。
単純な四角でもメモリ消費を20%減できる。
頂点を再利用することができるので、メモリの削減やパフォーマンスの向上がメリットといえる。
下記のように、実際には法線とテクスチャ座標が加わった四角を描画したとする。
(48byteの計算結果は、左から位置、色、法線、テクスチャ座標を表している。)
EBOなし 結果:288byte
3float + 4float + 3float + 2float = 48byte \\
48 \times 6 = 288byte \\
EBOあり 結果:216byte
3float + 4float + 3float + 2float = 48byte \\
6int = 24byte \\
48 \times 4 + 24 = 216byte \\
この四角形だけでも、EBOありの方が20%程の少ないことがわかる
さらに複雑な形状ならば、重複している頂点が膨大になるので、EBOのメリットが出てくるだろう。
OpenGL 3D Game Tutorial 3: Rendering with Index Buffers by thin Matrix
では、コードを交えつつ実装方法をみていく...
EBOもVBOとほぼ同じ手続きで生成する。
EBOは、VBOとほぼ同じ手続きで生成する。
glBindVertexArray()
中に作成すると、VAOが自動的にEBOとしてバインドしてくれる。
int indices[] = {0, 1, 2, 0, 3, 2};
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glDrawElementsで描画する
描画には、専用のglDrawElements()
を用いる。余談の項目に公式docの和訳を載せているので、気になる人は参照してほしい。
glBindVertexArray( vao );
glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0 );
glBindVertexArray( 0 );
上記の実装で4つの頂点で四角を描画できた。
oFでもEBOはofVbo
ofVboは、VBOとVAOの機能を持っていると解説したが、EBOの機能も持っている。
ofVboに、EBOをバインドするには、ofVbo::setIndexData()
を用いる。
// Position Attribute
std::array<ofVec3f, 4> of_vertices;
of_vertices[0] = ofVec3f(-0.7f, -0.7f, 0.0f);
of_vertices[1] = ofVec3f(0.7f,-0.7f, 0.0f);
of_vertices[2] = ofVec3f(0.7f, 0.7f, 0.0f);
of_vertices[3] = ofVec3f(-0.7f,0.7f, 0.0f);
// Color Attribute
std::array<ofFloatColor, 4> of_colors;
of_colors[0] = ofFloatColor(1.0f, 0.0f, 0.0f, 1.0f);
of_colors[1] = ofFloatColor(0.0f, 1.0f, 0.0f, 1.0f);
of_colors[2] = ofFloatColor(0.0f, 0.0f, 1.0f, 1.0f);
of_colors[3] = ofFloatColor(0.0f, 1.0f, 1.0f, 1.0f);
// Index Attribute
std::array<ofIndexType, 6> of_indices = {0, 1, 2, 0, 3, 2};
{
of_vao.bind();
of_vao.setVertexData(&of_vertices[0], 4 * sizeof(ofVec3f), GL_STATIC_DRAW);
of_vao.setColorData(&of_colors[0], 4 * sizeof(ofFloatColor), GL_STATIC_DRAW);
of_vao.setIndexData(&of_indices[0], 6 * sizeof(ofIndexType), GL_STATIC_DRAW);
of_vao.unbind();
}
キャプチャ
まとめ
- EBOを用いると、描画にあたり頂点を再利用する
- EBOによる描画は、
glDrawElements()
を用いる - oFでEBOを利用するには、ofVboを用いる。
余談1. 3Dモデルはあまり気にしなくていい?
筆者自身あまり詳しくないので断言できないが、世の中の3Dモデルに対して「インデックスをもったデータか」という心配はあまりしなくていいように思う。
実際、スタンフォードドラゴンを、MeshLabで「Remove Duplicate Vertices」しても、削減はできなかった上、ofxAssimpModelLoaderも、ofVbo::drawElements()しか実装されていない。
そのため、「3Dモデルはインデックスをもってエクスポートされるしできる前提」だと思っていいのではないか。
余談2. glDrawElements()について
ほとんど、公式ドキュメントの和訳だが、以下に仕様を載せておく。
glDrawElements(GLenum mode,
GLsizei count,
GLenum type,
const GLvoid * indices)
-
mode : 描画するプリミティブの指定。以下が指定可能。
- GL_POINTS
- GL_LINE_STRIP
- GL_LINE_LOOP
- GL_LINES
- GL_LINE_STRIP_ADJACENCY
- GL_LINES_ADJACENCY
- GL_TRIANGLE_STRIP
- GL_TRIANGLE_FAN
- GL_TRIANGLES
- GL_TRIANGLE_STRIP_ADJACENCY
- GL_TRIANGLES_ADJACENCY
- GL_PATCHES
-
count : Indexの数を指定。
-
type : Indexの型を指定。
- GL_UNSIGNED_BYTE
- GL_UNSIGNED_SHORT
- GL_UNSIGNED_INT
-
indices : インデックスが格納されている場所へのポインタを指定。VAOにバインドしているのでここでは、0にしている。