複数のテクスチャを使う
これまでの解説では,テクスチャは1枚しか使用しませんでした(キューブマッピングでは6枚の画像を使用していますが,これは6枚まとめてひとつのテクスチャとして扱っています).シーン中でテクスチャが1枚しか使えないというのは,一見不便なように思えます.しかし,1枚のテクスチャを切り分けて使えば,シーン中の異なる部分に異なるテクスチャを貼り付けることができます.
複数のテクスチャを合成した画像ファイルを使う
テクスチャを切り分けて使うには,あらかじめ複数のテクスチャをつなぎ合わせて1枚の画像ファイルに書き込んでおく必要があります.たとえば立方体の6面に異なる画像を貼り付けてサイコロのような図形を表示したい場合,次のような画像を用意します(実際のサイズは 1024×128 画素).

画像ファイルの縦横のサイズは,例によって 2n 画素にしておきます.使わないところは,もったいないですが,余らしておきましょう.これを,この第2回でやったように,四角形の全面に貼り付けてみます.
すると,こんな具合になります.

各面にテクスチャの異なる部分を貼り付ける
画像の大きさが正方形でなくても,テクスチャ座標は縦横とも 0〜1 の範囲になるので,画像は長辺方向に圧縮されてしまいます.そこで,立方体の各面に異なるテクスチャを貼り付けるために,各面に対応したテクスチャの範囲をテクスチャから切り出すようにテクスチャ座標を取ります.

これを各面の頂点のテクスチャ座標に割り当てます.box.cpp で定義している変数 texcoord の初期値を次のように変更してください.
/* 頂点のテクスチャ座標 */
static const GLdouble texcoord[][4][2] = {
{ { 0.0, 1.0 }, { 0.125, 1.0 }, { 0.125, 0.0 }, { 0.0, 0.0 } },
{ { 0.125, 1.0 }, { 0.25, 1.0 }, { 0.25, 0.0 }, { 0.125, 0.0 } },
{ { 0.25, 1.0 }, { 0.375, 1.0 }, { 0.375, 0.0 }, { 0.25, 0.0 } },
{ { 0.375, 1.0 }, { 0.5, 1.0 }, { 0.5, 0.0 }, { 0.375, 0.0 } },
{ { 0.5, 1.0 }, { 0.625, 1.0 }, { 0.625, 0.0 }, { 0.5, 0.0 } },
{ { 0.625, 1.0 }, { 0.75, 1.0 }, { 0.75, 0.0 }, { 0.625, 0.0 } },
};
すると,ちゃんと6面に異なるテクスチャが貼り付けられ,サイコロらしくなります.

複数の画像ファイルを使う
このように複数のテクスチャを合成した画像ファイルを用意すれば,1枚のテクスチャでも異なるテクスチャを使い分けることができます.しかし,そのような画像をあらかじめ用意しておくのも手間ですし,利用できるテクスチャのサイズには限界があるので,その限界を超えてテクスチャを詰め込むこともできません.
そこで,既に割り当てているテクスチャの一部を別の画像で入れ替えるという手段が用意されています.こうすればプログラムの実行時にテクスチャを入れ替えて,(処理時間は余計にかかりますが)何枚のテクスチャでも使用可能になります.これには glTexSubImage2D() を使用します.main.cpp に以下の内容を追加します.
/*
** 初期化
*/
static void init()
{
/* テクスチャ画像はワード単位に詰め込まれている */
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
/* テクスチャの読み込みに使う配列 */
GLubyte texture[TEXHEIGHT * TEXWIDTH * 4];
FILE *fp;
/* テクスチャ画像の読み込み */
if ((fp = fopen(texture_file, "rb")) != NULL) {
fread(texture, sizeof texture, 1, fp);
fclose(fp);
}
else {
perror(texture_file);
}
/* テクスチャの割り当て */
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,
GL_RGBA, GL_UNSIGNED_BYTE, texture);
if ((fp = fopen("room2ny.raw", "rb")) != NULL) {
/* テクスチャ画像の読み込み */
fread(texture, 128 * 128 * 4, 1, fp);
fclose(fp);
/* テクスチャの置き換え */
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 128, 128,
GL_RGBA, GL_UNSIGNED_BYTE, texture);
}
- void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)
- 現在のテクスチャの一部を別の画像で置き換えます.引数
targetはGL_TEXTURE_2Dでないといけません.levelには MIPMAP を行う場合のテクスチャの解像度レベルを指定します.MIPMAP を行わない場合は 0 にしておいてください.xoffsetとyoffsetには,テクスチャを置き換える先のテクスチャ上の位置を指定します.widthとheightには置き換えるテクスチャの幅と高さを指定します.formatは引数pixelsに指定したメモリ上の画像の形式です.GL_RGBのほか,GL_RGBA,GL_COLOR_INDEX,GL_RED,GL_GREEN,GL_BLUE,GL_ALPHA,GL_LUMINANCE,GL_LUMINANCE_ALPHAが指定できます.typeには引数pixelの要素のデータ型を指定します.GL_UNSIGNED_BYTEは*pixelsがGLubyte(unsigned charと等価)であることを示します.他にGL_BYTE,GL_SHORT,GL_UNSIGNED_SHORT,GL_INT,GL_UNSIGNED_INT,GL_FLOAT,GL_BITMAPが指定できます.pixelsにはテクスチャの画像を格納したメモリ(配列)のポインタを指定します.
これは 2005 年の記事です。OpenGL (3.1以降 Core Profile) および OpenGL ES 2.0/3.0 以降では、
GL_ALPHA、GL_LUMINANCE、GL_LUMINANCE_ALPHA、GL_INTENSITY、GL_COLOR_INDEX、GL_BITMAPは廃止されました。またborderも意味を持たなくなりましたので、常に 0 を指定してください。詳しくは OpenGL 4.0 以降のglTexSubImage2D()のマニュアルを読んでください。

glTexSubImage2D() は,glTexImage2D() でテクスチャを割り当てた後,そのテクスチャの一部を別の画像で置き換えます.この際,置き換える画像のサイズは 2n である必要はありません.つまり,最初に glTexImage2D() で(2n のサイズの)テクスチャを割り当てておけば,glTexSubImage2D() を使って(割り当てたテクスチャより小さな)任意のサイズの画像をテクスチャとして利用できます.
それでは先ほど追加した部分を書き換えて,6面全部を置き換えてしまいましょう.まず、サイコロのテクスチャを読み込んでいる部分を #if 0 ~ #endif ではさんで無効にします。
/*
** 初期化
*/
static void init()
{
/* テクスチャ画像はワード単位に詰め込まれている */
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
/* テクスチャの読み込みに使う配列 */
GLubyte texture[TEXHEIGHT * TEXWIDTH * 4];
FILE *fp;
#if 0
/* テクスチャ画像の読み込み */
if ((fp = fopen(texture_file, "rb")) != NULL) {
fread(texture, sizeof texture, 1, fp);
fclose(fp);
}
else {
perror(texture_file);
}
#endif
これにより配列 texture には何もデータが入っていない状態(不定)になるので、glTexImage2D() の最後の引数を nullptr にして、データの転送を行わずテクスチャメモリの確保だけを行うようにします。
/* テクスチャの割り当て */
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
そして glTexSubImage() を使ってテクスチャを置き換える位置の x 座標値をファイルごとに 128 画素ずつずらしながら,テクスチャを置き換えてゆきます.
for (int i = 0; i < 6; ++i) {
/* テクスチャファイル名 */
static const char *textures[] = {
"room2ny.raw", /* 下 */
"room2nz.raw", /* 裏 */
"room2px.raw", /* 右 */
"room2pz.raw", /* 前 */
"room2nx.raw", /* 左 */
"room2py.raw", /* 上 */
};
if ((fp = fopen(textures[i], "rb")) != NULL) {
/* テクスチャ画像の読み込み */
fread(texture, 128 * 128 * 4, 1, fp);
fclose(fp);
/* テクスチャの置き換え */
glTexSubImage2D(GL_TEXTURE_2D, 0, i * 128, 0, 128, 128,
GL_RGBA, GL_UNSIGNED_BYTE, texture);
}
}
glTexSubImage2D() は実行時に動的にテクスチャを置き換えることができるので,これを使ってテクスチャのアニメーションなども実現できます.
