ETC1圧縮テクスチャ ― 2013/11/13
OpenGLの中でもAndroidで共通に使える圧縮テクスチャ、ETC1に対応します。
圧縮テクスチャは、画像を圧縮した状態(サイズが小さい)でVRAMに置いておくテクスチャです。
PNG等の圧縮画像フォーマットは展開して元のサイズに戻してVRAMに転送しますが、ETC等の圧縮テクスチャではVRAM上でも小さいままなので、
・VRAMを占有するサイズが小さくなる
・描画時、転送サイズが小さいので高速
というメリットがあります。
そのかわり、
・不可逆圧縮なので、画質が落ちる
・ETC1圧縮テクスチャではαに未対応で、不透明画像のみ
といったデメリットもあります。
Android SDKでは etc1tool({sdkのフォルダ}/tools/etc1tool.exe)というPNG画像をETC1に変換するツールがありますので、そのツールで出力されるPKMフォーマットに対応します。
Texture.cpp
読み込み部分です。
ヘッダから幅と高さを取得して、データをそのまま作成関数に送っています。
"圧縮テクスチャ"というフォーマットのデータであってデータ自体は圧縮されていないので、展開処理は必要ありません。逆にいうと、データ自体にはまだ圧縮の余地があるということになります。サンプルでは assetsのzip圧縮に任せちゃっていますが。
テクスチャ作成部分です。
画像転送が glTexImage2Dから glCompressedTexImage2Dに代わり、フォーマットに GL_ETC1_RGB8_OESを指定しています。
あとは、読み込み部分でPNGとPKMに分けたり、キャッシュのサイズ計算でPKMに対応したりと細かな変更はありますが、ソースは省略します。
見た目はたいして変わらないので、スクリーンショットも省略していますが、
※ビー玉の画像は「かわいいフリー素材集 いらすとや」から使わせていただいています。どうもありがとうございます。
プロジェクト一式は、こちらから。
圧縮テクスチャは、画像を圧縮した状態(サイズが小さい)でVRAMに置いておくテクスチャです。
PNG等の圧縮画像フォーマットは展開して元のサイズに戻してVRAMに転送しますが、ETC等の圧縮テクスチャではVRAM上でも小さいままなので、
・VRAMを占有するサイズが小さくなる
・描画時、転送サイズが小さいので高速
というメリットがあります。
そのかわり、
・不可逆圧縮なので、画質が落ちる
・ETC1圧縮テクスチャではαに未対応で、不透明画像のみ
といったデメリットもあります。
Android SDKでは etc1tool({sdkのフォルダ}/tools/etc1tool.exe)というPNG画像をETC1に変換するツールがありますので、そのツールで出力されるPKMフォーマットに対応します。
Texture.cpp
/********************************** PKM読み込み 引数 data = PKMデータ **********************************/ void Texture::load_pkm(const u8* data) { format = FORMAT_ETC; width = (u16)(((u16)data[8] << 8) | data[9]); height = (u16)(((u16)data[10] << 8) | data[11]); make(data + 0x10); }
読み込み部分です。
ヘッダから幅と高さを取得して、データをそのまま作成関数に送っています。
"圧縮テクスチャ"というフォーマットのデータであってデータ自体は圧縮されていないので、展開処理は必要ありません。逆にいうと、データ自体にはまだ圧縮の余地があるということになります。サンプルでは assetsのzip圧縮に任せちゃっていますが。
/***************************************** 作成 引数 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; case FORMAT_ETC : glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC1_RGB8_OES, width, height, 0, width*height/2, data); break; default : assert(FALSE); break; } }
テクスチャ作成部分です。
画像転送が glTexImage2Dから glCompressedTexImage2Dに代わり、フォーマットに GL_ETC1_RGB8_OESを指定しています。
あとは、読み込み部分でPNGとPKMに分けたり、キャッシュのサイズ計算でPKMに対応したりと細かな変更はありますが、ソースは省略します。
見た目はたいして変わらないので、スクリーンショットも省略していますが、
※ビー玉の画像は「かわいいフリー素材集 いらすとや」から使わせていただいています。どうもありがとうございます。
プロジェクト一式は、こちらから。
気になるポーズ(閃の軌跡) ― 2013/11/14
今作からポリゴン表示となった「閃の軌跡」。
モデリングはまあ良いとして、モーションのパターンが少ないのが気になります。
ときどき見られる、立っているときに腰に手をあてるポーズ。会話の時に手持ち無沙汰なのか、よくこの仕草をします。
モデリングはまあ良いとして、モーションのパターンが少ないのが気になります。
ときどき見られる、立っているときに腰に手をあてるポーズ。会話の時に手持ち無沙汰なのか、よくこの仕草をします。
これが、先生や先輩のような目上の人間を相手に話しているときにもするので、どうも違和感があります。
この世界の文化では特に問題ないということなのでしょうが、日本人にはなんか失礼な態度に見えてしまいます。
あくまで"貴族"がするポーズなのかと見ていましたが、"平民"や"おのぼりさん"もしていました。しかも揃って…
ルパン三世 TVスペシャル ― 2013/11/15
…録れてなかった。
レコーダーの「おまかせ録画」におまかせしていたのが間違いだった。
しかし、キーワード「ルパン三世」でも引っかからないとはどういうこった。
ちゃんと見ていないんじゃ、批判もできやしない。
レコーダーの「おまかせ録画」におまかせしていたのが間違いだった。
しかし、キーワード「ルパン三世」でも引っかからないとはどういうこった。
ちゃんと見ていないんじゃ、批判もできやしない。
タッチパネル入力 その1 ― 2013/11/16
画面への出力がひと通り落ち着いたので、次はタッチパネルからの入力を行います。
タッチパネルの入力情報は java側で受け取ります。
まず一番上にあるビューに通知され、そこでスルーされると次のビュー、最後にはアクティビティに通知されるようになっています。
ここでは一つだけのビュー、サーフェスビューで処理します。
また、せっかくなのでマルチタッチに対応してみます。
BaseActivity.java
サーフェスビュー BaseView内
画面をタッチする、離す等のタッチイベントが起こると onTouchEventが呼ばれます。
Android SDKでダブルタップ等の動作を判定することもできるのですが、ここでは
・画面を押しているか
・押している場合、その位置
だけを取得して、後の処理は native側に任せることとします。
取得した状態は、どのみち nativeはフレーム単位で動いているのでイベントが起きる度ではなく、updateNativeのタイミングで送るようにします。
レンダラー BaseRenderer内
最新の状態だけを送るので、炎のコマで秒間200万回入力しても無視されます。
native側の処理は、また次回に。
タッチパネルの入力情報は java側で受け取ります。
まず一番上にあるビューに通知され、そこでスルーされると次のビュー、最後にはアクティビティに通知されるようになっています。
ここでは一つだけのビュー、サーフェスビューで処理します。
また、せっかくなのでマルチタッチに対応してみます。
BaseActivity.java
private short[] touch_status = new short[5*3]; // タッチパネル状態
サーフェスビュー BaseView内
/******************** タッチイベント ********************/ public boolean onTouchEvent(final MotionEvent event) { if ( future == null ) { return false; } int _action = event.getAction(); int _index, _id; switch ( _action & MotionEvent.ACTION_MASK ) { case MotionEvent.ACTION_DOWN : case MotionEvent.ACTION_POINTER_DOWN : _index = (_action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; _id = event.getPointerId(_index)*3; touch_status[_id + 1] = (short)event.getX(_index); // X座標 touch_status[_id + 2] = (short)event.getY(_index); // Y座標 touch_status[_id + 0] = 1; // タッチ中 break; case MotionEvent.ACTION_MOVE : for (_index = 0; _index < event.getPointerCount(); _index++) { _id = event.getPointerId(_index)*3; touch_status[_id + 1] = (short)event.getX(_index); // X座標 touch_status[_id + 2] = (short)event.getY(_index); // Y座標 touch_status[_id + 0] = 1; // タッチ中 } break; case MotionEvent.ACTION_UP : case MotionEvent.ACTION_POINTER_UP : _index = (_action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; _id = event.getPointerId(_index)*3; touch_status[_id + 0] = 0; // 非タッチ break; } return true; }
画面をタッチする、離す等のタッチイベントが起こると onTouchEventが呼ばれます。
Android SDKでダブルタップ等の動作を判定することもできるのですが、ここでは
・画面を押しているか
・押している場合、その位置
だけを取得して、後の処理は native側に任せることとします。
取得した状態は、どのみち nativeはフレーム単位で動いているのでイベントが起きる度ではなく、updateNativeのタイミングで送るようにします。
レンダラー BaseRenderer内
public void onDrawFrame(GL10 gl) { synchronized (renderer) { if ( (future != null) && !updateNative(touch_status) ) { // native部稼働 finish(); } } }
最新の状態だけを送るので、炎のコマで秒間200万回入力しても無視されます。
native側の処理は、また次回に。
タッチパネル入力 その2 ― 2013/11/17
前回に引き続きタッチパネルからの入力、今回は native側の処理を行います。
タッチパネル入力管理クラス sys::TouchManagerです。
TouchPanel.h
TouchPanel.cpp
マルチタッチ対応のため、配列 TouchPanelで複数のインスタンスを処理します。
フレーム毎の処理 updateでは javaで取得した状態を受け取り、各メンバに格納します。
タッチしているときは、flagのビットTOUCHを立てます。
タッチ位置座標は画面描画と同じ、(-SCREEN_WIDTH/2, -SCREEN_HEIGHT/2) - (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)になるように変換します。
基本的にはこれだけですが、他によく使う状態を計算しておきます。
flagのビット
・TRIG 離した状態からタッチしたとき
・RELEASE タッチした状態から離したとき
・REPEAT TRIGと同じですが、押しっぱなしのとき一定間隔でビットが立ちます
move_x, move_y
タッチしているときの前フレームからの移動距離
次に、TouchManagerを呼び出すシステム部分です。
SysMain.cpp
初期化・終了部分は、それぞれ init_managerと quit_managerを呼んでいるだけなので省略しています。
稼働部分では javaから受け取った jbyteArrayから GetPrimitiveArrayCriticalで配列のポインタを取得して TouchManagerに渡しています。
※javaから配列を渡すときはいくつか方法があり、GetPrimitiveArrayCriticalはそのままポインタを渡す(らしい)ので高速なのですが、その分いろいろと制限がありますので使うときは気をつけてください。
サンプルのタッチパネルテスト部分です。
タッチしたところにビー玉の絵が表示されます。
AppMain.cpp
タッチパネル入力管理クラス sys::TouchManagerです。
TouchPanel.h
/******************************** タッチパネル ********************************/ #ifndef ___TOUCH_PANEL_H___ #define ___TOUCH_PANEL_H___ #include "common.h" namespace sys { /********************** タッチパネル管理 **********************/ class TouchManager { public : static void init_manager(void); // 管理システム初期化 static void quit_manager(void); // 管理システム終了 static void update_manager(short const*); // 管理システム稼働 private : int repeat_cnt; // リピート用カウンタ int prev_x, prev_y; // 前回のタッチ位置 public : static const int TOUCH_MAX = 5; // マルチタッチ数 enum { TOUCH = (1 << 0), // タッチ TRIG = (1 << 1), // 初回タッチ REPEAT = (1 << 2), // リピート RELEASE = (1 << 3), // リリース }; u32 flag; // タッチ状態フラグ int x, y; // タッチ位置 int move_x, move_y; // 移動距離 TouchManager(void) // コンストラクタ { flag = 0x00; } void update(short const*); // 稼働 }; extern TouchManager* TouchPanel; } #endif /*************** End of File ****************************************/
TouchPanel.cpp
/*********************************** タッチパネル管理 ***********************************/ #include "TouchPanel.h" #include "Renderer.h" namespace sys { TouchManager* TouchPanel; /************ 初期化 ************/ void TouchManager::init_manager(void) { TouchPanel = new TouchManager[TOUCH_MAX]; } /********** 終了 **********/ void TouchManager::quit_manager(void) { delete[] TouchPanel; } /*********************************************** 稼働 引数 _status[0] = タッチしているか _status[1] = X座標 _status[2] = Y座標 ***********************************************/ void TouchManager::update_manager(short const* _status) { for (int i = 0; i < TOUCH_MAX; i++) { TouchPanel[i].update(_status); _status += 3; } } void TouchManager::update(short const* _status) { if ( _status[0] ) { // タッチ中 // タッチ位置補正 x = (_status[1] - sys::Renderer::screen_rect.x)*SCREEN_WIDTH/sys::Renderer::screen_rect.w - SCREEN_WIDTH/2; y = (_status[2] - sys::Renderer::screen_rect.y)*SCREEN_HEIGHT/sys::Renderer::screen_rect.h - SCREEN_HEIGHT/2; if ( !(flag & TOUCH) ) { // 初回 flag = TOUCH | TRIG | REPEAT; repeat_cnt = TOUCH_REPEAT1; move_x = 0; move_y = 0; prev_x = x; prev_y = y; } else { // 継続 flag = TOUCH; if ( --repeat_cnt == 0 ) { // リピート flag |= REPEAT; repeat_cnt = TOUCH_REPEAT2; } move_x = x - prev_x; move_y = y - prev_y; prev_x = x; prev_y = y; } } else { // 非タッチ flag = (flag & TOUCH) ? RELEASE : 0; } } } /*************** End of File *****************************************/
マルチタッチ対応のため、配列 TouchPanelで複数のインスタンスを処理します。
フレーム毎の処理 updateでは javaで取得した状態を受け取り、各メンバに格納します。
タッチしているときは、flagのビットTOUCHを立てます。
タッチ位置座標は画面描画と同じ、(-SCREEN_WIDTH/2, -SCREEN_HEIGHT/2) - (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)になるように変換します。
基本的にはこれだけですが、他によく使う状態を計算しておきます。
flagのビット
・TRIG 離した状態からタッチしたとき
・RELEASE タッチした状態から離したとき
・REPEAT TRIGと同じですが、押しっぱなしのとき一定間隔でビットが立ちます
move_x, move_y
タッチしているときの前フレームからの移動距離
次に、TouchManagerを呼び出すシステム部分です。
SysMain.cpp
/********** 稼働 **********/ JNIEXPORT jboolean JNICALL Java_sys_BaseActivity_updateNative(JNIEnv* env, jobject, jbyteArray touch) { Renderer::update(); // 描画管理稼働 jbyte* dst = (jbyte*)env->GetPrimitiveArrayCritical(touch, NULL); TouchManager::update_manager((short*)dst); // タッチパネル管理稼働 env->ReleasePrimitiveArrayCritical(touch, dst, 0); if ( update_app() ) { // アプリメイン稼働 Renderer::draw(); // 描画後処理 return JNI_TRUE; } init_flag = FALSE; return JNI_FALSE; // アプリ終了 }
初期化・終了部分は、それぞれ init_managerと quit_managerを呼んでいるだけなので省略しています。
稼働部分では javaから受け取った jbyteArrayから GetPrimitiveArrayCriticalで配列のポインタを取得して TouchManagerに渡しています。
※javaから配列を渡すときはいくつか方法があり、GetPrimitiveArrayCriticalはそのままポインタを渡す(らしい)ので高速なのですが、その分いろいろと制限がありますので使うときは気をつけてください。
サンプルのタッチパネルテスト部分です。
タッチしたところにビー玉の絵が表示されます。
AppMain.cpp
for (int i = 0; i < sys::TouchManager::TOUCH_MAX; i++) { // タッチパネルテスト if ( sys::TouchPanel[i].flag & sys::TouchManager::TOUCH ) { sprite[SPR_BALL_BLUE + (i % 4)].draw(sys::TouchPanel[i].x, sys::TouchPanel[i].y, 3.0f + sinf((cnt % 60)*M_PI*2/60)*2.0f); } } cnt++;
最近のコメント