6. 頂点バッファ

頂点バッファとは、頂点座標、頂点カラー、テクスチャ座標、頂点インデックスなどを格納するバッファです。多数の頂点を持つモデルなどを頂点シェーダに処理させる場合は頂点バッファを使用してください。頂点バッファを使用しない場合は CPU で重い処理(頂点アレイの並べ替えなど)が行われ、パフォーマンスが著しく低下する恐れがあります。

6.1. オブジェクトの生成

バッファオブジェクトを glGenBuffers() で生成します。

コード 6-1. glGenBuffers() の定義
void glGenBuffers(GLsizei n, GLuint* buffers);

n 個のバッファオブジェクトを生成して、buffers にその名前(オブジェクト名)を格納します。

6.2. オブジェクトの指定

バッファオブジェクトを関連付ける頂点バッファの種類を glBindBuffer() で指定します。この関数の呼び出し以降、各種頂点バッファへの処理は指定されたバッファオブジェクトに対して行われるようになります。

コード 6-2. glBindBuffer() の定義
void glBindBuffer(GLenum target, GLuint buffer);

target には頂点バッファの種類を指定します。buffer には glGenBuffers() で生成したバッファオブジェクトを指定します。buffer に未生成のオブジェクト名が指定された場合、そのオブジェクト名のバッファオブジェクトの生成も同時に行われます。

表 6-1. 頂点バッファの種類

target に指定する値

頂点バッファの種類

GL_ARRAY_BUFFER

頂点座標、頂点カラー、法線などのバッファ

GL_ELEMENT_ARRAY_BUFFER

glDrawElements() で使用するインデックスのバッファ

GL_VERTEX_STATE_COLLECTION_DMP

頂点ステートコレクション(「6.6. 頂点ステートコレクション」を参照)

6.3. バッファの確保

glBufferData() でバッファの領域を確保し、頂点データをロードします。

コード 6-3. glBufferData() の定義
void glBufferData(GLenum target, GLsizeiptr size, const void* data, 
                  GLenum usage);

target に指定する値は glBindBuffer()target と同じです(表 6-1)。

data size には格納する頂点データとそのサイズを渡します。data に 0(NULL)を指定した場合は領域の確保のみが行われます。

usage には GL_STATIC_DRAW を渡さなければなりません。

補足:

glBufferData()target に特別なフラグを論理和で渡すことで、GPU のアクセス先や領域確保時の処理を指定することができます。詳細については、「3DS プログラミングマニュアル - グラフィックス応用編」「メインメモリに置かれたデータの使用」を参照してください。

6.4. バッファの書き換え

glBufferData() で確保したバッファの一部分を書き換えるには glBufferSubData() を使用します。

コード 6-4. glBufferSubData() の定義
void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, 
                     const void* data);

target に指定する値は glBindBuffer()target と同じです(表 6-1)。

offset には書き換える部分へのオフセットを指定します。

data size には書き込むデータとそのサイズを渡します。

6.5. バッファの破棄

不要になったバッファオブジェクトは glDeleteBuffers() で破棄します。

コード 6-5. glDeleteBuffers() の定義
void glDeleteBuffers(GLsizei n, const GLuint* buffers);

buffers に格納されている n 個のオブジェクト名で指定されたバッファオブジェクトを破棄します。

6.6. 頂点ステートコレクション

頂点ステートコレクションは 3DS が独自に導入したもので、バッファオブジェクトと頂点バッファの関連付けを記録します。頂点ステートコレクションを使用することで、バッファオブジェクトと頂点バッファの関連付けと頂点属性の設定をまとめて行うことができます。

頂点ステートコレクションはバッファオブジェクトと名前空間を共有していますので、glGenBuffers()glBindBuffer()glDeleteBuffers() を使用して生成・指定・破棄を行います。

6.6.1. 頂点ステートコレクションの生成

頂点ステートコレクションは特殊なバッファオブジェクトとして動作します。そのためバッファオブジェクトと同じように、glGenBuffers() で頂点ステートコレクションとして使用するオブジェクトを生成します。

6.6.2. 頂点ステートコレクションの指定

頂点ステートコレクションとして使用するバッファオブジェクトを指定するには glBindBuffer() を使用します。targetGL_VERTEX_STATE_COLLECTION_DMP を渡して呼び出してください。デフォルトでは、名前 0(buffer に 0 を渡した状態)のオブジェクトが頂点ステートコレクションとなっています。

呼び出し後は、頂点バッファ(GL_ARRAY_BUFFERGL_ELEMENT_ARRAY_BUFFER)への glBindBuffer() によるバッファオブジェクトの関連付けや、glEnableVertexAttribArray()glDisableVertexAttribArray()glVertexAttrib{1234}{fv}()glVertexAttribPointer() による頂点属性の設定が頂点ステートコレクションに記録されていきます。同じ頂点バッファへの関連付けは上書きされ、頂点ステートコレクションへの記録はほかの頂点ステートコレクションに切り替えられるまで行われます。

頂点ステートコレクションを切り替えると、頂点バッファとバッファオブジェクトとの関連付けは頂点ステートコレクションに記録されているオブジェクトにすべて切り替わります。

6.6.3. 頂点ステートコレクションの破棄

バッファオブジェクトと同じように、glDeleteBuffers() で頂点ステートコレクションを破棄することができます。頂点ステートコレクションを破棄しても、記録されているバッファオブジェクトの頂点バッファとの関連付けに影響はありません。

使用中の頂点ステートコレクションへの glDeleteBuffers() の呼び出しでは、すぐに頂点ステートコレクションが破棄されません。ほかの頂点ステートコレクションに切り替えられるまで使用状態のまま残ります。デフォルトの頂点ステートコレクションは破棄できません。デフォルトの頂点ステートコレクションに対する glDeleteBuffers() の呼び出しは無視されます。

6.7. 頂点バッファの使用例

頂点バッファと glDrawElements() を使用して三角形を 1 つ描画するコード例を、配列の定義、バッファの確保、描画の 3 つの部分に分けて紹介します。

コード 6-6. 配列の定義
GLuint triIndexID, triArrayID;
GLushort triIndex[1 * 3] = { 0, 1, 2 };
GLfloat triVertex[3 * 4] =
{
     0.5f,  0.0f, 0.0f, 1.0f,
    -0.5f,  0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f, 0.0f, 1.0f
};
GLfloat triVertexColor[3 * 3] =
{
    1.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 1.0f
}; 
コード 6-7. バッファの確保
glGenBuffers(1, &triArrayID);
glBindBuffer(GL_ARRAY_BUFFER, triArrayID);
glBufferData(GL_ARRAY_BUFFER, sizeof(triVertex) + sizeof(triVertexColor), 0, 
        GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(triVertex), triVertex);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(triVertex), sizeof(triVertexColor), 
        triVertexColor);
glGenBuffers(1, &triIndexID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triIndexID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * sizeof(GLushort), triIndex, 
GL_STATIC_DRAW);
コード 6-8. glDrawElements() による描画
glBindBuffer(GL_ARRAY_BUFFER, triArrayID);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)sizeof(triVertex));

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triIndexID);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0);

6.8. 頂点データの配置に関する制約

頂点バッファに格納された頂点データを使用して描画を行う場合、glVertexAttribPointer() で設定する頂点データの配置には以下のハードウェアによる制約があります。これらの制約に抵触した場合、glDrawArrays() または glDrawElements() の呼び出し時に GL_INVALID_OPERATION のエラーを生成します。

  • 各頂点データは、自身の型のサイズでアライメントされていなければなりません。
  • 各頂点データのストライドは、同じ構造体に含まれる頂点データのうち、最もサイズの大きい型のサイズの倍数でなければなりません。
  • 上記 2 つの制約を満たすために最小限必要なパディングより大きいサイズのパディングを頂点データのうしろに挿入する場合、その頂点データの最後尾から最も近い 4 バイト境界の次にある 4 バイト境界に続く頂点データを配置しなければなりません。

コンパイラが自動的にパディングを挿入するため、コーディングの段階で意識しなくても、上 2 つの制約には抵触しないようになっていることもあります。

下記のコード例では、extraPadding2 を挿入しなければ最後の制約に抵触してしまいます。

コード 6-9. 頂点データの配置に関する制約を回避するコード例
struct tagVertex
{
    GLshort position[3];
    GLshort extraPadding1;
    GLshort extraPadding2[2];
    GLshort color[4];
};

1 回の glDrawArrays() または glDrawElements() の呼び出しで使用する頂点属性および頂点インデックスには、頂点バッファを使用する頂点データと使用しない頂点データを混在させることができません。混在させた場合は GL_INVALID_OPERATION のエラーを生成します。

6.8.1. glDrawElements() のみの制約

以下の条件をすべて満たす場合、頂点アレイの格納方法による制限によって GL_INVALID_OPERATION のエラーが生成されます。

  • 頂点バッファを使用している。
  • 頂点属性を 12 個使用している。
  • すべての頂点属性を頂点アレイとして使用している。
    (すべての頂点属性に対して glEnableVertexAttribArray() が呼ばれている)
  • glDrawElements() で描画している。

上記をすべて満たす場合、少なくとも 2 個の頂点属性をインターリーブドアレイとして配置しなければなりません。つまり、12 個の独立アレイを同時に頂点属性として使用することはできません。

補足:

複数の頂点属性を構造体として定義し、頂点アレイを構造体の配列として配置したものをインターリーブドアレイと呼びます。これに対し、1 個の頂点属性を個別の配列として配置したものを独立アレイと呼びます。