■ 2005年06月14日 [OpenGL][テクスチャ] 第18回 テクスチャオブジェクト
独立したテクスチャが使いたい
テクスチャが1枚しかなくても,テクスチャを分割して使うことで,異なる場所や物体に異なるテクスチャを貼り付けることができます.それでも,やはり独立したテクスチャが使えたほうが何かと便利です.たとえばキューブマッピングはテクスチャに使う画像の全体を使用しますから,その一部を切り分けて他に流用するようなことができません.この場合は独立したテクスチャが必要になります.
テクスチャオブジェクト
OpenGL において独立した複数のテクスチャを取り扱う機能のことを,テクスチャオブジェクトと呼びます.複数のテクスチャメモリを動的に割り当てたり開放したりする機能のほか,テクスチャに優先度を与えて処理速度を最適化する機能も用意されています.
部屋の中にティーポットを置いてみる
それでは,通常のテクスチャマッピングにキューブマッピングを組み合わせてみましょう.まず,前回作ったプログラムを雛形にして,箱の中にティーポットを置いて眺めてみることにします.
視点はこの箱の中心にありますから,それより少し前(奥)の方に,ティーポットを配置します.
・・・ /* ** シーンの描画 */ static void scene(void) { static const GLfloat color[] = { 1.0, 1.0, 1.0, 1.0 }; /* 材質 (色) */ /* 材質の設定 */ glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color); /* 視点より少し奥にティーポットを描く */ glPushMatrix(); glTranslated(0.0, 0.0, -5.0); glutSolidTeapot(1.0); glPopMatrix(); ・・・
ティーポットにキューブマッピングしてみる
このティーポットにキューブマッピングを施します.Windows の VC++ 6.0 に含まれている gl.h にはキューブマッピングに必要な記号定数が定義されていないので,SGI の OpenGL® Sample Implementation にある glext.h を #include するようにしてください.
#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 ・・・
テクスチャオブジェクトを作る
キューブマッピングテクスチャはその側の箱と独立したものにするために,テクスチャオブジェクトを一つ作ります.テクスチャオブジェクトには,それを識別するためにテクスチャ名(実際には番号)を割り当てます.それを格納しておく変数を用意します.今回はテクスチャオブジェクトを一つしか使わないので,テクスチャ名を格納する配列変数 texname の要素数は 1 にしています.
・・・ /* ** テクスチャ */ #define TEXWIDTH 1024 /* テクスチャの幅 */ #define TEXHEIGHT 128 /* テクスチャの高さ */ static const char texture1[] = "dice.raw"; /* テクスチャファイル名 */ static GLuint texname[1]; /* テクスチャ名(番号) */ ・・・
そして,テクスチャ名を生成して,この配列変数に格納します.
・・・ /* ** 初期化 */ static void init(void) { /* テクスチャの読み込みに使う配列 */ GLubyte texture[TEXHEIGHT * TEXWIDTH * 4]; FILE *fp; #if 0 /* テクスチャ画像の読み込み */ if ((fp = fopen(texture1, "rb")) != NULL) { fread(texture, sizeof texture, 1, fp); fclose(fp); } else { perror(texture1); } #endif /* テクスチャ名を1個生成する */ glGenTextures(1, texname); /* テクスチャ画像はワード単位に詰め込まれている */ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- void glGenTextures(GLsizei n, GLuint *textures)
- テクスチャ名を n 個生成します.生成したテクスチャ名は,引数 textures に指定した配列の各要素に格納されます.
テクスチャオブジェクトを選択する
このテクスチャ名を使って,テクスチャオブジェクトを処理対象のテクスチャとします.ここでは外側の箱の1面に貼り付けているテクスチャを,キューブマッピングの1面のテクスチャにも利用することにします.
まず,キューブマッピング用のテクスチャのターゲット名を配列変数 target に格納しておきます.そして,glTexImage2D() を使ってテクスチャの割り当てを行う処理を追加します.この処理に先立って glBindTexture(GL_TEXTURE_CUBE_MAP, texname[0]) を実行し,glTexImage2D() によるテクスチャの割り当てが texname[0] で指定されるテクスチャオブジェクトに対して行われるようにします.その後,glBindTexture(GL_TEXTURE_2D, 0) を呼び出して,処理対象のテクスチャを箱のテクスチャに切り替えます.このテクスチャ名が 0 のテクスチャを無名テクスチャといい,デフォルトで用意されているテクスチャを表します.
/* テクスチャの割り当て */ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture); for (int i = 0; i < 6; ++i) { /* テクスチャファイル名 */ static const char *textures[] = { "room2ny.raw", /* 下 */ "room2nz.raw", /* 裏 */ "room2px.raw", /* 右 */ "room2pz.raw", /* 前 */ "room2nx.raw", /* 左 */ "room2py.raw", /* 上 */ }; /* テクスチャのターゲット名 */ static const int target[] = { GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, }; 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); /* キューブマッピングのテクスチャの割り当て */ glBindTexture(GL_TEXTURE_CUBE_MAP, texname[0]); glTexImage2D(target[i], 0, GL_RGBA, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture); glBindTexture(GL_TEXTURE_2D, 0); } }
- void glBindTexture(GLenum target, GLuint texture)
- target に対して texture というテクスチャ名のテクスチャオブジェクトを結合します.テクスチャオブジェクトは,テクスチャ名に対して最初にこの呼び出しが行われたときに生成されます.これ以降,テクスチャに対する設定は texture に指定したテクスチャオブジェクトに対して行われます.target には GL_TEXTURE_1D, GL_TEXTURE_2D, および OpenGL 1.2 以降では GL_TEXTURE_3D,OpenGL 1.3 以降では GL_TEXTURE_CUBE_MAP が指定できます.テクスチャ名 0 はデフォルトのテクスチャを示します.
texname[0] のテクスチャに対する,そのほかの設定も行いましょう.この部分もやはり glBindTexture(GL_TEXTURE_CUBE_MAP, texname[0]) と glBindTexture(GL_TEXTURE_2D, 0) ではさみます.
なお,ここで glTexEnvi() で指定するテクスチャ環境が,テクスチャオブジェクトごとに保存されないことに注意してください.このため,テクスチャオブジェクトごとに異なるテクスチャ環境を設定する場合は,テクスチャオブジェクトを切り替える都度,テクスチャ環境を設定する必要があります.
/* テクスチャを拡大・縮小する方法の指定 */ 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_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); #if 0 /* テクスチャ環境 */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); #endif /* 設定対象をtexname[0] のテクスチャに切り替える */ glBindTexture(GL_TEXTURE_CUBE_MAP, texname[0]); /* テクスチャを拡大・縮小する方法の指定 */ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); /* テクスチャの繰り返し方法の指定 */ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP); /* キューブマッピング用にテクスチャ座標を生成する */ 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); /* 設定対象を無名テクスチャに戻す */ glBindTexture(GL_TEXTURE_2D, 0); ・・・
キューブマッピングを有効にする
そして,図形の描画時にキューブマッピングを有効にします.
・・・ /* ** シーンの描画 */ static void scene(void) { static const GLfloat color[] = { 1.0, 1.0, 1.0, 1.0 }; /* 材質 (色) */ /* 材質の設定 */ glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color); /* 設定対象をtexname[0] のテクスチャに切り替える*/ glBindTexture(GL_TEXTURE_CUBE_MAP, texname[0]); /* テクスチャ環境 */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); /* キューブマッピングを有効にする */ glEnable(GL_TEXTURE_CUBE_MAP); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); /* 視点より少し奥にティーポットを描く */ glPushMatrix(); glTranslated(0.0, 0.0, -5.0); glutSolidTeapot(1.0); glPopMatrix(); /* キューブマッピングを解除する */ glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glDisable(GL_TEXTURE_CUBE_MAP); /* 設定対象を無名テクスチャに戻す */ glBindTexture(GL_TEXTURE_2D, 0); /* テクスチャ環境 */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); ・・・
これで普通のテクスチャマッピングとキューブマッピングを両方実装できました.
部屋の回転にあわせて映り込みも回転させよう
このプログラムでは,マウスのドラッグにしたがって,部屋というか,部屋のテクスチャをマッピングした箱を回転させることができます.しかし,今のままではティーポットへの映り込みが動かないため,ちょっと違和感があります.そこで,部屋の回転にあわせて,以前やったようにティーポットへの映り込みを回転させてみましょう.
なお,Visual C++ 6.0 の gl.h にはこの回転に使う glLoadTransposeMatrixd() が宣言されていないので,プログラムの先頭部分でこの関数を指す関数ポインタ変数の定義を追加します.
#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" PFNGLLOADTRANSPOSEMATRIXDPROC glLoadTransposeMatrixd; #elif defined(__APPLE__) || defined(MACOSX) # include <GLUT/glut.h> #else # include <GL/glut.h> #endif ・・・
また,初期化の関数 init() の最後あたりで,この関数ポインタ変数に glLoadTransposeMatrixd() の実体のエントリポイントを代入しておきます.
・・・ /* 光源の初期設定 */ glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0, GL_DIFFUSE, lightcol); glLightfv(GL_LIGHT0, GL_SPECULAR, lightcol); glLightfv(GL_LIGHT0, GL_AMBIENT, lightamb); #if defined(WIN32) glLoadTransposeMatrixd = (PFNGLLOADTRANSPOSEMATRIXDPROC)wglGetProcAddress("glLoadTransposeMatrixd"); #endif } ・・・
例によって手を抜いてエラーチェックを省略しています.もし glLoadTransposeMatrixd() が実装されていない場合,関数ポインタ変数 glLoadTransposeMatrixd が NULL になるので,その状態で実行するとプログラムが異常終了してしまいます.
映り込みのテクスチャを回転する
テクスチャを回転させるために,まず glMatrixMode(GL_TEXTURE) でテクスチャ変換行列に切り替えてから,glLoadTransposeMatrixd() を使って回転の行列を設定します.その後,すぐに glMatrixMode(GL_MODELVIEW) を呼び出してモデルビュー変換行列に戻しておかないと,その後の座標変換が(テクスチャ変換行列に適用されて)正しく行われなくなってしまいます.
・・・ /* ** シーンの描画 */ static void scene(void) { static const GLfloat color[] = { 1.0, 1.0, 1.0, 1.0 }; /* 材質 (色) */ /* 材質の設定 */ glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color); /* 設定対象をtexname[0] のテクスチャに切り替える*/ glBindTexture(GL_TEXTURE_CUBE_MAP, texname[0]); /* テクスチャ環境 */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); /* キューブマッピングを有効にする */ glEnable(GL_TEXTURE_CUBE_MAP); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); /* テクスチャ変換行列にトラックボール式の回転を加える */ glMatrixMode(GL_TEXTURE); glLoadTransposeMatrixd(trackballRotation()); glMatrixMode(GL_MODELVIEW); /* 視点より少し奥にティーポットを描く */ glPushMatrix(); glTranslated(0.0, 0.0, -5.0); glutSolidTeapot(1.0); glPopMatrix(); /* テクスチャ変換行列を元に戻す */ glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); /* キューブマッピングを解除する */ glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glDisable(GL_TEXTURE_CUBE_MAP); /* 設定対象を無名テクスチャに戻す */ glBindTexture(GL_TEXTURE_2D, 0); /* テクスチャ環境 */ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); ・・・
テクスチャ変換行列はテクスチャオブジェクトと関連していないので,他の図形を描画するときはテクスチャ変換行列を元に戻しておく必要があります.
これで周囲の情景にあわせてティーポットへの映り込みも回転するので,レイトレーシングっぽい表現が可能になります.映り込みのテクスチャにレンダリングした画像を用いれば,擬似的なレイトレーシングがリアルタイムに可能になります.図形をテクスチャとしてレンダリングする方法については,また後日.
すばらしいサンプルです。<br>3年ほど前に、手抜きOpenGL入門でOpenGLを勉強させていただきましたが、その後、こんなに内容が充実したをの今日発見して、驚くと同時に感激です。Linux,Windows,Macまでサンプルが掲載されているところもすばらしいです。<br>私の希望なのですが、OpenGLやGlutだけだとユーザーインタフェースの部分が弱いので、他のToolkitを用いたそういうサンプルを載せていただけると、うれしいです。<br>これからもがんばってください。
ありがとうございます.これからも精進します(笑)ので,よろしくお願い致します.GLUT 以外の toolkit に関してですが,学生さんがサウンドを使うために SDL を使い始めたのを見たので,私も一度使ってみようかと思っているところです.
床井さま、とてもためになるサンプルの提供ありがとうございます。<br><br>現在WindowsでOpenGLを使ったMFCベースのアプリを書いています。<br>メモリDCを作成してwglCreateContextでRC(レンダリングコンテキスト)と関連付けて<br>ポリゴンを描画させるところは問題なく動きました。<br><br>しかしテクスチャマッピングを行おうとすると、<br>メモリDCに関連付けたRCだとうまくいきません。<br>m_DCはCDC型でメモリDC作成済みだとして、<br>HGLRC m_hRC = wglCreateContext(m_DC.GetSafeHdc());<br>の直後で<br>void* pTemp = wglGetProcAddress("glLoadTransposeMatrixd");<br>を行うとNULLが返ってきます。<br>また、glBindTextureコールも失敗します。(glGetErrror()にて確認)<br><br>ただし、ダミーのSTATICウィンドウを作成して、<br>それのDCをRCと関連付けてやると、うまく動作します。<br><br>なんとかメモリDCのまま、テクスチャマッピングを行う方法をご存じないでしょうか?
すぎまろんさま、コメントありがとうございます。<br>デフォルトのフレームバッファを使わず、<br>オフスクリーンだけの(ディスプレイがなくても動作する)プログラムを作るということでしょうか。<br>このあたり突っ込んだことはなく知識がないので<br>(以前そういうことをしようとしてできなかったので簡単にあきらめた)<br>思い付きで適当なことを書いてしまいますが、<br>> void* pTemp = wglGetProcAddress("glLoadTransposeMatrixd");<br>> を行うとNULLが返ってきます。<br>これはどうしようもない気がします。<br>wglGetProcAddress() はビデオカードのドライバ内のエントリポイントを取ってくるわけですが、<br>Installable Client Driver の場合、存在するハードウェアの DC を取らないと、<br>そのドライバが読み込まれないように思います。<br>メモリ DC を使う場合は、ドライバに依存しないで(wglGetProcAddress() を使用しないで)<br>OpenGL 1.1 の範囲内で実装するしかないんじゃないでしょうか。
床井さま、回答ありがとうございます。<br><br>> デフォルトのフレームバッファを使わず、オフスクリーンだけの(ディスプレイがなくても動作する)プログラムを作るということでしょうか。<br>はい、その通りです。<br>メモリDCに関連付けたRC上でテクスチャマッピング画像を生成し、<br>さらに別のメモリDCに転送して、他の画像エフェクトを加えるといったことを試みようとしていました。<br><br>> wglGetProcAddress() はビデオカードのドライバ内のエントリポイントを取ってくるわけですが、<br>> Installable Client Driver の場合、存在するハードウェアの DC を取らないと、<br>> そのドライバが読み込まれないように思います。<br><br>やはりメモリDCだと不可能なようですね。<br>今回はあきらめることにします。<br>回答ありがとうございました。<(..)>