■ 2005年01月21日 [OpenGL][テクスチャ] 第12回 キューブマッピング
しろうさぎ
いかん,また気分が落ち込んできました.こういうときは城北橋のしろうさぎのシュークリームでも買って帰ろうと思います(追記:買って帰りました).ちなみに,ここの和風のお弁当もお気に入りです.塩を減らすために酢を使ったような煮物の味付けにはまりました.ただ,お昼に買いに出るにはちょっと遠いので,めったに食べられないのが残念です.
キューブマッピング
キューブマッピングは環境(周囲の情景)に六面体に貼り付けたテクスチャを使って環境マッピングを行う手法です.これは OpenGL 1.3 で標準機能に取り入れられました.
キューブマッピングでは,平面のスクリーンに投影した像をテクスチャに用います.スフィアマッピングのように変形する必要はありません.その代わり,テクスチャに用いる画像は6枚必要になります.
それでは試しにやってみましょう.今回は前々回に作ったスフィアマッピングのプログラムを雛形にします.
しかし,こうやって前に作ったプログラムをベースにすると,修正箇所がどんどん累積していって,プログラムがとても読みにくくなってしまいますね.やっぱり素直な雛形プログラムを毎回用意したほうがいいかなぁ.
Windows の場合
キューブマッピングは OpenGL 1.3 で標準機能に取り入れられたので,Windows の VC++ 6.0 に含まれている gl.h では,それに必要な記号定数が定義されていません.この場合は SGI の OpenGL® Sample Implementation にある glext.h を使ってください.
#include <stdio.h> #include <stdlib.h> #include <math.h> #if defined(WIN32) //# pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") # include "glut.h" # include "glext.h" #elif defined(__APPLE__) || defined(MACOSX) # include <GLUT/glut.h> #else # include <GL/glut.h> #endif ・・・
なお,Vine Linux 3.1 および Mac OS X 10.3 では,キューブマッピングに使う記号定数が gl.h で定義されているようです.ただ,ビデオコントローラに ATI RAGE 128 を使っている自宅の iMac G3 400MHz は,キューブマッピングは機能しませんでした.OpenGL のバージョンは 1.1 だそうです(泣).やっぱり,バージョンや拡張機能のチェックは必須ですね.
テクスチャの読み込み
2次元テクスチャの場合は,1枚の画像を GL_TEXTURE_2D というターゲットに割り当てました.これに対してキューブマッピングでは,6枚の画像をそれぞれ以下の6つのターゲットに割り当てます.
- GL_TEXTURE_CUBE_MAP_NEGATIVE_X
- GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
- GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
- GL_TEXTURE_CUBE_MAP_POSITIVE_X
- GL_TEXTURE_CUBE_MAP_POSITIVE_Y
- GL_TEXTURE_CUBE_MAP_POSITIVE_Z
これだけのターゲットをいちいち指定するのは面倒なので,これらの定数を一旦配列に格納しておくことにします.
・・・
/*
** テクスチャ
*/
#define TEXWIDTH 256 /* テクスチャの幅 */
#define TEXHEIGHT 256 /* テクスチャの高さ */
static const char texture1[] = "room.raw"; /* テクスチャファイル名 */
static const GLenum target[] = { /* テクスチャのターゲット名 */
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
};
・・・
そして,この一つ一つのターゲットに glTexImage2D() を使って画像を割り当てます.この部分はループを使って書くことにします.
・・・
/*
** 初期化
*/
static void init(void)
{
/* テクスチャの読み込みに使う配列 */
GLubyte texture[TEXHEIGHT][TEXWIDTH][4];
FILE *fp;
/* テクスチャ画像の読み込み */
if ((fp = fopen(texture1, "rb")) != NULL) {
fread(texture, sizeof texture, 1, fp);
fclose(fp);
}
else {
perror(texture1);
}
/* テクスチャ画像はワード単位に詰め込まれている */
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
for (int i = 0; i < 6; ++i) {
glTexImage2D(target[i], 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,
GL_RGBA, GL_UNSIGNED_BYTE, texture);
}
そのほかの部分にある GL_TEXTURE_2D は,GL_TEXTURE_CUBE_MAP に置き換えます.
/* テクスチャを拡大・縮小する方法の指定 */ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); /* テクスチャの繰り返し方法の指定 */ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP); ・・・
また,テクスチャ座標の生成モードに GL_REFLECTION_MAP を指定します.キューブマッピングではスフィアマッピングと異なり,(s, t, r) の3つの座標についてテクスチャ座標を生成します.
・・・ /* テクスチャ座標生成関数の設定 */ glTexGendv(GL_S, GL_OBJECT_PLANE, genfunc[0]); glTexGendv(GL_T, GL_OBJECT_PLANE, genfunc[1]); glTexGendv(GL_R, GL_OBJECT_PLANE, genfunc[2]); glTexGendv(GL_Q, GL_OBJECT_PLANE, genfunc[3]); #else /* キューブマッピング用のテクスチャ座標を生成する */ glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); #endif /* アルファテストの判別関数 */ glAlphaFunc(GL_GREATER, 0.5); ・・・
シーンの描画時には,s, t に加えて r 座標に対するテクスチャ座標の自動生成を有効にします.そしてキューブマッピングによるテクスチャマッピングを有効にします.
・・・
/*
** シーンの描画
*/
static void scene(void)
{
static const GLfloat color[] = { 1.0, 1.0, 1.0, 1.0 }; /* 材質 (色) */
/* 材質の設定 */
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
/* アルファテスト開始 */
glEnable(GL_ALPHA_TEST);
/* テクスチャマッピング開始 */
glEnable(GL_TEXTURE_CUBE_MAP);
/* テクスチャの自動生成を有効にする */
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
#if 0
glEnable(GL_TEXTURE_GEN_Q);
#endif
#if 0
/* 箱を描く */
box(1.0, 1.0, 1.0);
#else
/* ティーポットを描く */
glutSolidTeapot(1.0);
#endif
/* テクスチャの自動生成を無効にする */
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_GEN_R);
#if 0
glDisable(GL_TEXTURE_GEN_Q);
#endif
/* テクスチャマッピング終了 */
glDisable(GL_TEXTURE_CUBE_MAP);
/* アルファテスト終了 */
glDisable(GL_ALPHA_TEST);
}
・・・
ここまでできたら,プログラムをコンパイルして実行してみてください.
今は1枚のスフィアマッピング用の画像をキューブマッピング用の6つのターゲットにそのまま割り当てているので,こういう画像が表示されます.上,下,右,左,および手前の5つのテクスチャがティーポットに映り込んでいます.
回転を止めよう
しかし,このプログラムは例によってテクスチャを回転していますから,またわけがわかんないことになっています.ややこしいので,一旦この回転を止めることにします.テクスチャ行列を設定してテクスチャを回している処理をすべて #if 0 〜 #endif ではさんでしまいます.
・・・
static void display(void)
{
#if 0
/* フレーム数をカウントして時間として使う */
static int frame = 0; /* フレーム数 */
double t = (double)frame / (double)FRAMES; /* 時間とともに 0→1 に変化 */
if (++frame >= FRAMES) frame = 0;
/* テクスチャ行列の設定 */
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glTranslated(0.5, 0.5, 0.0);
glRotated(t * 360.0, 0.0, 0.0, 1.0);
#if 0
glScaled(0.5, 0.5, 1.0);
gluPerspective(60.0, 1.0, 1.0, 100.0);
gluLookAt(0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
#else
glTranslated(-0.5, -0.5, 0.0);
#endif
#endif
・・・
キューブマッピング用のテクスチャ
さて,キューブマッピング用のテクスチャをどうやって用意するか,考えてみます.これはカメラを三脚に乗せて,上下左右前後の6方向の写真を撮影し,それぞれが周辺部分で連続するようにトリミングすればできそうです.でも気分が落ち込んでいると,その程度の手間すら煩わしく感じます.
それぞれのテクスチャには平面のスクリーンへの投影像を用いればいいので,これらはコンピュータに作ってもらうことにします.これをシーンのレンダリング時に動的に作成するというのが OpenGL などによるリアルタイムレンダリングの醍醐味なのですが,そんな説明は将来に回すとして(そこまで行き着くかなぁ?),ここでは CG ソフト (LightWave) で作った下の画像を使ってください.
この画像はカメラの位置を固定し,画角を 90°に設定して,6つの方向に向けてレンダリングしたものです.いずれの画像も正方形(縦横の画素数が同じ)である必要があります.6枚あるので,room2.lzh というファイルにまとめてあります.まず,これらのファイル名を,変数 target の内容と対応付けて,別の配列変数 textures に設定します.
なお,一つ一つのテクスチャのサイズは以前のものより小さくしているので,記号定数 TEXWIDTH と TEXHEIGHT も変更します.また以下のプログラムでは,もとあったスフィアマッピング用のテクスチャの設定は削除しています.
・・・
/*
** テクスチャ
*/
#define TEXWIDTH 128 /* テクスチャの幅 */
#define TEXHEIGHT 128 /* テクスチャの高さ */
static const char *textures[] = { /* テクスチャファイル名 */
"room2nx.raw",
"room2ny.raw",
"room2nz.raw",
"room2px.raw",
"room2py.raw",
"room2pz.raw",
};
static const GLenum target[] = { /* テクスチャのターゲット名 */
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
};
・・・
この6つの画像を,それぞれ対応するターゲットに割り当てます.プログラムは順番を入れ替え,ファイル名を指定しているところに textures[i] を使うように変更するだけです.
・・・
/*
** 初期化
*/
static void init(void)
{
/* テクスチャ画像はワード単位に詰め込まれている */
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
for (int i = 0; i < 6; ++i) {
/* テクスチャの読み込みに使う配列 */
GLubyte texture[TEXHEIGHT][TEXWIDTH][4];
FILE *fp;
/* テクスチャ画像の読み込み */
if ((fp = fopen(textures[i], "rb")) != NULL) {
fread(texture, sizeof texture, 1, fp);
fclose(fp);
}
else {
perror(textures[i]);
}
/* テクスチャの割り当て */
glTexImage2D(target[i], 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,
GL_RGBA, GL_UNSIGNED_BYTE, texture);
}
/* テクスチャを拡大・縮小する方法の指定 */
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
・・・
上のプログラムでは,ついでにテクスチャの拡大・縮小方法に線形補間を指定しています.これをコンパイルして実行すると,こんな具合になります.
図形を立方体に変えたときの品質は,スフィアマップより良好です.スクリーンに対して垂直に近い面において,テクスチャ座標が狂って正しくマッピングされないという現象も発生しません.
以下のプログラムでは,不要な部分を削除してあります.
GL_TEXTURE_WRAP_[ST] に GL_CLAMP を指定すると,テクスチャの境界が見えてしまうことがあります(nVIDIA のビデオカードだと気にならなかった?).GL_CLAMP よりテクスチャ1画素分内側でクランプする GL_CLAMP_TO_EDGE を使うと目立たなくなります.
いつも参考にさせてもらっています。<br>質問させていただきたいのですが、<br>キューブマッピングの原理はどういったものなのでしょうか?<br>投影される方向は視線の方向に対してGL_TEXTURE_CUBE_MAP_POSITIVE_Zが来るようですが、<br>投影される位置は視点の位置からなのでしょうか?
キューブマッピングは視点から物体表面上の1点を見た時の反射方向にあるテクスチャをその点にマッピングして,鏡のような映り込みを再現する環境マッピングの手法の一つです.<br> この手法では,視点は Z 軸の負の方向を向いています.したがって,その方向(すなわち視点の正面)にあり,視点の方向を向いた鏡には,その鏡の反射方向である Z 軸の正の方向のテクスチャ,すなわち GL_TEXTURE_CUBE_MAP_POSITIVE_Z のテクスチャがマッピングされます.
ありがとうございます。<br>度々の質問申し訳ありません。<br>視線の正面に平面の鏡がある場合は良いのですが、<br>視線からずれた位置に平面の鏡がある場合、<br>どの部分が映るのでしょうか?<br>つまり、<br>平面に垂直に視線を保ったまま左右に平行移動したときは、<br>Z軸の正の方向のテクスチャがマッピングされると思いますが、<br>そのテクスチャのどの部分がマッピングされるのでしょうか?
環境マッピングは視線の反射方向にあるテクスチャをサンプリングしますので,もし視線が右に反射した場合,キューブマッピングでは右側のテクスチャ,すなわち GL_TEXTURE_CUBE_MAP_POSITIVE_X のテクスチャがサンプリングされます.つまりキューブマッピングでは,視線の反射方向によって,6枚のテクスチャのうちどのテクスチャからサンプリングするかを選択します.<br> テクスチャは無限の彼方にあると仮定しているので,視線に垂直な鏡を左右に大きく平行移動した結果,視線の反射方向が Y 軸中心に Z 軸に対して +45°を超えれば GL_TEXTURE_CUBE_MAP_POSITIVE_X のテクスチャが選ばれ,-45°を下回れば GL_TEXTURE_CUBE_MAP_NEGATIVE_X のテクスチャが選ばれます.
最初の図がわかりにくいかもと思って,描き直してみました.ブラウザの表示が変わってないようでしたら,再読み込みしてみてください.実際にはテクスチャは無限遠にあるので,反射点は常にテクスチャを貼った立方体の中心にあるものとして取り扱われます.
いつも参考にさせてもらっています.<br>質問させて頂きたいのですが,<br>jpeg形式の画像を使ったキューブマッピングはどのように行えば良いでしょうか?
なおさま,コメントありがとうございます.<br>jpeg形式の画像はそのままではテクスチャとして読み込めませんので,PhotoShop や GIMP などの画像処理ソフトで RAW 形式(汎用フォーマット)に変換するか,The Independent JPEG Group (IJG) の libjpeg を使って伸長する http://www.syuhitu.org/other/jpeg/jpeg.html などの方法を採っていただけますでしょうか.よろしくお願いします.
質問に答えて頂き,ありがとうございます.<br>教えて頂いたページを元に勉強していきたいと思います.<br>今後ともよろしくお願いします.