▼最新の記事はこちらです
nn-hokuson.hatenablog.com
テクスチャを表示する
今回は、ついにテクスチャを表示してみます。テクスチャは基本的にそれ単体で表示できるものではなく、ビルやキャラクタなどポリゴンの表面に貼り付けることで初めて表示できるようになります。特に板ポリゴンに貼り付けたものをスプライトと呼び2Dゲームなどでよく使われます。
OpenGLでのテクスチャの扱い
OpenGLでは下図のように、テクスチャ画像をテクスチャIDという数値(GLuint型)で管理します。OpenGL内部ではテクスチャID⇔テクスチャデータの対応付けが行われており、テクスチャIDを通してどのテクスチャを描画するかを指定します。例えば花のテクスチャが描画したい場合は、花のテクスチャ画像を指定するのではなく、テクスチャIDである「2番」を指定します。
では、もう少し詳しくテクスチャを描画する流れを見てみましょう。まず最初にやることは、OpenGLから空いているテクスチャIDを教えてもらいます。上記の例では、テクスチャIDの3番以降が空いているので、テクスチャIDとして3を返してくれます。次に、表示したいテクスチャファイルをプログラム上に読み込み、そのデータを配列中に保存します。その読み込んだデータと先ほどのテクスチャID=3をプログラム上で紐付けたあと、テクスチャの各種設定を済ませれば下準備は完成です。
描画する際には、まずはじめに描画したいテクスチャの範囲を指定します。次にテクスチャIDを指定して「これからコレコレのIDのテクスチャを描画しますよ〜」ということをOpenGLに教えてやります。その上でOpenGLのDraw関数を呼ぶことで、指定したテクスチャを使った描画が完了します。それを踏まえて、今回のプログラムを見て行きましょう。
#include <GL/glfw.h> #include <cstdlib> #include <cmath> #include <fstream> #include <vector> #include <cassert> const int g_windowWidth = 640; const int g_windowHeight = 480; GLuint g_texID; void render() { static const GLfloat vtx[] = { 200, 120, 440, 120, 440, 360, 200, 360, }; glVertexPointer(2, GL_FLOAT, 0, vtx); // Step5. テクスチャの領域指定 static const GLfloat texuv[] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, }; glTexCoordPointer(2, GL_FLOAT, 0, texuv); // Step6. テクスチャの画像指定 glBindTexture(GL_TEXTURE_2D, g_texID); // Step7. テクスチャの描画 glEnable(GL_TEXTURE_2D); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glDrawArrays(GL_QUADS, 0, 4); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glDisable(GL_TEXTURE_2D); } void setupTexture( GLuint texID, const char *file, const int width, const int height) { // Step2. 画像データのロード std::ifstream fstr(file, std::ios::binary); assert(fstr); const size_t fileSize = static_cast<size_t>(fstr.seekg(0, fstr.end).tellg()); fstr.seekg(0, fstr.beg); std::vector<char> textureBuffer(fileSize); fstr.read(&textureBuffer[0], fileSize); // Step3. 画像データとテクスチャiDを結びつける glBindTexture(GL_TEXTURE_2D, texID); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, &textureBuffer[0]); // Step4. テクスチャの各種設定 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); } int main() { if( !glfwInit() ){ return -1; } if( !glfwOpenWindow(g_windowWidth, g_windowHeight, 0, 0, 0, 0, 0, 0, GLFW_WINDOW)) { return -1; } // モニタとの同期 glfwSwapInterval(1); // 描画範囲の指定 glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0f, g_windowWidth, 0.0f, g_windowHeight, -1.0f, 1.0f); // Step1. テクスチャのロード glGenTextures(1, &g_texID); setupTexture( g_texID, "sample.raw", 256, 256); // ゲームループ while (glfwGetWindowParam(GLFW_OPENED)) { // 画面の初期化 glClearColor(0.5f, 0.5f, 0.5f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); render(); // バッファの入れ替え glfwSwapBuffers(); } glfwTerminate(); return 0; }
テクスチャの準備
今回は、main文中でテクスチャIDを取得する部分と、テクスチャの準備を行うsetupTexture関数、テクスチャの描画を行うrender関数に変更を加えています。上記のプログラムを実行すると下記のような表示になると思います。では各ステップを詳しく見て行きましょう。
Step.1 空いているテクスチャIDを教えてもらう
まず初めにmain文中でglGenTexturesを使って、空いてるテクスチャID番号をOpenGLに問い合わせ、g_texIDに詰めて返してもらいます。glGenTexturesの第一引数には欲しい「空きテクスチャID」の個数を入れます。複数のテクスチャIDを教えてもらいたい場合には第二引数に配列を渡してやります。
Step.2 画像データのロード
setupTexture関数の中で、指定した画像データをtextureBufferの配列にロードしています。このへんはC++でファイル読み込みをする際の常套手段なので、何度か書いて覚えちゃいましょう。今回は、画像ロードのプログラムが極力短く簡単になるようにraw形式の画像ファイルを読み込んでいます。実際にはJPGやPNGが読み込めると便利なのですが、それはまたいつか。。。
Step.3 テクスチャIDと画像データを対応付ける
次の、 glBindTextureとglTexImage2Dの2つの関数を利用して、テクスチャIDとテクスチャデータの対応付けを行っています。まず最初に、glBindTexture関数を使って、引数に与えたテクスチャIDにフォーカスを与えます(次の行以降で指定したテクスチャIDの操作を行うことをOpenGLに伝えます)その状態でglTexImage2Dに、先ほどロードしたtextureBufferを引数として渡してやることで、テクスチャIDとテクスチャデータを対応付けることが出来ます。
ここで注意しておきたいのが、glTexImage2Dの第三引数と第七引数に与えているGL_RGBAというパラメータです。ここはファイルからロードした画像ファイルのチャネル数を指定します。今回のRARファイルはアルファレイヤを含む4チャネルあるのでRGBAを指定してみます。JPGやBMPの場合にはアルファレイヤを含まないのでGL_RGBを指定して下さい。ココの指定を間違うと、写りの悪いブラウン管テレビみたいな画像が表示されます(笑)
Step.4 テクスチャの各種設定
次にglTexParameteriが4つ書いてありますが、ここではテクスチャの各種設定を行っています。具体的にはテクスチャをマッピングした時のデータの保管方法と、テクスチャ座標外のテクスチャの扱い方を指定しています。うーーん、このへんもなかなかややこしいですねー。。。あまり詳しく書きすぎると頭がパンクしちゃいますし、あまり頻繁に利用する所でもないので、この部分はまた必要に応じて説明したいと思います。
テクスチャの描画
Step.5 テクスチャの領域指定
ここからは、テクスチャ描画時の設定なので、render関数内の説明になります。テクスチャを描画する際、まずはテクスチャのどの部分を描画するかを指定します。一枚絵のテクスチャでは分かりにくいかもしれませんが、フォントテクスチャ(下図左)やアニメーションテクスチャ(下図右)などは一部を切り取って使用することが多いので、テクスチャの領域指定が必要になります。フォントテクスチャなどは後ほど説明したいと思います。
どの部分を使用するかは、テクスチャの左上を原点とする、s, t座標系で指定します。今回はテクスチャの全面を使用するので、-1 < s < 1, -1 < t < 1 の範囲で指定しています。この際、頂点座標と対応するようにテクスチャ座標も指定して下さい。要するに、頂点座標は左下の(200, 120)から反時計回りに指定しているので、テクスチャ座標も左下の(0, 1)から反時計回りに指定しています。描画するテクスチャ座標の範囲を配列として作成し、そのテクスチャ座標をglTexCoordPointerに渡すことで、OpenGLにもテクスチャ座標の配列を流し込みます。
Step.6 テクスチャの画像指定
範囲指定の次は使用するテクスチャを指定します。テクスチャの準備のところでも出てきましたが、glBindTexture関数を利用して「これから、どのテクスチャを使おうとしているのか?」をOpenGLに伝えます。使用するテクスチャの指定には、テクスチャデータに対応したテクスチャIDを引数として与えます。
Step.7 テクスチャの描画
最後にテクスチャとそれを貼り付けるポリゴンの描画を行います。テクスチャの描画は glEnableでテクスチャの描画を有効にし、glEnableClientStateでテクスチャ座標配列を有効(テクスチャ座標配列を使用することをOpenGLに伝える)にしています。これらの関数を呼んだあとにglDrawArray関数を呼ぶことで、テクスチャ付きポリゴンが描画されます。
まとめ
今回はテクスチャの描画方法を説明しました。これまでと比べると使用した関数の数も多かったので、かなり難しかったのではないでしょうか。ただ、OpenGL内部でどのようにテクスチャが扱われているのかさえ分かれば、あとはテクスチャ描画までの流れをつかむだけなので比較的簡単に理解できると思いますー。
参考図書
「マルチプラットフォームのためのOpenGL ES入門」
「ゲームプログラマになる前に覚えておきたい技術」
この本は「ゲームをどのように作るのか」といった薄っぺらい内容ではなく、ゲームを動かすための技術を1から理解できるように解説してくれています。そして、この本にはゲーム開発にとどまらず、プログラマに必要な知識がたくさーん詰まっています。C++の基礎からキャッシュの話とか浮動小数点演算の話などのちょっと難しい話、最適化手法の話までもが書かれているので、プログラマならぜったい読むべき一冊ですね。そしてページ数も872ページとボリューミィ!分厚い参考書はいい本です!内部的に使われているのはDirectXですが、基本的にそれを意識する必要は内容に解説されているので、絶対に一度は読んでおきたい一冊です。
「OpenGLで作るiPhone SDKゲームプログラミング」
この本は2D〜3Dまでサンプルがとても充実しているので、ゲームを作りはじめようという時に非常に良い取っ掛かりになります!また、パーティクルや衝突判定などゲームには欠かせない話もしっかりと説明されています!