OpenGLでSpriteBatchクラスを作る。
VertexBufferのテンプレート化
VertexBufferで扱う頂点は、
- 2Dか3Dか
- colorを持つかどうか
- テクスチャ座標を持つかどうか
などユーザが自由に決めることができるので、テンプレート化しておくと便利です。
/** * 頂点バッファに特化したバッファオブジェクト */ template< typename Vertex > class VertexBuffer : public BufferObject { public: /** * 頂点配列でバッファを更新 */ void update( int offset, int vertex_count, const Vertex* vertex ) { BufferObject::update( offset, sizeof( Vertex ), vertex_count, vertex ); } public: /** * 頂点配列でバッファの生成、初期化を行う。 * vertexにnullptrを指定した場合、vertex_countのサイズでバッファの生成のみ行う。 * * @param vertex_count 頂点数 * @param vertex 頂点配列の先頭アドレス */ VertexBuffer( int vertex_count, const Vertex* vertex = nullptr ) : BufferObject( GL_ARRAY_BUFFER_ARB, sizeof( Vertex ), vertex_count, vertex ) { } };
SpriteBatchクラスの仕様。
- 頂点は以下のような、2D・floatのRGBA・テクスチャ座標を持つ
/** * SpriteBatch用頂点構造体 */ struct SpriteBatchVertex { float x, y; ///< とりあえず2D float r, g, b, a; ///< unsigned charの方が良いかも float tu, tv; ///< テクスチャ座標 };
- コンストラクタで一度に描画可能なスプライトの最大数を指定して、頂点バッファとインデックスバッファを作成
- 頂点バッファは「スプライトの4点×最大数」のメモリを割り当てるだけ。
- インデックスバッファは以下のようにスプライト1つに対して「0-1-2」「0-2-3」の2つの三角形、つまり「3×2×最大数分」のメモリを割り当てて、「0-1-2」「0-2-3」「4-5-6」「4-6-5」...という値で初期化しておく。
- add()で、行列スタックを使って座標変換した4点を頂点バッファに追加。
- draw()で、頂点バッファにたまっているスプライトをまとめて一気に描画。
- とりあえずZ値でのソート、テクスチャでのソートなどは行わない。
- 以前の呼び出しと異なるテクスチャが指定された場合、現在までに溜まっているスプライトを描画してリセットしてから新しく追加する。
SpriteBatchクラスの実装
class SpriteBatch { public: /** * 頂点バッファにスプライトを追加。 */ void add( const TexturePtr& texture, const Recti& rect, const Color& color ); /** * 頂点バッファに溜められているスプライトをまとめて描画。 */ void draw(); public: SpriteBatch( GLushort sprite_count ); private: VertexBuffer< SpriteBatchVertex > vertex; ///< sprite_count * 4個の頂点 IndexBuffer index; ///< sprite_count * 6個のインデックス int current; ///< 現在のスプライト数 int sprite_count; ///< スプライトの最大数 std::shared_ptr< OpenGLTexture > texture; ///< 現在のスプライトで使われているテクスチャ };
/** * コンストラクタ * * @param sprite_count スプライト数 */ SpriteBatch::SpriteBatch( GLushort sprite_count ) : vertex( sprite_count * 4 ) //スプライト数 * 4の頂点バッファを生成 , index( sprite_count * 6 ) //スプライト数 * 6のインデックスバッファを生成 , current( 0 ) , sprite_count( sprite_count ) { //左上から時計回りで0-1-2-3の四角形を //0-1-2、0-2-3の三角形で描画するための //インデックス配列を設定する for( GLushort i = 0, j = 0; i < sprite_count; i ++, j += 4 ) { const std::array< GLushort, 3 * 2 > temp = { 0 + j, 1 + j, 2 + j, 0 + j, 2 + j, 3 + j, }; int offset = index.get_stride() * i * 6; index.update( offset, 6, temp.data() ); } } /** * スプライトバッチにスプライトの頂点データを追加する。 * * @param texture 使用するテクスチャ * @param rect 描画する矩形 * @param color 合成するポリゴンの色 */ void SpriteBatch::add( const TexturePtr& texture, const Recti& rect, const Color& color ) { //以前の呼び出しと異なるテクスチャが使われた場合、 //または格納可能なスプライトの最大数を超えている場合 //現在までのスプライトを全て描画する。 if( ( this->texture && this->texture != texture ) || ( sprite_count <= current ) ) { draw(); } this->texture = std::static_pointer_cast< OpenGLTexture >( texture ); const GLfloat width = rect.Width() * 1.0f; const GLfloat height = rect.Height() * 1.0f; const GLfloat addx = rect.Width() / 2.0f; const GLfloat addy = rect.Height() / 2.0f; const Rectf tex_coord = texture->GetTextureCoord( rect ); typedef std::array< SpriteBatchVertex, 4 > VertexArray; const VertexArray temp = { SpriteBatchVertex( 0.0f - addx, 0.0f - addy, color, tex_coord.left, tex_coord.top ), SpriteBatchVertex( width - addx, 0.0f - addy, color, tex_coord.right, tex_coord.top ), SpriteBatchVertex( width - addx, height - addy, color, tex_coord.right, tex_coord.bottom ), SpriteBatchVertex( 0.0f - addx, height - addy, color, tex_coord.left, tex_coord.bottom ), }; //頂点座標のトランスフォーム float m[ 16 ]; glGetFloatv( GL_MODELVIEW_MATRIX, m ); VertexArray v = temp; for( int i = 0; i < 4; i ++ ) { v[ i ].x = temp[ i ].x * m[ 0 ] + temp[ i ].y * m[ 4 ] + 1.0f * m[ 8 ] + 1.0f * m[ 12 ]; v[ i ].y = temp[ i ].x * m[ 1 ] + temp[ i ].y * m[ 5 ] + 1.0f * m[ 9 ] + 1.0f * m[ 13 ]; } int offset = vertex.get_stride() * current * 4; vertex.update( offset, 4, v.data() ); current ++; //スプライト数をインクリメント } /** * スプライトバッチの描画 */ void SpriteBatch::draw() { vertex.bind( true ); index.bind( true ); texture->bind( true ); SpriteBatchVertex::draw( current * 6 ); vertex.bind( false ); index.bind( false ); texture->bind( false ); current = 0; //スプライト数をリセット }
描画方法は頂点構造体に依存するので、以下のような関数を追加しておきます。
/** * SpriteBatch用頂点構造体 */ struct SpriteBatchVertex { /*略*/ /** * 頂点配列の有効化/無効化 */ static void enable( bool enable ) { if( enable ) { glEnableClientState( GL_VERTEX_ARRAY ); glEnableClientState( GL_COLOR_ARRAY ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); } else { glDisableClientState( GL_VERTEX_ARRAY ); glDisableClientState( GL_COLOR_ARRAY ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); } } /** * 頂点配列を使った描画 */ static void draw( GLsizei index_count ) { //頂点構造体内のストライドと、頂点座標・頂点色・テクスチャ座標のオフセットを指定 int stride = sizeof( SpriteBatchVertex ); int offset = 0; glVertexPointer( 2, GL_FLOAT, stride, reinterpret_cast< GLvoid* >( offset ) ); offset += sizeof( float ) * 2; glColorPointer( 4, GL_FLOAT, stride, reinterpret_cast< GLvoid* >( offset ) ); offset += sizeof( float ) * 4; glTexCoordPointer( 2, GL_FLOAT, stride, reinterpret_cast< GLvoid* >( offset ) ); glDrawElements( GL_TRIANGLES, index_count, GL_UNSIGNED_SHORT, nullptr ); } };
使い方
//適当なテクスチャを作成 TexturePtr texture = ...; //スプライトバッチを生成。最大スプライト数を指定。 SpriteBatch sprite( 1024 ); SpriteBatchVertex::enable( true ); //頂点配列の有効化 OpenGLTexture::enable( true ); //テクスチャマッピングの有効化 //適当にスプライトを追加 view->Push(); view->Translate( 50.0f, 50.0f, 0.0f ); sprite.add( texture, Recti( 0, 0, 64, 64 ), Color::white ); view->Translate( 50.0f, 50.0f, 0.0f ); sprite.add( texture, Recti( 0, 0, 64, 64 ), Color::red ); view->Translate( 50.0f, 50.0f, 0.0f ); sprite.add( texture, Recti( 0, 0, 64, 64 ), Color::green ); view->Translate( 50.0f, 50.0f, 0.0f ); sprite.add( texture, Recti( 0, 0, 64, 64 ), Color::blue ); view->Pop(); //描画 sprite.draw(); SpriteBatchVertex::enable( false ); //頂点配列の無効化 OpenGLTexture::enable( false ); //テクスチャマッピングの有効化
OpenGLViewにスプライト描画機能を実装する。
ここまでのフレームワークは、以下のようなISpriteインターフェイスを実装しています。
/** * スプライト描画機能 */ class ISprite { public: /** * スプライト描画開始。以降、AddSpriteにより内部バッファにスプライトを追加。 */ virtual void BeginSprite() = 0; /** * スプライト描画終了。内部のバッファに溜めてあるスプライトリストをバックバッファへ出力。 */ virtual void EndSprite() = 0; /** * バッファにスプライトを追加。 */ virtual void AddSprite( const TexturePtr& texture, const Recti& rect, const Color& color ) = 0; };
OpenGLによる実装クラスであるOpenGLViewでは、まだこれらの機能を実装していないので、今回作成したSpriteBatchクラスを使ってスプライト描画機能を追加してみましょう。
/** * ViewのOpenGL実装 */ class OpenGLView : public View { /*略*/ // ------------------------------------------------------------------------------- // ISprite実装 // ------------------------------------------------------------------------------- public: /** * スプライト描画開始。以降、AddSpriteにより内部バッファにスプライトを追加。 */ void BeginSprite(); /** * スプライト描画終了。内部のバッファに溜めてあるスプライトリストをバックバッファへ出力。 */ void EndSprite(); /** * バッファにスプライトを追加。 */ void AddSprite( const TexturePtr& texture, const Recti& rect, const Color& color ); private: SpriteBatchPtr sprite; };
/** * コンストラクタ * * @param title ウィンドウのタイトル * @param client_size ウィンドウのクライアントサイズ */ OpenGLView::OpenGLView( const String& title, const Sizei& client_size ) /*略*/ //スプライト最大数を指定 , sprite( create_sprite_batch( 1024 ) ) { /*略*/ } /** * スプライト描画開始。以降、AddSpriteにより内部バッファにスプライトを追加。 */ void OpenGLView::BeginSprite() { SpriteBatchVertex::enable( true ); OpenGLTexture::enable( true ); } /** * スプライト描画終了。内部のバッファに溜めてあるスプライトリストをバックバッファへ出力。 */ void OpenGLView::EndSprite() { sprite->draw(); SpriteBatchVertex::enable( false ); OpenGLTexture::enable( false ); } /** * バッファにスプライトを追加。 * * @param texture テクスチャ * @param rect 描画元矩形 * @param color 合成する色 */ void OpenGLView::AddSprite( const TexturePtr& texture, const Recti& rect, const Color& color ) { sprite->add( texture, rect, color ); }
使い方
Direct3DViewと同等の使い方が可能になりました。
//適当なテクスチャを作成 TexturePtr texture = ...; view->BeginSprite(); //適当にスプライトを追加 view->Push(); view->Translate( 50.0f, 50.0f, 0.0f ); view->AddSprite( texture, Recti( 0, 0, 64, 64 ), Color::white ); view->Translate( 50.0f, 50.0f, 0.0f ); view->AddSprite( texture, Recti( 0, 0, 64, 64 ), Color::red ); view->Translate( 50.0f, 50.0f, 0.0f ); view->AddSprite( texture, Recti( 0, 0, 64, 64 ), Color::green ); view->Translate( 50.0f, 50.0f, 0.0f ); view->AddSprite( texture, Recti( 0, 0, 64, 64 ), Color::blue ); view->Pop(); view->EndSprite();
ダウンロード
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 );
ダウンロード
Programmers' Grimoireを予約しました。
std::unique_ptrでHDCとHGLRCを扱う。
ゲームフレームワーク的なものを作る。番外編1。
windowsプログラミングにおいて、HDCなどのハンドルの管理は非常に面倒ですよね?
HWND hwnd = ...;
HDC hdc = GetDC( hwnd );
GDIを使った描画など;
ReleaseDC( hwnd, hdc ); //使い終わったらReleaseするのが面倒!
std::unique_ptrを使う。
デバイスコンテキストクラス的なものを作って、デストラクタでReleaseすれば良いわけですが、正直デストラクタも書きたくないのです。
こちらをお手本にして、std::unique_ptrを使ってみましょう。
deleterでWindowDCを解放する。
/** * WindowDCを解放するためのdeleter */ struct dc_deleter { typedef HDC pointer; void operator()( HDC hdc )const { ReleaseDC( hwnd, hdc ); } dc_deleter( HWND hwnd ) : hwnd( hwnd ){} HWND hwnd; };
記述が長くなるのでtypedefしておくと便利。
/** * HDCのunique_ptr */ typedef std::unique_ptr< HDC, dc_deleter > dc_handle;
使い方
{
dc_handle dc( ::GetDC( hwnd ), deleter( hwnd ) );
GDIなどで描画( dc.get(), ... );
}//スコープから抜ける時にdeleterで解放される
Windowクラスにデバイスコンテキスト取得メソッドを追加。
class Window { /*略*/ public: /** * WinodwDC取得 */ dc_handle get_dc()const{ reutrn dc_handle( ::GetDC( hwnd ), deleter( hwnd ) ); } };
使い方
Window* window = ...; //既にウィンドウは生成済み { dc_handle dc( window->get_dc() ); GDIなどで描画( dc.get(), ... ); }//スコープから抜ける時にdeleterで解放される
OpenGLレンダリングコンテキストをstd::unique_ptrで管理する。
上記のウィンドウのデバイスコンテキストと同様に、レンダリングコンテキストもstd::unique_ptrに切り替えてみます。
deleterを作る
/** * レンダリングコンテキストを破棄するためのdeleter */ struct glrc_deleter { typedef HGLRC pointer; void operator()( HGLRC rc )const; { ::wglMakeCurrent( dc, nullptr ); ::wglDeleteContext( rc ); } glrc_deleter( HDC dc ) : dc( dc ){} HDC dc; }; /** * レンダリングコンテキストハンドル */ typedef std::unique_ptr< HGLRC, glrc_deleter > glrc_handle;
OpenGLの初期化
/** * ViewのOpenGL実装 */ class OpenGLView : public View { public: /** * コンストラクタ */ OpenGLView( const String& title, const Sizei& client_size ); private: /** * OpenGLレンダリングコンテキストの生成 */ glrc_handle create_rendering_context( const dc_handle& dc ); private: dc_handle dc; glrc_handle rc; };
/** * コンストラクタ * * @param title ウィンドウのタイトル * @param client_size ウィンドウのクライアントサイズ */ OpenGLView::OpenGLView( const String& title, const Sizei& client_size ) : View( title, client_size ) //Viewの生成 , dc( get_dc() ) //ViewからDCを取得 , rc( create_rendering_context( dc ) ) //ViewのDCを使ってGLRCを生成 { } /** * OpenGLレンダリングコンテキストの生成 * * @param dc デバイスコンテキスト */ glrc_handle OpenGLView::create_rendering_context( const dc_handle& dc ) { //PIXELFORMATDESCRIPTORにピクセルフォーマットを設定し、 //デバイスコンテキストにもっと適合するピクセルフォーマットを選択、設定後 //レンダリングコンテキストを生成する PIXELFORMATDESCRIPTOR pfd = { sizeof( PIXELFORMATDESCRIPTOR ) }; pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW // support window | PFD_SUPPORT_OPENGL // support OpenGL | PFD_DOUBLEBUFFER // double buffered ; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 32; pfd.cDepthBits = 16; pfd.iLayerType = PFD_MAIN_PLANE; int format = ::ChoosePixelFormat( dc.get(), &pfd ); if( format == 0 ) throw std::runtime_error( "ChoosePixelFormat" ); if( !::SetPixelFormat( dc.get(), format, &pfd ) ) throw std::runtime_error( "SetPixelFormat" ); HGLRC rc = ::wglCreateContext( dc.get() ); ::wglMakeCurrent( dc.get(), rc ); return glrc_handle( rc, glrc_deleter( dc.get() ) ); }
ウィンドウの生成からOpenGLの初期化まで非常にシンプルに記述できていると思います。
また、初期化の途中で例外が発生しても、unique_ptrのデストラクタで確実に解放されます。
ダウンロード
ゲームフレームワーク的なものを作る。(10)〜OpenGLでテクスチャを使う〜
まずはOpenGLでテクスチャを使用する際の基本から。
R8G8B8A8の32bitテクスチャを作る
GLuint texture;
Size size( w, h );
DWORD pixel[] = { ... }; //適当なピクセルデータを設定しておく
- glGenTextures()で指定枚数のテクスチャを作成。とりあえず1でよい。
- glBindTexture()で作成したテクスチャをバインドしてから、フィルタなどのパラメータを設定
- フィルタの設定は必須。
- glTexture2D()でピクセルデータをコピー。
- バインド解除
glPixelStorei( GL_UNPACK_ALIGNMENT, 4 ); glGenTextures( 1, &texture ); glBindTexture( GL_TEXTURE_2D, texture ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel ); glBindTexture( GL_TEXTURE_2D, 0 ); //バインド解除
- 使い終わったらglDeleteTextures()で削除
glDeleteTextures( 1, &texture );
テクスチャの生成は以上で終了です。
スクリーンの設定
OpenGLではデフォルトで画面の中心を原点とした上に向かってY軸の正(-1.0〜1.0)、右に向かってX軸の正(-1.0〜1.0)となるような座標系になっているので、
画面の左上を原点、ウィンドウサイズに合わせた座標系に変更します。
//ビューポート行列の設定 glViewport( 0, 0, client_size.width, client_size.height ); //プロジェクション行列の設定 glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluOrtho2D( 0.0, static_cast< GLdouble >( client_size.width ), //left, right static_cast< GLdouble >( client_size.height ), 0.0 //bottom, top ); //ビュー行列の設定 glMatrixMode( GL_MODELVIEW ); glLoadIdentity();
ポリゴンにテクスチャを貼る
- glEnable()でテクスチャマップを有効化
- glBindTexture()でポリゴンに貼るテクスチャを設定
- glBegin()〜glEnd()でポリゴンの各頂点を設定
- glTexCoord(s,t)でテクスチャ座標(0.0〜1.0)の設定
- glVertex2f(x,y)で頂点座標の設定
glEnable( GL_TEXTURE_2D ); //テクスチャマップを有効にする glBindTexture( GL_TEXTURE_2D, texture ); //生成したテクスチャをバインド glBegin( GL_QUADS ); //四角形ポリゴン //左上から時計回りの四角形 glTexCoord2f( 0.0f, 0.0f ); glVertex2f( 0.0f, 0.0f ); //左上 glTexCoord2f( 1.0f, 0.0f ); glVertex2f( 128.0f, 0.0f ); //右上 glTexCoord2f( 1.0f, 1.0f ); glVertex2f( 128.0f, 128.0f ); //右下 glTexCoord2f( 0.0f, 1.0f ); glVertex2f( 0.0f, 128.0f ); //左下 glEnd(); glBindTexture( GL_TEXTURE_2D, 0 ); //テクスチャ解除 glDisable( GL_TEXTURE_2D ); //テクスチャマップ無効
ちなみにテクスチャは以下のように作っています。
DWORD pixel[ 64 * 64 ]; int index = 0; for( int y = 0; y < 64; y ++ ) { for( int x = 0; x < 64; x ++ ) { Color color; color.r = x / 63.0f; color.g = 0.0f; color.b = y / 63.0f; color.a = 1.0f; pixel[ index ] = color.to_R8G8B8A8(); index ++; } }
ピクセルフォーマット
VC++2010ExpressEditionで32ビットテクスチャを作る場合、デフォルトで上位バイトからR8G8B8A8のフォーマットしか使えません(OpenGL 1.1)。Direct3Dと同じA8R8G8B8を使いたければ、こちらから「glext.h」「glxext.h」「wglext.h」をダウンロード、インストールして(VCのパスを通す)、インクルードする必要があります。(OpenGL 1.2以上)
format | type | |
---|---|---|
GL_BGRA | GL_UNSIGNED_INT_8_8_8_8 | 0xbbggrraa |
GL_RGBA | GL_UNSIGNED_INT_8_8_8_8 | 0xrrggbbaa |
GL_BGRA | GL_UNSIGNED_INT_8_8_8_8_REV | 0xaabbggrr |
GL_RGBA | GL_UNSIGNED_INT_8_8_8_8_REV | 0xaarrggbb |
/** * ピクセルフォーマット **/ struct PixelFormat { enum Type { Unknown, b8g8r8a8, //0xbbggrraa r8g8b8a8, //0xrrggbbaa a8r8g8b8, //0xaarrggbb a8b8g8r8, //0xaabbggrr }; };
int format = 0, type = 0; switch( pixel_format ) { case PixelFormat::b8g8r8a8: format = GL_BGRA, type = GL_UNSIGNED_INT_8_8_8_8; break; case PixelFormat::r8g8b8a8: format = GL_RGBA, type = GL_UNSIGNED_INT_8_8_8_8; break; case PixelFormat::a8r8g8b8: format = GL_BGRA, type = GL_UNSIGNED_INT_8_8_8_8_REV; break; case PixelFormat::a8b8g8r8: format = GL_RGBA, type = GL_UNSIGNED_INT_8_8_8_8_REV; break; default: format = GL_BGRA, type = GL_UNSIGNED_INT_8_8_8_8; break; } glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0, format, type, &pixel[ 0 ] );
テクスチャインターフェイス
デバッグ用にテクスチャの情報を出力するために文字列化インターフェイスを実装しておくと便利かもしれません。
/** * テクスチャインターフェイス */ class ITexture : public IStringizable //ストリームへ出力可能 , noncopyable //インスタンスのコピー禁止 { public: /** * デストラクタ */ virtual ~ITexture(){} public: /** * サイズを取得 */ virtual Size GetSize()const = 0; /** * ピクセルフォーマットを取得 */ virtual PixelFormat::Type GetPixelFormat()const = 0; };
OpenGLテクスチャクラス
/** * テクスチャクラス(OpenGLによる実装) */ class OpenGLTexture : public ITexture { // ------------------------------------------------------------------------------- // 生成と破棄 // ------------------------------------------------------------------------------- public: /** * コンストラクタ */ OpenGLTexture( const std::vector< DWORD >& pixel, const Size& size, PixelFormat::Type pixel_format ); /** * デストラクタ */ ~OpenGLTexture(); // ------------------------------------------------------------------------------- // ITexture実装 // ------------------------------------------------------------------------------- public: /** * テクスチャサイズ取得 */ Size GetSize()const{ return size; } /** * ピクセルフォーマット取得 */ PixelFormat::Type GetPixelFormat()const{ return pixel_format; } // ------------------------------------------------------------------------------- // IStringizable実装 // ------------------------------------------------------------------------------- private: /** * 文字列への変換 */ String to_string()const; // ------------------------------------------------------------------------------- // 基本機能 // ------------------------------------------------------------------------------- public: /** * インスタンスを取得 */ GLuint GetInstance()const{ return instance; } private: Size size; ///< テクスチャサイズ PixelFormat::Type pixel_format; ///< ピクセルフォーマット GLuint instance; ///< OpenGLテクスチャの実体 };
/** * コンストラクタ * * @param pixel ピクセル配列 * @param size テクスチャサイズ * @param pixel_format ピクセルフォーマット */ OpenGLTexture::OpenGLTexture( const std::vector< DWORD >& pixel, const Size& size, PixelFormat::Type pixel_format ) : size( size ) , instance( 0 ) , pixel_format( pixel_format ) { //ピクセルフォーマットの選択 int format = 0, type = 0; switch( pixel_format ) { case PixelFormat::b8g8r8a8: format = GL_BGRA, type = GL_UNSIGNED_INT_8_8_8_8; break; case PixelFormat::r8g8b8a8: format = GL_RGBA, type = GL_UNSIGNED_INT_8_8_8_8; break; case PixelFormat::a8r8g8b8: format = GL_BGRA, type = GL_UNSIGNED_INT_8_8_8_8_REV; break; case PixelFormat::a8b8g8r8: format = GL_RGBA, type = GL_UNSIGNED_INT_8_8_8_8_REV; break; default: format = GL_BGRA, type = GL_UNSIGNED_INT_8_8_8_8; break; } //テクスチャ生成 glPixelStorei( GL_UNPACK_ALIGNMENT, 4 ); glGenTextures( 1, &instance ); glBindTexture( GL_TEXTURE_2D, instance ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0, format, type, &pixel[ 0 ] ); glBindTexture( GL_TEXTURE_2D, 0 ); } /** * デストラクタ */ OpenGLTexture::~OpenGLTexture() { glDeleteTextures( 1, &instance ); } /** * 文字列への変換。サイズを出力 */ String OpenGLTexture::to_string()const { std::stringstream ss; ss << size; return ss.str(); }
使い方
//適当なテクスチャを作成 std::vector< DWORD > pixel( 64 * 64 ); int index = 0; for( int y = 0; y < 64; y ++ ) { for( int x = 0; x < 64; x ++ ) { Color color; color.r = ( x & 63 ) / 63.0f; color.g = 0.0f; color.b = ( y & 63 ) / 63.0f; color.a = 1.0f; pixel[ index ] = color.to_a8r8g8b8(); index ++; } } OpenGLTexture texture( pixel, Size( 64, 64 ), PixelFormat::a8r8g8b8 ); //適当なポリゴンを描画 glEnable( GL_TEXTURE_2D ); //テクスチャマップを有効にする glBindTexture( GL_TEXTURE_2D, texture.GetInstance() ); //生成したテクスチャをバインド glPushMatrix(); glBegin( GL_QUADS ); //四角形ポリゴン //左上から時計回りの四角形 glTexCoord2f( 0.0f, 0.0f ); glVertex2f( 0.0f, 0.0f ); //左上 glTexCoord2f( 1.0f, 0.0f ); glVertex2f( 128.0f, 0.0f ); //右上 glTexCoord2f( 1.0f, 1.0f ); glVertex2f( 128.0f, 128.0f ); //右下 glTexCoord2f( 0.0f, 1.0f ); glVertex2f( 0.0f, 128.0f ); //左下 glEnd(); glPopMatrix(); glBindTexture( GL_TEXTURE_2D, 0 ); //テクスチャ解除 glDisable( GL_TEXTURE_2D ); //テクスチャマップ無効
ダウンロード
ゲームフレームワーク的なものを作る。まとめ。
とりあえず、ここまでのまとめ。
今後も週に1,2回程度のペースで追加していきます。
- ゲームフレームワーク的なものを作る。(20)〜2Dキーフレームアニメーション〜
- ゲームフレームワーク的なものを作る。(19)〜シーングラフ〜
- ゲームフレームワーク的なものを作る。(18)〜シリアライズ・デシリアライズ〜
- ゲームフレームワーク的なものを作る。(17)〜オブジェクトプール〜
- ゲームフレームワーク的なものを作る。(16)〜フェードイン・フェードアウト〜
- ゲームフレームワーク的なものを作る。(15)〜ゲームの状態遷移(シーン遷移)〜
- ゲームフレームワーク的なものを作る。(14)〜イベントハンドラ〜
- ゲームフレームワーク的なものを作る。(13)〜イベントキューを作る〜
- ゲームフレームワーク的なものを作る。(12)〜OpenGLで画像描画〜
- ゲームフレームワーク的なものを作る。(11)〜OpenGLで文字列描画〜
- ゲームフレームワーク的なものを作る。(10)〜OpenGLでテクスチャを使う〜 - while( c++ );
- std::unique_ptrでHDCとHGLRCを扱う。 - while( c++ );
- std::unique_ptrの使い方について。
- BufferObjectクラスを作る。 - while( c++ );
- OpenGLのバッファオブジェクトの使い方とBufferObjectクラスの実装について。
- OpenGLでSpriteBatchクラスを作る。 - while( c++ );
- 固定長のスプライト配列に溜めておいて一気に描画。
- std::unique_ptrでHDCとHGLRCを扱う。 - while( c++ );
- ゲームフレームワーク的なものを作る。(9)〜OpenGLの初期化〜 - while( c++ );
- ゲームフレームワーク的なものを作る。(8)〜描画エンジンの抽象化〜 - while( c++ );
- ゲームフレームワーク的なものを作る。(7)〜Textureインターフェイスとスプライト描画メソッド〜 - while( c++ );
- ゲームフレームワーク的なものを作る。(6)〜スプライト描画〜 - while( c++ );
- ゲームフレームワーク的なものを作る。(5)〜Point、Size、Rect、Color〜 - while( c++ );
- ゲームフレームワーク的なものを作る。(4)〜文字列描画〜 - while( c++ );
- ゲームフレームワーク的なものを作る。(3)〜Graphicsクラス〜 - while( c++ );
- ゲームフレームワーク的なものを作る。(2)〜Gameクラスに描画機能を追加〜 - while( c++ );
- ゲームフレームワーク的なものを作る。(1)〜Gameクラス〜 - while( c++ );
- windowsでゲームループを作る。(2)〜Windowクラス〜 - while( c++ );
- windowsでゲームループを作る。 - while( c++ );
ゲームフレームワーク的なものを作る。(9)〜OpenGLの初期化〜
今回はOpenGLを使った描画機能を追加していきます。久しぶりのOpenGLですね。
OpenGLの初期化
まず、OpenGLを利用するためのヘッダとライブラリをプロジェクトに追加します。VCをインストールすれば、標準で必要なファイルが含まれるので、特に新しくライブラリなどをインストールする必要はありません。
#include <gl/gl.h> #pragma comment( lib, "opengl32.lib" )
以下、初期化手順です。詳細はgoogle先生に聞きましょう。「opengl 初期化」
- 1.PIXELFORMATDESCRIPTOR構造体でウィンドウに関するピクセルフォーマット情報を指定。
- 2.ChoosePixelFormat()を呼び出して、デバイスコンテキストに最も適合するピクセルフォーマットを選択。
- 3.SetPixelFormat()を呼び出して、2で選んだピクセルフォーマットを実際に設定。
- 4.wglCreateContext()でOpenGLレンダリングコンテキストを作成。
- 5.作成したレンダリングコンテキストをカレントコンテキストに設定。
- 6.プログラム終了時にレンダリングコンテキストをカレントコンテキストから解除して、破棄。
初期化の前に、DeviceContextクラスを作っておく。
win32/wglでは頻繁にデバイスコンテキストを使うことになるので、クラス化しておくと便利です。
/** * ウィンドウのデバイスコンテキストのラッパー */ class DeviceContext : noncopyable { public: explicit DeviceContext( HWND hwnd ) : hdc( ::GetDC( hwnd ) ), hwnd( hwnd ) { } ~DeviceContext() { ::ReleaseDC( hwnd, hdc ); } //HDCへのキャスト operator HDC()const{ return hdc; } private: HDC hdc; HWND hwnd; };
Windowクラスにデバイスコンテキストを取得するメソッドを追加。
class Window { public: /** * DC取得 */ DeviceContext GetDeviceContext()const{ return DeviceContext( hwnd ); } /*略*/ };
あらためて、OpenGLの初期化処理を追加
class OpenGLView : public View { public: /** * コンストラクタ */ OpenGLView( const String& title, const Size& client_size ); /** * デストラクタ */ ~OpenGLView(); private: /** * OpenGLレンダリングコンテキストの生成 */ HGLRC CreateRenderingContext( const DeviceContext& dc ); private: DeviceContext device_context; HGLRC rendering_context; /*略*/ };
/** * コンストラクタ * * @param title ウィンドウのタイトル * @param client_size ウィンドウのクライアントサイズ */ OpenGLView::OpenGLView( const String& title, const Size& client_size ) : View( title, client_size ) //ウィンドウ生成 , device_context( GetDeviceContext() ) //ウィンドウのDCを取得 , rendering_context( CreateRenderingContext( device_context ) ) //RC生成 { ::wglMakeCurrent( device_context, rendering_context ); } /** * デストラクタ */ OpenGLView::~OpenGLView() { ::wglMakeCurrent( device_context, nullptr ); ::wglDeleteContext( rendering_context ); } /** * OpenGLレンダリングコンテキストの生成 * * @param dc デバイスコンテキスト */ HGLRC OpenGLView::CreateRenderingContext( const DeviceContext& dc ) { //PIXELFORMATDESCRIPTORにピクセルフォーマットを設定し、 //デバイスコンテキストにもっと適合するピクセルフォーマットを選択、設定後 //レンダリングコンテキストを生成する PIXELFORMATDESCRIPTOR pfd = { sizeof( PIXELFORMATDESCRIPTOR ) }; pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW // support window | PFD_SUPPORT_OPENGL // support OpenGL | PFD_DOUBLEBUFFER // double buffered ; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 32; pfd.cDepthBits = 16; pfd.iLayerType = PFD_MAIN_PLANE; int format = ::ChoosePixelFormat( dc, &pfd ); if( format == 0 ) throw std::runtime_error( "ChoosePixelFormat" ); if( !::SetPixelFormat( dc, format, &pfd ) ) throw std::runtime_error( "SetPixelFormat" ); return ::wglCreateContext( dc ); }
以上で初期化完了です。
画面を指定色で塗りつぶす。
正しく初期化できているか確認するために、とりあえず適当な色で画面を塗りつぶしてみましょう。
OpenGLViewクラスにClear()とUpdate()の実装を追加します。
/** * ViewのOpenGL実装 */ class OpenGLView : public View { /*略*/ // ------------------------------------------------------------------------------- // IGraphics実装 // ------------------------------------------------------------------------------- public: /** * シーンのクリア */ void Clear(); /** * シーンのレンダリング開始 */ bool BeginScene(); /** * シーンのレンダリング終了 */ void EndScene(); /** * 画面の更新 */ void Update(); };
/** * シーンのクリア */ void OpenGLView::Clear() { ::glClearColor( 0.1f, 0.1f, 0.1f, 1.0f ); //クリア色を指定 //::glClearDepth( 1.0f ); ::glClear( GL_COLOR_BUFFER_BIT /*| GL_DEPTH_BUFFER_BIT*/ ); } /** * シーンのレンダリング開始 */ bool OpenGLView::BeginScene() { return true; } /** * シーンのレンダリング終了 */ void OpenGLView::EndScene() { } /** * 画面の更新 */ void OpenGLView::Update() { ::SwapBuffers( device_context ); }
使い方
OpenGLViewのインスタンスを生成して、Gameクラスに渡すだけです。
/** * エントリーポイント */ int main() { Application::Initialize(); try { //ゲーム用ウィンドウの生成と描画エンジンの初期化 //Direct3DView view( "hoge", Size( 720, 480 ) ); //ウィンドウタイトルとクライアントサイズを指定 OpenGLView view( "hoge", Size( 720, 480 ) ); //ウィンドウタイトルとクライアントサイズを指定 //ゲームを初期化して、ゲームループを開始する Game game( &view ); //ゲームループ // ウィンドウが閉じられるまで1フレームの処理を繰り返す while( !view.IsClosed() ) { //1フレームの処理 game.Run(); //メッセージキューにある全てのwindowsイベントを処理する Application::DoEvents(); } } catch( std::exception& e ) { std::cout << e.what() << std::endl; } catch( ... ) { std::cout << "予期せぬ例外" << std::endl; } Application::Finalize(); return 0; }
ダウンロード
- framework09(OpenGLView).zip
- VC++2010 ExpressEdition、DirectX SDK(February 2010)で動作確認