Windows では、(OpenGL 等を使用しない場合)BMP 画像はメモリに読み込んで BitBlt 関数等で
Android では、デバイスが多様で、画面解像度も画面サイズ(ピクセル数)も縦横比も想定できないので、 直接座標指定はしなく、画像も画面 1 ピクセルに対して 1 ドットに対応させません。
また、どうやら描画タイミングでは毎回、全画面再描画であり、Windows のように
ここでは、BMP 画像から 2D テクスチャを作成する方法について、検討しています。
画像データはすでにメモリに読み込まれているものとしています。 BMP ファイルからの読み込みについては 「24 ビット BMP 画像を読み込む」で検討しています。 メモリに RGB で読み込まれていればいいので、元のイメージデータは BMP でなくても関係ありません。
なお、OpenGL ES は 3D の描画にも対応しているようですが、ここでは 2D のみ扱っています。
2021 年 3 月 8 日、基本の流れは変わりませんが、文面などを見直しています。
このページ、および開発関連ページは、PC 向けデザインとなっております。 画面サイズの小さいスマホでは、快適な表示が得られませんので、ご了承ください。
ご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
また、本サイトが初めての方は、まずこのページの注意事項をご覧ください。
2D テクスチャを作成するための流れを、まずまとめておきます。
gl で始まる関数は OpenGL ES の関数で、 Android developers の 「GLES20」を参考にしています。
まず、Android システムから、未使用のテクスチャ ID を取得します。 テクスチャはアプリが管理するのではなく、システムが管理します。 テクスチャ ID は、システムがテクスチャを管理するための番号です。
glGenTextures 関数で、 m_textureID には GLuint 型、 すなわち符号なし整数値で、未使用のテクスチャ ID が設定されます。 1 未満の値が設定されたときは、空きテクスチャ ID がない、という意味です。 その場合は、この先の処理を行えません。
glGenTextures(1, &m_textureID);
空きテクスチャ ID を取得したら、それをglBindTexture 関数でバインド(アクティブに・選択状態に)します。
glBindTexture(GL_TEXTURE_2D, m_textureID);
テクスチャの描画方法等を指定します。あとで実行でもいいのかも知れません。 詳しくは次のセクションにある、setParamsRGBA 関数をご覧ください。
glTexImage2D 関数で、イメージを転送します。 イメージデータの詳細は「24 ビット BMP 画像を読み込む」に書いていますが、 m_width に画像の幅、 m_height に画像の高さ、 m_bits にビットデータが設定されているものとします。
glTexImage2D 関数を呼び出すことで、2次元のテクスチャが作成されます。
glTexImage2D( GL_TEXTURE_2D, // GLenum、または GL_PROXY_TEXTURE_2D 0, // GLint、詳細レベル番号 GL_RGBA, // GLint、テクスチャ内のカラー要素数 m_width, // GLsizei、幅(ピクセル単位、2のべき乗であること) m_height, // GLsizei、高さ(同) 0, // GLint、テクスチャの境界幅(0または1) GL_RGBA, // GLenum、描画時のフォーマット? GL_UNSIGNED_BYTE, // GLenum、データ型? m_bits // const GLvoid*、テクスチャに読み込む画像データ );
コメントに「?」が付いているところがありますが、とりあえずはこれでいいみたいです。
テクスチャ作成前に、以下の関数を、順次実行しています。
まずは、テクスチャのデータは、1 ピクセルが 4 バイトであることを設定しています。 RGB と、透過度を表すアルファチャンネル A です。
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
テクスチャの拡大・縮小には、近似カラーを使用してもらいます。 次のような指定でいいようですが、現時点では詳しく理解していません。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
テクスチャのサイズが不足している時は、リピート描画します(どう使われるのかはわかりません)。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
透過処理を使用する設定を行います。
設定がよくわからず、いろいろな値の組み合わせを試しましたが、これで希望通りの描画になりました。
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
私は
bool CaxTexture::setParamsRGBA(void)
という関数に上記処理をまとめて、これら関数を実行するようにしています。
なお、glTexEnvi 関数だけ、version 1.1 に定義されているようでした。
アプリ起動中ずっと使用する画像は起動時のスプラッシュ画面で読み込んでおけばいいのですが、 デバイスの限られたメモリを考慮すると、一時的に使う画像は、その都度読み込み、解放したほうがよいと思われます。
そこで、1 テクスチャを管理するクラスを作成し、それを複数持って、テクスチャ全体を管理しようと考えました。
1 テクスチャを管理するクラスのクラス名を CaxTexture として、次のような定義にしました。
class CaxTexture { public: /** コンストラクター */ CaxTexture(); /** デストラクター */ ~CaxTexture(); /** イメージが読み込み済みなら true を返します。 */ bool isImageLoaded(void); /** テクスチャ画像の幅を返します。 */ int32_t getWidth(void) { return m_width; } /** テクスチャ画像の高さを返します。 */ int32_t getHeight(void) { return m_height; }
続けて、2D テクスチャの作成の流れに沿った関数です。
/** * テクスチャIDが未設定のとき、設定します。 * glGenTextures 関数を呼び出します。 * すでに設定済みのときは、何もしません。 * 設定されたテクスチャIDを返します(0のとき失敗です)。 */ GLuint generateID(void); /** * テクスチャIDをバインドします。 * glBindTexture( GL_TEXTURE_2D ) でバインドします。 * テクスチャIDが無効のときは、何もしません(falseを返します)。 */ bool bind(void); /** * RGBA画像用パラメータを設定します。 * 現在バインドされているテクスチャに対応します。 * テクスチャIDが無効のときは、何もしません(falseを返します)。 */ bool setParamsRGBA(void);
続けて、イメージを管理する関数です。
/** assets から指定のBMP画像を読み込み、RGBAデータとしてメモリに格納します。 */ void loadBmpRGBA(AAssetManager* AssetManager, const char* filename); /** assets から指定のPNG画像を読み込み、RGBAデータとしてメモリに格納します。 */ void loadPngRGBA(AAssetManager* AssetManager, const char* filename); /** * 読み込んだイメージで、2Dテクスチャを作成します。 * テクスチャIDが無効のときは、何もしません(falseを返します)。 * また、イメージが未設定のときも、何もしません(falseを返します)。 */ bool createImage2D(void); /** * 読み込んだイメージデータを破棄します。 * 作成されたテクスチャは破棄されません。 * テクスチャを破棄するには、invalidateTextureID を呼び出します。 */ void deleteBits(void); /** * 管理しているテクスチャIDをクリアします。 * メモリにあるテクスチャが解放されます。 */ void invalidateTextureID(void);
PNG イメージを読み込む loadPngRGBA 関数も定義されていますが、ここでは扱っていません。
最後に、メンバ変数です。
protected: /** テクスチャID */ GLuint m_textureID; /** 画像データビット */ GLubyte* m_bits; /** 読み込み済みかどうか */ bool m_loaded; /** テクスチャ画像のサイズ(ピクセル) */ int32_t m_width, m_height; };
クラスの実装に触れる前に、このクラスをどのように呼び出して使用するかについて、記述します。
CaxTexture クラスのオブジェクト texture に、 assets フォルダから指定のビットマップファイルを読み込む上位の関数は、次のようになっています。
bool loadBmpTexture(struct android_app* app, CaxTexture& texture, const char* filename) { if (texture.generateID() < 1) { // テクスチャIDを取得 return false; } if (!texture.isImageLoaded()) { // テクスチャ作成前の場合のみ texture.bind(); texture.setParamsRGBA(); AAssetManager* mgr = app->activity->assetManager; // アセットマネージャーを取得 texture.loadBmpRGBA(mgr, filename); texture.createImage2D(); } return true; }
コンストラクターでは、各パラメーターを初期化しています。
CaxTexture::CaxTexture() : m_textureID(0) // テクスチャID , m_bits(NULL) // 画像ビットデータ , m_loaded(false) // 読み込み済みかどうか , m_width(0) , m_height(0) { }
デストラクターでは、テクスチャが解放されていることを保証しています。
CaxTexture::~CaxTexture() { deleteBits(); // 念のため確保したメモリを解放 invalidateTextureID(); // テクスチャを破棄 }
generateID 関数は、テクスチャ ID 未取得の場合、システムから空きテクスチャ ID を取得します。
コンストラクターで m_textureID を0にしていますので、その場合に限り、空きテクスチャ ID を要求します。 取得できたら、その値を m_textureID に設定します。 すでにテクスチャ ID を取得済みの場合は、何もしません。
GLuint CaxTexture::generateID(void) { if (m_textureID < 1) { // 未設定の時のみ glGenTextures(1, &m_textureID); // 空きテクスチャIDを取得 if (m_textureID < 1) { // 空きがないとき return 0; // 失敗します } } return m_textureID; }
isImageLoaded 関数は、テクスチャがすでに作成されているかどうかを返します。
コンストラクターで m_loaded を false に設定していますので、初期状態では false が返ります。 テクスチャが作成されたとき、m_loaded が true に設定され、この関数は true を返します。
bool CaxTexture::isImageLoaded(void) { return m_loaded; }
bind 関数は、クラスが保持しているテクスチャ ID m_texture をバインド(選択)します。
bool CaxTexture::bind(void) { if (m_textureID < 1) { // テクスチャIDが未設定? return false; } glBindTexture(GL_TEXTURE_2D, m_textureID); return true; }
createImage2D 関数は、 イメージのビットデータ m_bits とサイズ情報 m_width, m_height を用いて、 2次元テクスチャを作成します。
bool CaxTexture::createImage2D(void) { if (m_textureID < 1) { // テクスチャIDが未設定? return false; } // イメージが読み込まれていない場合は、テクスチャを作成できません。 if (!m_bits) { return false; } // 2次元のテクスチャを作成します。 glTexImage2D( GL_TEXTURE_2D, // GLenum、または GL_PROXY_TEXTURE_2D 0, // GLint、詳細レベル番号 GL_RGBA, // GLint、テクスチャ内のカラー要素数 m_width, // GLsizei、幅(ピクセル単位、2のべき乗であること) m_height, // GLsizei、高さ(同) 0, // GLint、テクスチャの境界幅(0または1) GL_RGBA, // GLenum、描画時のフォーマット? GL_UNSIGNED_BYTE, // GLenum、データ型? m_bits // const GLvoid*、テクスチャに読み込む画像データ ); // テクスチャは作成されたと考えられます。 m_loaded = true; // ビット情報はもう不要なので削除します。 deleteBits(); return true; }
deleteBits 関数は、ビットデータのために確保した m_bits のメモリを解放します。
void CaxTexture::deleteBits(void) { if (m_bits) { delete[] m_bits; m_bits = NULL; } }
deleteBits 関数は、ビットデータを破棄しますが、テクスチャは破棄されません。 テクスチャを破棄するには、このinvalidateTextureID 関数を呼び出す必要があります。
void CaxTexture::invalidateTextureID(void) { if (m_textureID > 0) { glDeleteTextures(1, &m_textureID); m_textureID = 0; } m_loaded = false; }
loadBmpRGBA 関数は、 プロジェクトのパッケージの assets フォルダに入れた BMP ファイルをメモリに読み込み、解析して、 CaxTexture クラスの m_width と m_height に画像のサイズを、 m_bits に(必要なサイズのメモリを確保して)画像のビットデータをセットするものです。
詳細は、「OpenGL ES:24 ビット BMP 画像を読み込む」をご覧ください。
改めて、CaxTexture クラスには loadPngRGBA という関数も用意しています。 これは、BMP 画像ではなく PNG 画像を読み込んでテクスチャを作成するものです。 PNG 画像を読めないと、透過(アルファチャンネル)付き画像を扱えないので、必須と言っても過言ではないのですが・・・ これがなかなか厄介ですので、ここでは触れません。
イメージデータを読み込み、2D テクスチャを作成できたら、 次はテクスチャを貼り付ける面を作成して、いよいよ貼り付けを行います。
OpenGL ES で 2D テクスチャ描画
OpenGL ES で 2D テクスチャ描画
作成された 2D テクスチャを画面に描画するまでの手順について、書いています。
Android 開発に関する記事をまとめた Android 開発トップ もご覧ください。