床井研究室

独立したテクスチャが使いたい

テクスチャが1枚しかなくても、テクスチャを分割して使うことで、異なる場所や物体に異なるテクスチャを貼り付けることができます。それでも、やはり独立したテクスチャが使えたほうが何かと便利です。たとえばキューブマッピングはテクスチャに使う画像の全体を使用しますから、その一部を切り分けて他に流用するようなことができません。この場合は独立したテクスチャが必要になります。

テクスチャオブジェクト

OpenGL において独立した複数のテクスチャを取り扱う機能のことを、テクスチャオブジェクトと呼びます。複数のテクスチャメモリを動的に割り当てたり開放したりする機能のほか、テクスチャに優先度を与えて処理速度を最適化する機能も用意されています。

部屋の中にティーポットを置いてみる

それでは、通常のテクスチャマッピングにキューブマッピングを組み合わせてみましょう。まず、前回作ったプログラムを雛形にして、箱の中にティーポットを置いて眺めてみることにします。

視点はこの箱の中心にありますから、それより少し前(奥)の方に、ティーポットを配置します。ティーポットを配置するモデリング変換が他に影響を与えないように glPushMatrix() で現在の変換行列を保存し、ティーポットを配置・描画した後に glPopMatrix() で保存した変換行列を復帰するようにします。

/*
** シーンの描画
*/
static void scene()
{
  static const GLfloat color[] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* 材質 (色) */

  /* 材質の設定 */
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);

  /* 視点より少し奥にティーポットを描く */
  glPushMatrix();
  glTranslated(0.0, 0.0, -3.0);
  glutSolidTeapot(1.0);
  glPopMatrix();

また、部屋のテクスチャを貼りつけた箱が小さくてティーポットが入りきらないので、箱を大きくしておきます。

  /* テクスチャマッピング開始 */
  glEnable(GL_TEXTURE_2D);

  /* 箱を描く */
  box(5.0, 5.0, 5.0);

  /* テクスチャマッピング終了 */
  glDisable(GL_TEXTURE_2D);
}

部屋の中のティーポット

ティーポットにキューブマッピングしてみる

このティーポットにキューブマッピングを施します。Windows の VC++ 6.0 に含まれている gl.h にはキューブマッピングに必要な記号定数が定義されていないので、この場合は SGIOpenGL® Sample Implementation にある glext.h1#include するようにしてください。

#if defined(__APPLE__) || defined(MACOSX)
#  define GL_SILENCE_DEPRECATION
#  include <GLUT/glut.h>
#  include <OpenGL/glext.h>
#else
#  if defined(_MSC_VER)
//#    pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#    define _USE_MATH_DEFINES
#    define _CRT_SECURE_NO_WARNINGS
#  endif
#  include <GL/glut.h>
#  include <GL/glext.h>
#endif

テクスチャオブジェクトを作る

キューブマッピングテクスチャはその側の箱と独立したものにするために、テクスチャオブジェクトを1つ作ります。テクスチャオブジェクトには、それを識別するためにテクスチャ名(実際には番号)を割り当てます。それを格納しておく変数を用意します。今回はテクスチャオブジェクトを1つしか使わないので、テクスチャ名を格納する配列変数 texname の要素数は 1 にしています。

/*
** テクスチャ
*/
#define TEXWIDTH  1024                     /* テクスチャの幅    */
#define TEXHEIGHT 128                      /* テクスチャの高さ   */
static const char texture1[] = "dice.raw"; /* テクスチャファイル名 */
static GLuint texname[1];                  /* テクスチャ名(番号) */

そして、テクスチャ名を生成して、この配列変数に格納します。

/*
** 初期化
*/
static void init()
{
  ...

  /* テクスチャ環境 */
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

#if 0
  /* 混合する色の設定 */
  static const GLfloat blend[] = { 0.0f, 1.0f, 0.0f, 1.0f };
  glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, blend);
#endif

/* テクスチャ名を1個生成する */
glGenTextures(1, texname);
void glGenTextures(GLsizei n, GLuint* textures)
テクスチャ名を n 個生成します。生成したテクスチャ名は、引数 textures に指定した配列の各要素に格納されます。

テクスチャオブジェクトを選択する

このテクスチャ名を使って、テクスチャオブジェクトを処理対象のテクスチャとします。ここでは外側の箱の1面に貼り付けているテクスチャを、キューブマッピングの1面のテクスチャにも利用することにします。まず、キューブマッピング用のテクスチャのターゲット名を配列変数 target に格納しておきます。

  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,
    };

そして、glTexImage2D() を使ってテクスチャの割り当てを行う処理を追加します。この処理に先立って glBindTexture( GL_TEXTURE_CUBE_MAP, texname[0] ) を実行し、glTexImage2D() によるテクスチャの割り当てが texname[0] で指定されるテクスチャオブジェクトに対して行われるようにします。その後、glBindTexture( GL_TEXTURE_2D, 0 ) を呼び出して、マッピングするテクスチャを箱のテクスチャに戻します。このテクスチャ名が 0 のテクスチャを無名テクスチャといい、デフォルトで用意されているテクスチャを表します。

    FILE* fp = fopen(textures[i], "rb");
    if (fp != 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, cubemap);

      /* キューブマッピングのテクスチャの割り当て */
      glTexImage2D(target[i], 0, GL_RGBA, 128, 128, 0, 
        GL_RGBA, GL_UNSIGNED_BYTE, texture);
    }
  }
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() で指定するテクスチャ環境が、テクスチャオブジェクトごとに保存されないことに注意してください。このため、テクスチャオブジェクトごとに異なるテクスチャ環境を設定する場合は、テクスチャオブジェクトを切り替える都度、テクスチャ環境を設定する必要があります。

  /* 設定対象を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);

キューブマッピングを有効にする

そして、図形の描画時にキューブマッピングを有効にします。

/*
** シーンの描画
*/
static void scene()
{
  static const GLfloat color[] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* 材質 (色) */

  /* 材質の設定 */
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);

  /* 設定対象をtexname[0] のテクスチャに切り替える*/
  glBindTexture(GL_TEXTURE_CUBE_MAP, texname[0]);

  /* キューブマッピングを有効にする */
  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, -3.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);

これで普通のテクスチャマッピングとキューブマッピングを両方実装できました。

ティーポットにキューブマッピング

部屋の回転にあわせて映り込みも回転させよう

このプログラムでは、マウスのドラッグにしたがって、部屋というか、部屋のテクスチャをマッピングした箱を回転させることができます。しかし、今のままではティーポットが部屋と一緒に移動してしまいます。そこでティーポットを制止させ、以前やったように部屋の回転にあわせてティーポットへの映り込みだけを回転させてみましょう。

マウスのドラッグによる回転はシーン全体に適用していましたから、まず、これを止めます。

static void display()
{
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  /* 光源の位置を設定 */
  glLightfv(GL_LIGHT0, GL_POSITION, lightpos);

#if 0
  /* 視点の移動(物体の方を奥に移動)*/
  glTranslated(0.0, 0.0, -5.0);
  //gluLookAt(1.5, 2.0, 2.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
#endif

#if 0
  /* トラックボール処理で図形を回転 */
  glMultMatrixd(trackballRotation());
#endif

  /* 画面クリア */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  /* シーンの描画 */
  scene();

  /* ダブルバッファリング */
  glutSwapBuffers();
}

そして、この回転を部屋のテクスチャをマッピングした箱にだけ適用するようにします。

/*
** シーンの描画
*/
static void scene()
{
  ...

  /* テクスチャマッピング開始 */
  glEnable(GL_TEXTURE_2D);

  /* トラックボール処理による回転 */
  glMultMatrixd(trackballRotation());

  /* 箱を描く */
  box(5.0, 5.0, 5.0);

  /* テクスチャマッピング終了 */
  glDisable(GL_TEXTURE_2D);
}

ティーポットにキューブマッピングするテクスチャは映り込みなので、部屋とは逆方向に回転させる必要があります。そこで、この回転の変換行列の設定に glLoadTransposeMatrixd() を使います。これは Visual C++ 6.0 の gl.h には宣言されていないので2、プログラムの先頭部分でこの関数を指す関数ポインタ変数の定義を追加します。

#if defined(__APPLE__) || defined(MACOSX)
#  define GL_SILENCE_DEPRECATION
#  include <GLUT/glut.h>
#  include <OpenGL/glext.h>
#else
#  if defined(_MSC_VER)
//#    pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#    define _USE_MATH_DEFINES
#    define _CRT_SECURE_NO_WARNINGS
#  endif
#  include <GL/glut.h>
#  include <GL/glext.h>
#  if defined(_WIN32)
PFNGLLOADTRANSPOSEMATRIXDPROC glLoadTransposeMatrixd;
#  endif
#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() が実装されていない場合、関数ポインタ変数 glLoadTransposeMatrixdNULL になるので、その状態で実行するとプログラムが異常終了してしまいます。

映り込みのテクスチャを回転する

テクスチャを回転させるために、まず glMatrixMode( GL_TEXTURE ) でテクスチャ変換行列に切り替えてから、glLoadTransposeMatrixd() を使って回転の行列を設定します。その後、すぐに glMatrixMode( GL_MODELVIEW ) を呼び出してモデルビュー変換行列に戻しておかないと、その後の座標変換が(テクスチャ変換行列に適用されて)正しく行われなくなってしまいます。

/*
** シーンの描画
*/
static void scene()
{
  ...
  static const GLfloat color[] = { 1.0f, 1.0f, 1.0f, 1.0f };  /* 材質 (色) */

  /* 材質の設定 */
  glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);

  /* 設定対象をtexname[0] のテクスチャに切り替える*/
  glBindTexture(GL_TEXTURE_CUBE_MAP, texname[0]);

  /* キューブマッピングを有効にする */
  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, -3.0);
  glutSolidTeapot(1.0);
  glPopMatrix();

  /* テクスチャ変換行列を元に戻す */
  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);

これで周囲の情景にあわせてティーポットへの映り込みも回転するので、レイトレーシングっぽい表現が可能になります。映り込みのテクスチャにレンダリングした画像を用いれば、擬似的なレイトレーシングがリアルタイムに可能になります。図形をテクスチャとしてレンダリングする方法については、また後日。

ティーポットへの映り込みも回転する

  1. これは OpenGL Registry のものです。(2026 年 4 月 28 日 追記) 

  2. Windows 本来の OpenGL のバージョンは 1.1 のままなので、それ以降の API は今も Windows 本体には実装されていません。そのため、Installable Client Driver (ICD) という仕組みを使って GPU ベンダーのドライバが提供するものを使うようになっています。(2026 年 5 月 6 日 追記)