OpenGL API のオブジェクト操作は、必ずライブラリ内の global な変数を
経由して行います。
例えば Buffer Size を調べる場合も、現在の context のステートに
オブジェクトを bind してから対応するメソッドを呼び出します。
// GL2 glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); GLint param[1]; glGetBufferParameteriv( GL_ARRAY_BUFFER, GL_BUFFER_SIZE, param ); glBindBuffer( GL_ARRAY_BUFFER, 0 );
オブジェクトが導入される前の API と互換性を保つことができる反面、
どのオブジェクトを対象としているのかコードを見ただけではわからなく
なっています。
どのオブジェクトが必要なのか、どこまで影響を与えるのかは、それぞれ
命令仕様をマニュアル等で確認する必要があります。
例えば OpenGL ES 2.0 の描画のコードは下記の通りです。
// GL2 -- (1) glUseProgram( program ); glUniform4fv( location, 4, matrix ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 24, (void*)0 ); glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 24, (void*)12 ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glDrawElements( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 );
もし仮に D3D API のような書き方ができるとしたらこんな↓感じになるでしょうか。
// GL2 -- 擬似コード (2) program->SetConstant( location, 4, matrix ); device_context->SetProgram( program ); device_context->SetIndexBuffer( index_object ); device_context->SetVertexAttrib( 0, 3, GL_FLOAT, GL_FALSE, 24, 0, vertex_object); device_context->SetVertexAttrib( 1, 3, GL_FLOAT, GL_FALSE, 24, 12, vertex_object); device_context->DrawIndexed( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 );
「擬似コード (2)」を見ると device_context に SetVertexBuffer() 命令が無く、
SetVertexAttrib() の方に vertex_object を渡していることがわかるかと思います。
実際に (1) の glDrawElements() は glBindBuffer( GL_ARRAY_BUFFER ) を
見ておらず、直前に glBindBuffer( GL_ARRAY_BUFFER, 0 ) があっても動作します。
glBindBuffer( GL_ARRAY_BUFFER ) を必要とするのは glVertexAttribPointer()
の方で、各 Vertex Attribute が頂点バッファの情報を保持しています。
Attribute それぞれが異なる Vertex Buffer を参照している可能性があるからです。
やはり (1) のコードを見ただけではこのような内部構造がわからないので、
OpenGL 命令の仕様はきちんと確認した方が良いようです。
● Vertex Array Object
頂点のバインドは複雑で情報も多いので、OpenGL 3.x 以降や OpenGL ES 3.0 では
Vertex Array Object (VAO) が導入されています。
// GL3 -- Vertex Array Object の作成 (3) GLuint input_layout= 0; glGenVertexArrays( 1, &input_layout ); glBindVertexArray( input_layout ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 24, (void*)0 ); glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 24, (void*)12 ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glBindVertexArray( 0 );
VAO は頂点 Attribute の情報を保存し、描画時に利用することができます。
// GL3 -- VAO を使った Rendering (4) glUseProgram( program ); glUniform4fv( location, 4, matrix ); glBindVertexArray( input_layout ); glDrawElements( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 );
OpenGL 3.x (core profile) 以降は VAO が必須で、glBindVertexArray() が無いと
glVertexAttribPointer() がエラーになります。
つまり glVertexAttribPointer() は GL2 までは device_context のメソッドでしたが、
GL3 からは Vertex Array Object のメソッドに相当します。
擬似コードで表現してみます。
// GL3 -- VAO 作成 擬似コード (5) IInputLayout* input_layout= device->CreateInputLayout( 2 ); input_layout->SetIndexBuffer( index_object ); input_layout->SetVertexAttrib( 0, 3, GL_FLOAT, GL_FALSE, 24, 0, vertex_object ); input_layout->SetVertexAttrib( 1, 3, GL_FLOAT, GL_FALSE, 24, 12, vertex_object );
// GL3 -- VAO を使った Rendering 擬似コード (6) program->SetConstant( location, 4, matrix ); device_context->SetProgram( program ); device_context->SetInputLayout( input_layout ); device_context->DrawIndexed( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 );
OpenGL ES 3.0 では OpenGL ES 2.0 互換性のために VAO 無しでも
glVertexAttribPointer() が使えるようになっています。
bind == 0 時にデフォルトの VAO が割り当てられていると考えられます。
OpenGL 3.x でも、RADEON など一部 GPU では default VAO が有効なものが
あるようです。
Vertex Array Object は Direct3D の InputLayout や VertexDeclaration に
相当しますが、上記のように vertex_object や index_object が含まれている点で
異なっています。
D3D では頂点のバインド情報とバッファの割り当ては別の命令でした。
OpenGL でも 4.3 で D3D 同様の仕組みが導入されているようです。
(後述: Vertex Attrib Binding)
● GL_ELEMENT_ARRAY_BUFFER の扱い
VAO の index_object に対する挙動は vertex_object の場合とは異なっています。
glDrawElements() は glBindBuffer( GL_ARRAY_BUFFER ) を参照しないし
VAO も glBindBuffer( GL_ARRAY_BUFFER ) に影響を与えません。
ですが、glDrawElements() は glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) を
参照し、VAO は glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) を置き換えます。
よって直前で glBindBuffer( GL_ELEMENT_ARRAY_BUFFER ) に 0 が
書き込まれると glDrawElements() は失敗します。
GLuint input_layout; GLuint vertex_object1; GLuint index_object1; // ** Bind: Vertex=[0] Index=[0] glBindVertexArray( input_layout ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object1 ); // ↓ ここでの bind は VAO input_layout に対して行う glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object1 ); // ** Bind: Vertex=[vertex_object1] Index=[index_object1] glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 12, 0 ); glEnableVertexAttribArray( 0 ); glBindVertexArray( 0 ); // ↑VAO の bind を解除したので index_object1 のバインドも消える↓ // ** Bind: Vertex=[vertex_object1] Index=[0] // VAO の変更によって GL_ELEMENT_ARRAY_BUFFER (index) の bind は // 元に戻るが、GL_ARRAY_BUFFER (vertex) には影響がでない。
同じ glBindBuffer() 命令でも動作が異なっていることがわかるかと思います。
GL_ELEMENT_ARRAY_BUFFER のバインド情報
・VAO が 0 の時は Default の Bind 情報が保持される。
・VAO が割り当てられている場合は VAO に格納する。
例えば下記 (7) のようなコードがあった場合、(A) は描画やオブジェクトに影響を
与えませんが (B) は VAO (input_layout) の内容を置き換えています。
// GL3 -- (7) glUseProgram( program ); glUniform4fv( location, 4, matrix ); glBindVertexArray( input_layout ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); // -- (A) glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); // -- (B) glDrawElements( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0 ); glBindVertexArray( 0 );
● Vertex Attribute Binding
(7) にあるように、描画時の (A) glBindBuffer( GL_ARRAY_BUFFER ) は直接
影響を与えません。
もし描画に使用する頂点バッファを置き換えたい場合は、Attribute 毎に
glVertexAttribPointer() を呼び直すことになります。
glVertexAttribPointer() に渡す TYPE などの情報が必要になりますし、
同じバッファを見ている Attribute の数だけ命令を実行しなければなりません。
OpenGL 4.3 では Vertex Buffer と Vertex Attribute を分けて管理できる
仕組みが追加されています。
glVertexAttribBinding( attrib_index, bind_index ); glBindVertexBuffer( bind_index, buffer_object, offset, stride ); glVertexAttribFormat( attrib_index, size, type, normalized, offset );
各 Attribute の頂点フォーマットは、これまでの
glVertexAttribPointer() 同様に glVertexAttribFormat() で定義します。
ただし各 Attribute が参照するバッファの情報は別で、bind_index を用いた
間接参照となります。
bind_index が指しているのは VertexBuffer の情報が入ったテーブルです。
実際のバッファの情報はこの VertexBuffer のテーブルの方に格納されます。
◎ GL3.x/4.x ・頂点 Attribute が持つ情報 Type, Size, Offset, NormalizeFlag, VertexBuffer, Stride ◎ GL4.3/4.4 VertexAttribBinding ・頂点 Attribute が持つ情報 Type, Size, Offset, NormalizeFlag, BindIndex (BindVertexBuffer を参照する) ・BindVertexBuffer のテーブルが持つ情報 VertexBuffer, Stride
bind_index を通して、Attribute 毎に個別に持っていたバッファ情報を共有する
ことができます。
bind_index の値は glGetVertexAttribiv( GL_VERTEX_ATTRIB_BINDING ) で確認
することができます。
また実装上は Vertex Attribute の Table と Vertex Buffer の Table は
同一のもので、bind_index は自分自身への 2段階間接参照であることもわかります。
// GL3 --- (8) GLuint input_layout= 0; glGenVertexArrays( 1, &input_layout ); glBindVertexArray( input_layout ); glBindBuffer( GL_ARRAY_BUFFER, vertex_object ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 24, (void*)0 ); glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 24, (void*)12 ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glBindVertexArray( 0 );
(8) を VertexAttribBinding を使って置き換えると下記の通り。
// GL4.3/4.4 --- (9) GLuint input_layout= 0; glGenVertexArrays( 1, &input_layout ); glBindVertexArray( input_layout ); glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, index_object ); glBindVertexBuffer( 0, vertex_object, 0, 24 ); glVertexAttribBinding( 0, 0 ); glVertexAttribBinding( 1, 0 ); glVertexAttribFormat( 0, 3, GL_FLOAT, GL_FALSE, 0 ); glVertexAttribFormat( 1, 3, GL_FLOAT, GL_FALSE, 12 ); glEnableVertexAttribArray( 0 ); glEnableVertexAttribArray( 1 ); glBindVertexArray( 0 );
VertexAttribBinding を使うと、下記のように glBindVertexBuffer() 一つで
頂点が参照する Vertex Buffer を置き換えることができます。
// GL4.3/4.4 -- (10) glBindVertexArray( input_layout ); glBindVertexBuffer( 0, vertex_object2, 0, 64 ); glBindVertexArray( 0 );
試してみると glBindVertexBuffer() は glBindBuffer( GL_ARRAY_BUFFER )
を経由しないし全く変更もしないことがわかります。
これで描画に glBindBuffer( GL_ARRAY_BUFFER ) は必要なくなりました。