PNGでテクスチャ ― 2013/11/05
今回はタイトル通り、PNG画像の読み込みとテクスチャの設定・描画を行います。
PNGの読み込み、つまりはPNGデータを読み込んでテクスチャデータに展開するのですが、OpenGLやAndroid NDKにはそのような機能はありません。そこで、libpngというPNG読み込み(と書き込み)のライブラリを使用します。
ありがたいことにlibpngにはAndroid用がありますので、そちらを使わせていただきます。
libpng for Android
jni/sysの下に丸々コピーして、jni/Android.mkから jni/sys/libpng/jni/Android.mkをインクルードするようにします。これでlibpngがプロジェクトに組み込まれます。
それではテクスチャ管理クラス sys::Textureです。
PNGの読み込み部分(load_png)と、テクスチャの設定部分(make)があります。
Texture.h
Texture.cpp
・PNG読み込み(load_png)
大体は libpngの普通の使い方になっていますが、ちょっと変えているのは以下の2点。
libpngは基本的にデータをファイルから読み込むようになっていますが、メモリから読み込むように読み込みコールバックを差し替えています(76行目)。
また、ピクセルフォーマットを24ビットRGB(不透明)か、32ビットRGBA(α付き)に揃えています(84行目から)。
・テクスチャ設定(make)
glGenTexturesでテクスチャオブジェクトを作成して、glTexImage2Dでデータの設定という、これまた普通の使い方です。
GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_Tを GL_CLAMP_TO_EDGEに設定していますが、こうするとOpenGL ES 2.0ではテクスチャの幅や高さが2の累乗でなくても良くなるそうです。
テクスチャ描画のために、新しくシェーダプログラムを追加しています。
Renderer.cpp
シェーダが増えたことで切り替え処理など他にも変更点はあるのですが、ここでは省略します.
最後に実施にテクスチャを描画する部分です。
AppMain.cpp
初期化部でテクスチャを読み込み、稼働部で描画しています。
描画は生ポリゴンの時の処理に、テクスチャのバインドとUV座標の設定が追加された位です。四角形の描画なので、トライアングルストリップ(GL_TRIANGLE_STRIP)を使用しています。
PNGの読み込み、つまりはPNGデータを読み込んでテクスチャデータに展開するのですが、OpenGLやAndroid NDKにはそのような機能はありません。そこで、libpngというPNG読み込み(と書き込み)のライブラリを使用します。
ありがたいことにlibpngにはAndroid用がありますので、そちらを使わせていただきます。
libpng for Android
jni/sysの下に丸々コピーして、jni/Android.mkから jni/sys/libpng/jni/Android.mkをインクルードするようにします。これでlibpngがプロジェクトに組み込まれます。
それではテクスチャ管理クラス sys::Textureです。
PNGの読み込み部分(load_png)と、テクスチャの設定部分(make)があります。
Texture.h
#ifndef ___TEXTURE_H___ #define ___TEXTURE_H___ #include "common.h" #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> namespace sys { /**************** テクスチャ ****************/ class Texture { public : // ピクセルフォーマット enum { FORMAT_RGBA, FORMAT_RGB, }; GLuint texture; // テクスチャオブジェクト short format; // テクスチャフォーマット short width, height; // サイズ Texture(void) // コンストラクタ { texture = 0; } ~Texture() // デストラクタ { clear(); } void make(const u8*); // 作成 void clear(void); // 削除 void load_png(const u8*); // PNG読み込み }; } #endif /********************* End of File ********************************/
Texture.cpp
/*************************** テクスチャ ***************************/ #include "Texture.h" #include <libpng/jni/png.h> namespace sys { /***************************************** 作成 引数 data = テクスチャデータ *****************************************/ void Texture::make(const u8* data) { clear(); glGenTextures(1, &texture); // テクスチャオブジェクト作成 glBindTexture(GL_TEXTURE_2D, texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); switch ( format ) { case FORMAT_RGBA : glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); break; case FORMAT_RGB : glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); break; } } /********** 削除 **********/ void Texture::clear(void) { if ( texture ) { glDeleteTextures(1, &texture); texture = 0; } } /******************************** メモリ読み込みコールバック ********************************/ static void png_read(png_structp png_ptr, png_bytep data, png_size_t length) { u8** _p = (u8**)png_get_io_ptr(png_ptr); // データポインタ memcpy(data, *_p, length); *_p += length; } /********************************** PNG読み込み 引数 data = PNGデータ **********************************/ void Texture::load_png(const u8* data) { png_structp _png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); assert(_png); png_infop _info = png_create_info_struct(_png); assert(_info); png_set_read_fn(_png, NULL, png_read); png_init_io(_png, (png_FILE_p)&data); png_read_info(_png, _info); png_uint_32 _width, _height; int _bit_depth, _color_type; png_get_IHDR(_png, _info, &_width, &_height, &_bit_depth, &_color_type, NULL, NULL, NULL); switch ( _color_type ) { case PNG_COLOR_TYPE_RGB : format = FORMAT_RGB; break; case PNG_COLOR_TYPE_PALETTE : png_set_palette_to_rgb(_png); png_read_update_info(_png, _info); png_get_IHDR(_png, _info, &_width, &_height, &_bit_depth, &_color_type, NULL, NULL, NULL); format = (_color_type == PNG_COLOR_TYPE_RGB) ? FORMAT_RGB : FORMAT_RGBA; break; case PNG_COLOR_TYPE_GRAY : png_set_gray_to_rgb(_png); png_read_update_info(_png, _info); format = FORMAT_RGB; break; case PNG_COLOR_TYPE_GRAY_ALPHA : png_set_gray_to_rgb(_png); png_read_update_info(_png, _info); format = FORMAT_RGBA; break; default : format = FORMAT_RGBA; break; } width = (short)_width; height = (short)_height; int _row_bytes = width*((format == FORMAT_RGBA) ? 4 : 3); u8* _buf = (u8*)memalign(4, _row_bytes*height); png_bytep _rows[height]; assert(_buf != NULL); for (int i = 0; i < height; i++) { _rows[i] = _buf + i*_row_bytes; } png_read_image(_png, _rows); make(_buf); // テクスチャ作成 png_destroy_read_struct(&_png, &_info, NULL); free(_buf); } } /**************** End of File ******************************************/
・PNG読み込み(load_png)
大体は libpngの普通の使い方になっていますが、ちょっと変えているのは以下の2点。
libpngは基本的にデータをファイルから読み込むようになっていますが、メモリから読み込むように読み込みコールバックを差し替えています(76行目)。
また、ピクセルフォーマットを24ビットRGB(不透明)か、32ビットRGBA(α付き)に揃えています(84行目から)。
・テクスチャ設定(make)
glGenTexturesでテクスチャオブジェクトを作成して、glTexImage2Dでデータの設定という、これまた普通の使い方です。
GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_Tを GL_CLAMP_TO_EDGEに設定していますが、こうするとOpenGL ES 2.0ではテクスチャの幅や高さが2の累乗でなくても良くなるそうです。
テクスチャ描画のために、新しくシェーダプログラムを追加しています。
Renderer.cpp
{ // SHADER_TEXTURE(テクスチャ有り) static const char gVertexShader[] = // 頂点シェーダプログラム "attribute vec4 position;" "attribute vec4 color;" "varying vec4 vColor;" "attribute vec2 texcoord;" "varying vec2 vTexcoord;" "uniform mat4 projection;" "void main() {" "gl_Position = projection*position;" "vColor = color;" "vTexcoord = texcoord;" "}"; static const char gFragmentShader[] = // フラグメントシェーダプログラム "precision mediump float;" "varying vec4 vColor;" "varying vec2 vTexcoord;" "uniform sampler2D texture;" "void main() {" "gl_FragColor = texture2D(texture, vTexcoord)*vColor;" "}"; vert_shader = loadShader(GL_VERTEX_SHADER, gVertexShader); // 頂点シェーダ frag_shader = loadShader(GL_FRAGMENT_SHADER, gFragmentShader); // フラグメントシェーダ shader[SHADER_TEXTURE].init(vert_shader, frag_shader, TRUE); // シェーダプログラム作成 glDeleteShader(vert_shader); glDeleteShader(frag_shader); }
シェーダが増えたことで切り替え処理など他にも変更点はあるのですが、ここでは省略します.
最後に実施にテクスチャを描画する部分です。
AppMain.cpp
#include "common.h" #include "Renderer.h" #include "Texture.h" static sys::Texture texture; // テクスチャ /************ 初期化 ************/ void init_app(void) { u8* _buf = (u8*)sys::load_asset("photo.png"); // テクスチャ用PNG読み込み texture.load_png(_buf); // テクスチャ設定 free(_buf); } /****************************** 稼働 戻り値 アプリ続行か ******************************/ Bool update_app(void) { static const float texcoord[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f }; static const float vertex[] = { -320.0f, -480.0f, 320.0f, -480.0f, -320.0f, 480.0f, 320.0f, 480.0f, }; static const GLubyte color[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }; sys::ShaderProgram* _shader; _shader = sys::Renderer::use_shader(sys::Renderer::SHADER_TEXTURE); // テクスチャ用シェーダ glBindTexture(GL_TEXTURE_2D, texture.texture); // テクスチャ glVertexAttribPointer(_shader->texcoord, 2, GL_FLOAT, GL_FALSE, 0, texcoord); // UV座標 glVertexAttribPointer(_shader->position, 2, GL_FLOAT, GL_FALSE, 0, vertex); // 頂点座標 glVertexAttribPointer(_shader->color, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, color); // カラー glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); return TRUE; } /**************** End of File *************************************************/
初期化部でテクスチャを読み込み、稼働部で描画しています。
描画は生ポリゴンの時の処理に、テクスチャのバインドとUV座標の設定が追加された位です。四角形の描画なので、トライアングルストリップ(GL_TRIANGLE_STRIP)を使用しています。
プロジェクト一式はこちらです。
裏でこっそりフレームバッファ ― 2013/11/06
今までのサンプルではテクスチャなどを画面に直接描画していましたが、これを画面ではなくフレームバッファと呼ばれる自分で作成したバッファに描画することができます(画面に描画するのも、デフォルトのフレームバッファに描画するということらしいですが)。
そんなことをして何が嬉しいのかと言いますと…
昨日のサンプルでは描画しているテクスチャは一枚だけでした。これがテクスチャなどの枚数が増えてくると、描画にいくらかの時間がかかるようになります。そうすると描画途中の絵が画面に出ることになり、絵がちらついたりします。
フレームバッファを使用する場合、絵は一旦フレームバッファに描画して、全て描画した後に画面に表示します。描画途中の絵が画面に出ることはないのでちらつきはありません。オフスクリーンレンダリングと呼ばれる方法です。
フレームバッファクラス sys::FrameBufferを見てみます。
FrameBuffer.h
FrameBuffer.cpp
テクスチャとしてカラーバッファを持っていますので、sys::Textureの派生クラスになっています。普通はデプスバッファやステンシルバッファといったレンダーバッファを持っているものなんですが、ここではカラーバッファしか使いません。
作成(make)時には幅と高さを指定しています。今回のように画面全体のフレームバッファとして使う場合はサイズは仮想画面と同じになりますが、描画(されること)のできるテクスチャとして他にも使いようがありますのでサイズは指定できるようになっています。
描画時の座標をサイズに合わせるために、座標変換の行列はフレームバッファで持っておきます。
描画クラス sys::Rendererの変更部分です。
Renderer.cpp
まず初期化部分では、仮想画面サイズでフレームバッファを作成しています。
ビューポートの設定はフレームバッファの切り替えごとに行うので、ここでは値の計算だけしておきます。
今までは Rendererの処理はメインの処理前にだけ行っていましたが、最後にフレームバッファを画面に描画しなければならないので後にも処理を入れています。
プロジェクト一式は、こちらです。
そんなことをして何が嬉しいのかと言いますと…
昨日のサンプルでは描画しているテクスチャは一枚だけでした。これがテクスチャなどの枚数が増えてくると、描画にいくらかの時間がかかるようになります。そうすると描画途中の絵が画面に出ることになり、絵がちらついたりします。
フレームバッファを使用する場合、絵は一旦フレームバッファに描画して、全て描画した後に画面に表示します。描画途中の絵が画面に出ることはないのでちらつきはありません。オフスクリーンレンダリングと呼ばれる方法です。
フレームバッファクラス sys::FrameBufferを見てみます。
FrameBuffer.h
#ifndef ___FRAME_BUFFER_H___ #define ___FRAME_BUFFER_H___ #include "common.h" #include "Texture.h" namespace sys { /******************************** フレームバッファテクスチャ ********************************/ class FrameBuffer : public Texture { GLuint frame_buffer; // フレームバッファオブジェクト GLfloat mat_projection[4*4]; // 透視変換行列 public : FrameBuffer(void) // コンストラクタ { frame_buffer = 0; } FrameBuffer(int _w, int _h) { make(_w, _h); } ~FrameBuffer() // デストラクタ { clear(); } void make(int, int); // 作成 void clear(void); // 削除 void bind(void); // 使用 }; } #endif /********************* End of File ********************************/
FrameBuffer.cpp
/********************************************* フレームバッファテクスチャ *********************************************/ #include "FrameBuffer.h" #include "Renderer.h" namespace sys { /******************************** 作成 引数 _width = 幅 _height = 高さ ********************************/ void FrameBuffer::make(int _width, int _height) { glGenFramebuffers(1, &frame_buffer); // フレームバッファオブジェクト作成 glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer); format = FORMAT_RGBA; // テクスチャフォーマット width = _width; // 幅 height = _height; // 高さ Texture::make(NULL); // テクスチャ作成 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); // フレームバッファにアタッチ glBindFramebuffer(GL_FRAMEBUFFER, 0); for (int i = 0; i < 4*4; i++) { // 透視変換行列初期化 mat_projection[i] = 0.0f; } mat_projection[0] = 2.0f/width; mat_projection[5] = -2.0f/height; mat_projection[10] = 1.0f; mat_projection[15] = 1.0f; } /********** 削除 **********/ void FrameBuffer::clear(void) { if ( frame_buffer ) { glDeleteFramebuffers(1, &frame_buffer); frame_buffer = 0; Texture::clear(); } } /********** 使用 **********/ void FrameBuffer::bind(void) { glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer); glViewport(0, 0, width, height); // ビューポート設定 Renderer::mat_projection = mat_projection; // 透視変換行列設定 if ( Renderer::current_shader ) { glUniformMatrix4fv(Renderer::current_shader->projection, 1, GL_FALSE, mat_projection); } } } /**************** End of File ******************************************/
テクスチャとしてカラーバッファを持っていますので、sys::Textureの派生クラスになっています。普通はデプスバッファやステンシルバッファといったレンダーバッファを持っているものなんですが、ここではカラーバッファしか使いません。
作成(make)時には幅と高さを指定しています。今回のように画面全体のフレームバッファとして使う場合はサイズは仮想画面と同じになりますが、描画(されること)のできるテクスチャとして他にも使いようがありますのでサイズは指定できるようになっています。
描画時の座標をサイズに合わせるために、座標変換の行列はフレームバッファで持っておきます。
描画クラス sys::Rendererの変更部分です。
Renderer.cpp
/************************************************ 初期化 引数 width, height = 端末画面サイズ ************************************************/ void Renderer::init(int width, int height) { initShader(); // シェーダ初期化 frame_buffer = new FrameBuffer(SCREEN_WIDTH, SCREEN_HEIGHT); // フレームバッファ作成 if ( width*SCREEN_HEIGHT < height*SCREEN_WIDTH ) { // 横長(上下カット) screen_rect.w = width; screen_rect.h = width*SCREEN_HEIGHT/SCREEN_WIDTH; } else { // 縦長(左右カット) screen_rect.w = height*SCREEN_WIDTH/SCREEN_HEIGHT; screen_rect.h = height; } screen_rect.x = (width - screen_rect.w)/2; screen_rect.y = (height - screen_rect.h)/2; }
まず初期化部分では、仮想画面サイズでフレームバッファを作成しています。
ビューポートの設定はフレームバッファの切り替えごとに行うので、ここでは値の計算だけしておきます。
/******************** 稼働(前処理) ********************/ void Renderer::update(void) { glEnable(GL_BLEND); // αブレンド初期化 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); frame_buffer->bind(); // フレームバッファ使用 } /******************** 描画(後処理) ********************/ void Renderer::draw(void) { static const GLfloat _projection[4*4] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; static const GLfloat _texcoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f }; static const GLfloat _vertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; static const GLubyte _colors[] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }; ShaderProgram* _shader = use_shader(SHADER_TEXTURE); // フレームバッファテクスチャ描画 glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(screen_rect.x, screen_rect.y, screen_rect.w, screen_rect.h); glUniformMatrix4fv(_shader->projection, 1, GL_FALSE, _projection); glBindTexture(GL_TEXTURE_2D, frame_buffer->texture); glVertexAttribPointer(_shader->texcoord, 2, GL_FLOAT, GL_FALSE, 0, _texcoords); glVertexAttribPointer(_shader->position, 2, GL_FLOAT, GL_FALSE, 0, _vertices); glVertexAttribPointer(_shader->color, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, _colors); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }
今までは Rendererの処理はメインの処理前にだけ行っていましたが、最後にフレームバッファを画面に描画しなければならないので後にも処理を入れています。
プロジェクト一式は、こちらです。
閃の軌跡 再び ― 2013/11/07
新しいパッチでロードが早くなったということで、「閃の軌跡」の2週目を始めました。
まだ序盤ですが、確かに体感的に結構早くなっています。今までがひどかったので、やっと普通のゲーム並みというところでしょうか。最初からこのくらいで売ればいいのに。
1週目は先が気になるのと、街がだだっ広いのとであまり寄り道をせず進めましたが、今度はじっくりやってみようと思います。
今作で話は完結していないので、最後までじっくりやったところでどのみち先は気になるんですが。
【送料無料】【連続エントリーでポイント最大5倍】【初回特典】英雄伝説 閃の軌跡 通常版 PS Vi... |
続きがどうなるというより、どういう方向に進むのかもわからないような終わり方だったからなぁ…。
スプライトで描画 ― 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 *************************************************/
HTML5ゲーム:かえすがえす ― 2013/11/09
いつもAndroidのプログラミングの話をしていますが、HTML5なんかにも手を出していたりします。
投稿型ゲームサイト9leapで公開済みのものですが、にぎやかしということで制作物を紹介致します。
パズル
かえすがえす
・ルール
隣の足場へ移動すると両脇のパネルがひっくり返り、消えていたものは出現、出現していたものは消えます。
ただし、一度乗った足場は消えてしまい、もう一度乗ることはできません。
全てのパネルを出現させて、絵を完成させればクリアです。
・操作方法
キャラクタの上下左右をタッチして、その方向に足場があれば移動します。
移動をキャンセルする場合は、来た方向に戻ってください。
スリザーリンクの亜流みたいなものです。
問題はランダムで作っているので、ときどき「NORMAL」や「HARD」でもやたらと簡単だったりしますがご容赦ください。
プログラムの制作には、enchant.jsを使用しています。
投稿型ゲームサイト9leapで公開済みのものですが、にぎやかしということで制作物を紹介致します。
パズル
かえすがえす
・ルール
隣の足場へ移動すると両脇のパネルがひっくり返り、消えていたものは出現、出現していたものは消えます。
ただし、一度乗った足場は消えてしまい、もう一度乗ることはできません。
全てのパネルを出現させて、絵を完成させればクリアです。
・操作方法
キャラクタの上下左右をタッチして、その方向に足場があれば移動します。
移動をキャンセルする場合は、来た方向に戻ってください。
スリザーリンクの亜流みたいなものです。
問題はランダムで作っているので、ときどき「NORMAL」や「HARD」でもやたらと簡単だったりしますがご容赦ください。
プログラムの制作には、enchant.jsを使用しています。
最近のコメント