BufferObjectクラスを作る。

今回はOpenGLDirect3D風の頂点バッファクラス、インデックスバッファクラスを作ります。
まずは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 );

以上で次のような三角形が描画されます。

ダウンロード