BufferObjectクラスを作る。
今回はOpenGLでDirect3D風の頂点バッファクラス、インデックスバッファクラスを作ります。
まずはOpenGLのバッファオブジェクトの基礎から。
バッファオブジェクトの使用手順。
- 1.バッファオブジェクトが利用可能かどうか調べる。
- 1-1.サポートされているExtension(拡張機能)の一覧を取得。
- 1-2.Extensionの関数ポインタを取得。
- 2.バッファオブジェクトを使う。
- 2-1.バッファオブジェクトの生成と破棄。
- 2-2.バッファのバインド。
- 2-3.バッファの割り当て(+初期化)。
- 2-4.バッファの更新。
- 3.BufferObjectクラスを作る。
- 4.VertexBuffer(頂点バッファ)クラスを作る。
- 5.IndexBuffer(インデックスバッファ)クラスを作る。
- 6.描画
- 6-1.頂点配列の有効化・無効化。
- 6-2.描画。
1.バッファオブジェクトが利用可能かどうか調べる。
1-1.サポートされているExtensionの一覧を取得。
まず、バッファオブジェクトがサポートされているか調べるために、Extensionの一覧を文字列として取得し、バッファオブジェクトを表す「GL_ARB_vertex_buffer_object」を検索します。
const std::string extensions_str( reinterpret_cast< const char* >( ::glGetString( GL_EXTENSIONS ) ) ); const std::string extension_name = "GL_ARB_vertex_buffer_object"; if( extensions_str.find( extension_name ) == std::string::npos ) { std::stringstream ss; ss << "[" << extension_name << "]がサポートされていない" << std::endl; throw std::runtime_error( ss.str() ); }
- 1-2.Extensionの関数ポインタを取得。
Extensionが利用可能であれば、wglGetProcAddress()を使って関数のアドレスを取得します。
とりあえずstd::functionを使ってみます。
std::function< void ( GLsizei, GLuint* ) > glGenBuffers; std::function< void ( GLsizei, const GLuint* ) > glDeleteBuffers; std::function< void ( GLenum, GLuint ) > glBindBuffer; std::function< void ( GLenum, GLsizeiptrARB, const GLvoid*, GLenum ) > glBufferData; std::function< void ( GLenum, GLintptrARB, GLsizeiptrARB, const GLvoid* ) > glBufferSubData; std::function< GLvoid* ( GLenum, GLenum ) > glMapBuffer; std::function< GLboolean ( GLenum ) > glUnmapBuffer; glGenBuffers = reinterpret_cast< PFNGLGENBUFFERSARBPROC >( ::wglGetProcAddress( "glGenBuffersARB" ) ); glDeleteBuffers = reinterpret_cast< PFNGLDELETEBUFFERSARBPROC >( ::wglGetProcAddress( "glDeleteBuffersARB" ) ); glBindBuffer = reinterpret_cast< PFNGLBINDBUFFERARBPROC >( ::wglGetProcAddress( "glBindBufferARB" ) ); glBufferData = reinterpret_cast< PFNGLBUFFERDATAARBPROC >( ::wglGetProcAddress( "glBufferDataARB" ) ); glBufferSubData = reinterpret_cast< PFNGLBUFFERSUBDATAARBPROC >( ::wglGetProcAddress( "glBufferSubDataARB" ) ); glMapBuffer = reinterpret_cast< PFNGLMAPBUFFERARBPROC >( ::wglGetProcAddress( "glMapBufferARB" ) ); glUnmapBuffer = reinterpret_cast< PFNGLUNMAPBUFFERARBPROC >( ::wglGetProcAddress( "glUnmapBufferARB" ) );
2.バッファオブジェクトを使う。
2-1.バッファオブジェクトの生成と破棄。
GLuint buffer;
//バッファオブジェクトを生成 glGenBuffers( 1, &buffer );
//バッファオブジェクトの破棄 glDeleteBuffers( 1, &buffer );
2-2.バッファオブジェクトのバインドと解除。
targetには、頂点バッファの場合「GL_ARRAY_BUFFER_ARB」を、インデックスバッファの場合「GL_ELEMENT_ARRAY_BUFFER_ARB」を指定します。
//バインド
glBindBuffer( target, buffer );
//バインド解除 glBindBuffer( target, 0 );
2-3.バッファの割り当て(+初期化)。
buffer_sizeにはバッファ全体のバイト数を、dataにはバッファに格納するデータの先頭アドレスを指定します。dataにnullptrを指定するとバッファの割り当てのみ行われます。
glBindBuffer( target, buffer );
glBufferData( target, buffer_size, data, GL_STATIC_DRAW_ARB );
glBindBuffer( target, 0 );
2-4.バッファの更新。
バッファのoffsetバイト目からstride * element_countまでdataをコピーします。
strideには要素1つ分のバイト数を、element_countには要素数を、dataにはコピーするデータの先頭アドレスを指定します。
glBindBuffer( target, buffer );
glBufferSubData( target, offset, stride * element_count, data );
glBindBuffer( target, 0 );
3.BufferObjectクラスを作る。
以上を踏まえて、BufferObjectクラスを作ってみましょう。
- コンストラクタでバッファオブジェクトの生成、デストラクタでバッファオブジェクトの破棄。
- BufferObject、~BufferObject
- バインドは頻繁に使うので簡略化。
- bind
- 任意のデータでバッファの更新。
- update
/** * バッファオブジェクト */ class BufferObject : noncopyable { public: /** * バッファのバインド */ void bind( bool enable ); /** * バッファの更新 */ void update( int offset, int stride, int element_count, const GLvoid* data ); /** * ストライドを取得 */ int get_stride()const{ return stride; } /** * 要素数を取得 */ int size()const{ return element_count; } public: /** * バッファオブジェクトの生成 */ BufferObject( GLenum target, int stride, int element_count, const GLvoid* data = nullptr ); virtual ~BufferObject(); protected: GLuint buffer; ///< GLenum target; ///< GL_ARRAY_BUFFER_ARB、GL_ELEMENT_ARRAY_BUFFER_ARB int buffer_size; ///< バッファ全体のバイト数 int stride; ///< バッファ内の要素1つ分のサイズ int element_count; ///< 要素数 };
/** * コンストラクタ。バッファオブジェクトの生成。 * * @param target 頂点バッファの場合GL_ARRAY_BUFFER_ARBを、インデックスバッファの場合GL_ELEMENT_ARRAY_BUFFER_ARBを指定。 * @param stride バッファ内の要素1つ分のサイズ * @param element_count 要素数 * @param data 格納するデータの先頭アドレス。メモリを割り当てるだけであればnullptrを渡す。 */ BufferObject::BufferObject( GLenum target, int stride, int element_count, const GLvoid* data ) : target( target ) , buffer( 0 ) , buffer_size( stride * element_count ) , stride( stride ) , element_count( element_count ) { //バッファオブジェクトを生成 vbo.glGenBuffers( 1, &buffer ); bind( true ); glBufferData( target, buffer_size, data, GL_STATIC_DRAW_ARB ); bind( false ); } /** * デストラクタ。バッファオブジェクトの破棄。 */ BufferObject::~BufferObject() { //バッファオブジェクトの破棄 glDeleteBuffers( 1, &buffer ); } /** * バッファオブジェクトのバインド * * @param enable true(バインドする)/false(バインドを解除する) */ void BufferObject::bind( bool enable ) { glBindBuffer( target, enable ? buffer : 0 ); } /** * バッファの更新 * * @param offset オフセット * @param stride * @param element_count * @param data */ void BufferObject::update( int offset, int stride, int element_count, const GLvoid* data ) { bind( true ); glBufferSubData( target, offset, stride * element_count, data ); bind( false ); }
4.VertexBuffer(頂点バッファ)クラスを作る。
3で作ったBufferObjectクラスを継承して、頂点バッファに特化したVertexBufferクラスを作ります。また、頂点構造体も用意します。
/** * テスト用頂点構造体 */ struct Vertex { float x, y; ///< とりあえず2D float r, g, b, a; ///< unsigned charの方が良いかも float tu, tv; ///< テクスチャ座標 Vertex(){} Vertex( float x, float y, const Color& color, float tu, float tv ) : x( x ), y( y ) , r( color.r ), g( color.g ), b( color.b ), a( color.a ) , tu( tu ), tv( tv ) {} }; /** * 頂点バッファに特化したバッファオブジェクト */ class VertexBuffer : public BufferObject { public: /** * 頂点配列でバッファを更新 */ void update( int offset, int vertex_count, const Vertex* vertex ); public: /** * 頂点配列でバッファの生成、初期化 */ VertexBuffer( int vertex_count, const Vertex* vertex = nullptr ); };
BufferObjectのtargetにGL_ARRAY_BUFFER_ARBを指定します。
/** * 頂点配列でバッファの生成、初期化を行う。 * vertexにnullptrを指定した場合、vertex_countのサイズでバッファの生成のみ行う。 * * @param vertex_count 頂点数 * @param vertex 頂点配列の先頭アドレス */ VertexBuffer::VertexBuffer( int vertex_count, const Vertex* vertex ) : BufferObject( GL_ARRAY_BUFFER_ARB, sizeof( Vertex ), vertex_count, vertex ) { } /** * 頂点配列でバッファを更新 */ void VertexBuffer::update( int offset, int vertex_count, const Vertex* vertex ) { BufferObject::update( offset, sizeof( Vertex ), vertex_count, vertex ); }
5.IndexBuffer(インデックスバッファ)クラスを作る。
/** * インデックスバッファに特化したバッファオブジェクト */ class IndexBuffer : public BufferObject { public: /** * インデックス配列でバッファを更新 */ void update( int offset, int index_count, const GLushort* index ); public: /** * インデックス配列でバッファの生成、初期化 */ IndexBuffer( int index_count, const GLushort* index = nullptr ); };
BufferObjectのtargetにGL_ELEMENT_ARRAY_BUFFER_ARBを指定します。
/** * インデックス配列でバッファの生成、初期化を行う。 * indexにnullptrを指定した場合、index_countのサイズでバッファの生成のみ行う。 * * @param index_count インデックス配列の要素数 * @param index インデックス配列の先頭アドレス */ IndexBuffer::IndexBuffer( int index_count, const GLushort* index ) : BufferObject( GL_ELEMENT_ARRAY_BUFFER_ARB, sizeof( GLushort ), index_count, index ) { } /** * インデックス配列でバッファの更新 */ void IndexBuffer::update( int offset, int index_count, const GLushort* index ) { BufferObject::update( offset, sizeof( GLushort ), index_count, index ); }
6.描画。
6-1.頂点配列の有効化・無効化。
頂点配列を使って描画する場合は、事前にglEnableClientStateを呼び出して有効化する必要があります。(使い終わったら無効化)
//頂点配列を有効化 glEnableClientState( GL_VERTEX_ARRAY ); glEnableClientState( GL_COLOR_ARRAY ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); //描画 ; //頂点配列を無効化 glDisableClientState( GL_VERTEX_ARRAY ); glDisableClientState( GL_COLOR_ARRAY ); glDisableClientState( GL_TEXTURE_COORD_ARRAY );
6-2.描画。
- 頂点バッファ・インデックスバッファをバインド
- glVertexPointer、glColorPointer、glTexCoordPointerを使って頂点構造体内の頂点座標・頂点色・テクスチャ座標のオフセットを指定
- glDrawElementsで描画
Vertex v[] = { Vertex( 100.0f, 100.0f, Color::red, 0.0f, 0.0f ), Vertex( 200.0f, 200.0f, Color::green, 0.0f, 0.0f ), Vertex( 80.0f, 250.0f, Color::blue, 0.0f, 0.0f ), }; GLushort i[] = { 0, 1, 2, }; VertexBuffer vertex( 3, v ); IndexBuffer index( 3, i );
//バッファオブジェクトのバインド vertex.bind( true ); index.bind( true ); //頂点構造体内の頂点座標、頂点色のオフセットを指定 glVertexPointer( 2, GL_FLOAT, sizeof( Vertex ), reinterpret_cast< GLvoid* >( 0 ) ); glColorPointer( 4, GL_FLOAT, sizeof( Vertex ), reinterpret_cast< GLvoid* >( sizeof( GLfloat ) * 2 ) ); glTexCoordPointer( 2, GL_FLOAT, sizeof( Vertex ), reinterpret_cast< GLvoid* >( sizeof( GLfloat ) * 6 ) ); glDrawElements( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr ); //バッファオブジェクトのバインド解除 vertex.bind( false ); index.bind( false );