Posted at

Element Array Bufferによる描画を行う

More than 1 year has passed since last update.


はじめに


  • 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のメリットが出てくるだろう。

スクリーンショット 2017-11-29 19.43.34 2.png

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();
}


キャプチャ

 以下のような四角が描画される。

ebo2.gif


まとめ


  • 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にしている。