OpenGL API のオブジェクト操作は、必ずライブラリ内の global な変数を
経由して行います。
例えば Buffer Size を調べる場合も、現在の context のステートに
オブジェクトを bind してから対応するメソッドを呼び出します。
オブジェクトが導入される前の API と互換性を保つことができる反面、
どのオブジェクトを対象としているのかコードを見ただけではわからなく
なっています。
どのオブジェクトが必要なのか、どこまで影響を与えるのかは、それぞれ
命令仕様をマニュアル等で確認する必要があります。
例えば OpenGL ES 2.0 の描画のコードは下記の通りです。
もし仮に D3D API のような書き方ができるとしたらこんな↓感じになるでしょうか。
「擬似コード (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) が導入されています。
VAO は頂点 Attribute の情報を保存し、描画時に利用することができます。
OpenGL 3.x (core profile) 以降は VAO が必須で、glBindVertexArray() が無いと
glVertexAttribPointer() がエラーになります。
つまり glVertexAttribPointer() は GL2 までは device_context のメソッドでしたが、
GL3 からは Vertex Array Object のメソッドに相当します。
擬似コードで表現してみます。
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() は失敗します。
同じ glBindBuffer() 命令でも動作が異なっていることがわかるかと思います。
GL_ELEMENT_ARRAY_BUFFER のバインド情報
・VAO が 0 の時は Default の Bind 情報が保持される。
・VAO が割り当てられている場合は VAO に格納する。
例えば下記 (7) のようなコードがあった場合、(A) は描画やオブジェクトに影響を
与えませんが (B) は VAO (input_layout) の内容を置き換えています。
● Vertex Attribute Binding
(7) にあるように、描画時の (A) glBindBuffer( GL_ARRAY_BUFFER ) は直接
影響を与えません。
もし描画に使用する頂点バッファを置き換えたい場合は、Attribute 毎に
glVertexAttribPointer() を呼び直すことになります。
glVertexAttribPointer() に渡す TYPE などの情報が必要になりますし、
同じバッファを見ている Attribute の数だけ命令を実行しなければなりません。
OpenGL 4.3 では Vertex Buffer と Vertex Attribute を分けて管理できる
仕組みが追加されています。
各 Attribute の頂点フォーマットは、これまでの
glVertexAttribPointer() 同様に glVertexAttribFormat() で定義します。
ただし各 Attribute が参照するバッファの情報は別で、bind_index を用いた
間接参照となります。
bind_index が指しているのは VertexBuffer の情報が入ったテーブルです。
実際のバッファの情報はこの VertexBuffer のテーブルの方に格納されます。
bind_index を通して、Attribute 毎に個別に持っていたバッファ情報を共有する
ことができます。
bind_index の値は glGetVertexAttribiv( GL_VERTEX_ATTRIB_BINDING ) で確認
することができます。
また実装上は Vertex Attribute の Table と Vertex Buffer の Table は
同一のもので、bind_index は自分自身への 2段階間接参照であることもわかります。
(8) を VertexAttribBinding を使って置き換えると下記の通り。
VertexAttribBinding を使うと、下記のように glBindVertexBuffer() 一つで
頂点が参照する Vertex Buffer を置き換えることができます。
試してみると glBindVertexBuffer() は glBindBuffer( GL_ARRAY_BUFFER )
を経由しないし全く変更もしないことがわかります。
これで描画に glBindBuffer( GL_ARRAY_BUFFER ) は必要なくなりました。
関連エントリ
・OpenGL ES 3.0 と Vertex Array Object
経由して行います。
例えば 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 ) は必要なくなりました。
関連エントリ
・OpenGL ES 3.0 と Vertex Array Object