スプライトで描画 ― 2013/11/08
テクスチャで画像を描画できるようになりましたが、毎度OpenGLを直接叩くのでは手順が面倒です。
描画を管理するスプライトクラスを作って、手軽に描画できるようにしましょう。
・スプライトクラス(sys::Sprite)
スプライトはテクスチャと、描画する範囲(切り抜き)から成っています。つまり1枚のテクスチャには複数のスプライトの画像を収めることができます(描画時、テクスチャの切り替えにはコストがかかるので、なるべく少ないテクスチャにまとめておいた方が効率が良いです)。
Sprite.h
Sprite.cpp
・初期化
void set(Texture* _tex, SRect const* _coord, int _origin)
void set(Texture* _tex, int _origin)
テクスチャ _tex、描画範囲座標 _coordでスプライトを初期化します。描画範囲を省略すると、テクスチャ自体と同じ大きさ(1枚のテクスチャ = 1枚のスプライト)になります。
_originはスプライトの原点を指定します。省略すると画像の中心が原点になります(sys::Sprite::X_CENTER | sys::Sprite::Y_CENTER)。
void set(short _width, short _height, int _origin)
テクスチャを指定しないと、幅 _width、高さ _heightの単色ポリゴンになります。
・描画
void draw(float _x, float _y)
座標(_x, _y)に描画します。
void draw(float _x, float _y, float _sx, float _sy)
縦と横の拡大率を指定して描画します。
void Sprite::draw(float _x, float _y, float _sx, float _sy, float _rot)
角度 _rot(単位:ラジアン)で回転して描画します。
void draw(float const* vertex)
直接4頂点(vertex[4][XY])を指定して描画します。
さて描画時に頂点座標をOpenGLに設定しますが、このデータをスプライト自体に持っておくと1度に1枚のスプライトしか描画できなくなります。よってRendererの方で汎用的なバッファを持っておいて、描画の度にそこから使用するようにします。
なお、UV座標はスプライトごとに固定なのでスプライト側で持っておきます。
Rendererの汎用的なプリミティブバッファの管理部分です。
def.hにバッファサイズ PRIM_BUF_SIZEを定義しておきます。
Renderer.cpp
また、テクスチャのバインドなど変化していないのに設定し直すのは効率が悪いので、現在の値を覚えておくようにします。
ただし、頂点は無条件で設定しています。全く同じ座標に何枚もプリミティブを描画することは少ないと思われますので。
それでは、実際の使用例です。
AppMain.cpp
描画を管理するスプライトクラスを作って、手軽に描画できるようにしましょう。
・スプライトクラス(sys::Sprite)
スプライトはテクスチャと、描画する範囲(切り抜き)から成っています。つまり1枚のテクスチャには複数のスプライトの画像を収めることができます(描画時、テクスチャの切り替えにはコストがかかるので、なるべく少ないテクスチャにまとめておいた方が効率が良いです)。
Sprite.h
#ifndef ___SPRITE_H___ #define ___SPRITE_H___ #include "common.h" #include "Texture.h" namespace sys { /**************** スプライト ****************/ class Sprite { protected : static u32 const* spr_color; // カラー public : // 原点位置 enum { X_CENTER = 0x00, // センタリング X_LEFT = 0x01, // 左揃え X_RIGHT = 0x02, // 右揃え Y_CENTER = 0x00, // センタリング Y_TOP = 0x10, // 上揃え Y_BOTTOM = 0x20, // 下揃え }; static void set_color(u32 _color = 0xffffffff); // カラー設定 static void set_color(u8 _r, u8 _g, u8 _b, u8 _a = 0xff) { set_color(RGBA(_r, _g, _b, _a)); } static void set_color(u32 const*); Texture* texture; // テクスチャ GLfloat texcoord[4][XY]; // UV座標 short width, height; // 大きさ short ox, oy; // 原点 void set(Texture*, SRect const*, int _origin = X_CENTER | Y_CENTER); // 設定 void set(Texture*, int _origin = X_CENTER | Y_CENTER); // 設定(テクスチャに合わせた大きさ) void set(short, short, int _origin = X_CENTER | Y_CENTER); // 設定(テクスチャ無し) void set(short, const void*, int _origin = X_CENTER | Y_CENTER); void set_origin(int _origin = X_CENTER | Y_CENTER); // 原点位置設定 void draw(GLfloat*); // 描画 void draw(float, float); void draw(float, float, float, float); void draw(float _x, float _y, float _scl) { draw(_x, _y, _scl, _scl); } void draw(float, float, float, float, float); void draw(float const*); }; } #endif /****************** End of File ***************************************/
Sprite.cpp
/******************************* スプライト *******************************/ #include "Sprite.h" #include "Renderer.h" namespace sys { u32 const* Sprite::spr_color; // カラー /************************************** 設定 引数 _tex = テクスチャ _coord = UV座標 _origin = 原点位置 **************************************/ void Sprite::set(Texture* _tex, SRect const* _coord, int _origin) { texture = _tex; // テクスチャ texcoord[0][X] = texcoord[2][X] = (GLfloat)_coord->x/_tex->width; // UV座標 texcoord[0][Y] = texcoord[1][Y] = (GLfloat)_coord->y/_tex->height; texcoord[1][X] = texcoord[3][X] = (GLfloat)(_coord->x + _coord->w)/_tex->width; texcoord[2][Y] = texcoord[3][Y] = (GLfloat)(_coord->y + _coord->h)/_tex->height; width = _coord->w; // 幅 height = _coord->h; // 高さ set_origin(_origin); // 原点位置設定 } void Sprite::set(Texture* _tex, int _origin) { texture = _tex; // テクスチャ texcoord[0][X] = texcoord[2][X] = 0.0f; // UV座標 texcoord[0][Y] = texcoord[1][Y] = 0.0f; texcoord[1][X] = texcoord[3][X] = 1.0f; texcoord[2][Y] = texcoord[3][Y] = 1.0f; width = _tex->width; // 幅 height = _tex->height; // 高さ set_origin(_origin); // 原点位置設定 } void Sprite::set(short _width, short _height, int _origin) { texture = NULL; // テクスチャ無し width = _width; // 幅 height = _height; // 高さ set_origin(_origin); // 原点位置設定 } /************************************ 原点位置設定 引数 _origin = 原点位置 ************************************/ void Sprite::set_origin(int _origin) { switch ( _origin & 0x0f ) { case X_CENTER : // センタリング ox = width/2; break; case X_LEFT : // 左揃え ox = 0; break; case X_RIGHT : // 右揃え ox = width; break; } switch ( _origin & 0xf0 ) { case Y_CENTER : // センタリング oy = height/2; break; case Y_TOP : // 上揃え oy = 0; break; case Y_BOTTOM : // 下揃え oy = height; break; } } /*********************************** カラー設定 引数 _color = カラー値 ***********************************/ void Sprite::set_color(u32 _color) { if ( _color == 0xffffffff ) { // デフォルト static const u32 color_white[] = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }; spr_color = color_white; } else if ( (_color != spr_color[0]) || (_color != spr_color[1]) || (_color != spr_color[2]) || (_color != spr_color[3]) ) { u32* _p = (u32*)Renderer::get_prim_buffer(sizeof(u32)*4); // カラーバッファ _p[0] = _color; _p[1] = _color; _p[2] = _color; _p[3] = _color; spr_color = _p; } } void Sprite::set_color(u32 const* _color) { u32* _p = (u32*)Renderer::get_prim_buffer(sizeof(u32)*4); // カラーバッファ _p[0] = _color[0]; _p[1] = _color[1]; _p[2] = _color[2]; _p[3] = _color[3]; spr_color = _p; } /*********************************** 描画 引数 _x, _y = 位置 _sx, _sy = 拡大率 _rot = 回転角 ***********************************/ void Sprite::draw(GLfloat* _vertex) { if ( texture ) { // テクスチャ有り Renderer::use_shader(Renderer::SHADER_TEXTURE); // シェーダ texture->bind(); // テクスチャ Renderer::set_texcoord(&texcoord[0][0]); // UV座標 } else { // テクスチャ無し Renderer::use_shader(Renderer::SHADER_PLAIN); // シェーダ } sys::Renderer::set_vertex(_vertex); // 頂点 Renderer::set_color((GLubyte*)spr_color); // カラー glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 描画 } void Sprite::draw(float _x, float _y) { GLfloat* _vertex = (GLfloat*)Renderer::get_prim_buffer(sizeof(GLfloat)*4*XY); // 頂点バッファ _vertex[0*XY + X] = _vertex[2*XY + X] = _x - ox; _vertex[0*XY + Y] = _vertex[1*XY + Y] = _y - oy; _vertex[1*XY + X] = _vertex[3*XY + X] = _x + (width - ox); _vertex[2*XY + Y] = _vertex[3*XY + Y] = _y + (height - oy); draw(_vertex); } void Sprite::draw(float _x, float _y, float _sx, float _sy) { GLfloat* _vertex = (GLfloat*)Renderer::get_prim_buffer(sizeof(GLfloat)*4*XY); // 頂点バッファ _vertex[0*XY + X] = _vertex[2*XY + X] = _x - _sx*ox; _vertex[0*XY + Y] = _vertex[1*XY + Y] = _y - _sy*oy; _vertex[1*XY + X] = _vertex[3*XY + X] = _x + _sx*(width - ox); _vertex[2*XY + Y] = _vertex[3*XY + Y] = _y + _sy*(height - oy); draw(_vertex); } void Sprite::draw(float _x, float _y, float _sx, float _sy, float _rot) { GLfloat* _vertex = (GLfloat*)Renderer::get_prim_buffer(sizeof(GLfloat)*4*XY); // 頂点バッファ float _cos = cosf(_rot), _sin = sinf(_rot), _cx0, _sx0, _cy0, _sy0, _cx1, _sx1, _cy1, _sy1; _cx0 = -_sx*ox; _sx0 = _sin*_cx0; _cx0 *= _cos; _cy0 = -_sy*oy; _sy0 = _sin*_cy0; _cy0 *= _cos; _cx1 = _sx*(width - ox); _sx1 = _sin*_cx1; _cx1 *= _cos; _cy1 = _sy*(height - oy); _sy1 = _sin*_cy1; _cy1 *= _cos; _vertex[0*XY + X] = _x + _cx0 - _sy0; _vertex[0*XY + Y] = _y + _cy0 + _sx0; _vertex[1*XY + X] = _x + _cx1 - _sy0; _vertex[1*XY + Y] = _y + _cy0 + _sx1; _vertex[2*XY + X] = _x + _cx0 - _sy1; _vertex[2*XY + Y] = _y + _cy1 + _sx0; _vertex[3*XY + X] = _x + _cx1 - _sy1; _vertex[3*XY + Y] = _y + _cy1 + _sx1; draw(_vertex); } void Sprite::draw(float const* vertex) { GLfloat* _vertex = (GLfloat*)Renderer::get_prim_buffer(sizeof(GLfloat)*4*XY); // 頂点バッファ for (int i = 0; i < 4*XY; i++) { _vertex[i] = vertex[i]; } draw(_vertex); } } /******************* End of File *******************************************************/
・初期化
void set(Texture* _tex, SRect const* _coord, int _origin)
void set(Texture* _tex, int _origin)
テクスチャ _tex、描画範囲座標 _coordでスプライトを初期化します。描画範囲を省略すると、テクスチャ自体と同じ大きさ(1枚のテクスチャ = 1枚のスプライト)になります。
_originはスプライトの原点を指定します。省略すると画像の中心が原点になります(sys::Sprite::X_CENTER | sys::Sprite::Y_CENTER)。
void set(short _width, short _height, int _origin)
テクスチャを指定しないと、幅 _width、高さ _heightの単色ポリゴンになります。
・描画
void draw(float _x, float _y)
座標(_x, _y)に描画します。
void draw(float _x, float _y, float _sx, float _sy)
縦と横の拡大率を指定して描画します。
void Sprite::draw(float _x, float _y, float _sx, float _sy, float _rot)
角度 _rot(単位:ラジアン)で回転して描画します。
void draw(float const* vertex)
直接4頂点(vertex[4][XY])を指定して描画します。
さて描画時に頂点座標をOpenGLに設定しますが、このデータをスプライト自体に持っておくと1度に1枚のスプライトしか描画できなくなります。よってRendererの方で汎用的なバッファを持っておいて、描画の度にそこから使用するようにします。
なお、UV座標はスプライトごとに固定なのでスプライト側で持っておきます。
Rendererの汎用的なプリミティブバッファの管理部分です。
def.hにバッファサイズ PRIM_BUF_SIZEを定義しておきます。
Renderer.cpp
prim_buffer = (u8*)memalign(4, PRIM_BUF_SIZE); // プリミティブ用汎用バッファ prim_p = 0;
/**************************************** プリミティブバッファ取得 引数 _size = 使用サイズ 戻り値 バッファ ****************************************/ void* Renderer::get_prim_buffer(u32 _size) { if ( prim_p + _size > PRIM_BUF_SIZE ) { prim_p = 0; } void* _ret = (void*)(prim_buffer + prim_p); prim_p += _size; return _ret; }
また、テクスチャのバインドなど変化していないのに設定し直すのは効率が悪いので、現在の値を覚えておくようにします。
ただし、頂点は無条件で設定しています。全く同じ座標に何枚もプリミティブを描画することは少ないと思われますので。
/********************************************** テクスチャ使用 引数 tex = テクスチャオブジェクト **********************************************/ void Renderer::bind_texture(GLuint tex) { if ( tex != current_texture ) { glBindTexture(GL_TEXTURE_2D, tex); current_texture = tex; } } /******************************** カラー設定 引数 color = カラー ********************************/ void Renderer::set_color(GLubyte const* color) { if ( color != current_color ) { glVertexAttribPointer(current_shader->color, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, color); current_color = color; } } /******************************** テクスチャUV座標設定 引数 coord = UV座標 ********************************/ void Renderer::set_texcoord(GLfloat const* coord) { if ( coord != current_texcoord ) { glVertexAttribPointer(current_shader->texcoord, 2, GL_FLOAT, GL_FALSE, 0, coord); current_texcoord = coord; } } /*********************************** 頂点座標設定 引数 vertex = 頂点座標 ***********************************/ void Renderer::set_vertex(GLfloat const* vertex) { glVertexAttribPointer(current_shader->position, 2, GL_FLOAT, GL_FALSE, 0, vertex); }
それでは、実際の使用例です。
AppMain.cpp
#include "common.h" #include "Sprite.h" // テクスチャ enum { TEX_PHOTO = 0, // 背景 TEX_BALL, // ビー玉 TEX_MAX, }; // スプライト enum { SPR_PHOTO = 0, // 背景 SPR_BALL_BLUE, // 青ビー玉 SPR_BALL_GREEN, // 緑ビー玉 SPR_BALL_RED, // 赤ビー玉 SPR_BALL_YELLOW, // 黄ビー玉 SPR_MAX, }; static sys::Texture texture[TEX_MAX]; // テクスチャ static sys::Sprite sprite[SPR_MAX]; // スプライト static int cnt; // カウンタ /************ 初期化 ************/ void init_app(void) { static const char* tex_name[TEX_MAX] = { "photo.png", "biidama.png", }; u8* _buf; for (int i = 0; i < TEX_MAX; i++) { // テクスチャ読み込み _buf = (u8*)sys::load_asset(tex_name[i]); texture[i].load_png(_buf); free(_buf); } struct SprDef { short tex_num; // テクスチャ番号 SRect coord; // UV座標 }; static const SprDef spr_def[SPR_MAX] = { {TEX_PHOTO, { 0, 0, 640, 960}}, // 背景 {TEX_BALL, { 0, 0, 60, 60}}, // 青ビー玉 {TEX_BALL, { 60, 0, 60, 60}}, // 緑ビー玉 {TEX_BALL, { 0, 60, 60, 60}}, // 赤ビー玉 {TEX_BALL, { 60, 60, 60, 60}}, // 黄ビー玉 }; for (int i = 0; i < SPR_MAX; i++) { // スプライト設定 sprite[i].set(&texture[spr_def[i].tex_num], &spr_def[i].coord); } cnt = 0; // カウンタ } /****************************** 稼働 戻り値 アプリ続行か ******************************/ Bool update_app(void) { sprite[SPR_PHOTO].draw(0.0f, 0.0f); // 背景 for (int i = 0; i < 4; i++) { // ビー玉 int t = ((cnt + i*15) % 60) - 30; sprite[SPR_BALL_BLUE + i].draw(i*128 - 192, 400 - (30*30 - t*t)/2); } cnt++; return TRUE; } /**************** End of File *************************************************/
最近のコメント