床井研究室

仕事が遅い

つくづく、自分の仕事の遅さに困り果てています。たった90分の授業1コマの準備に丸一週間費やすなんてことはざらです。こんなことをしていたら、研究はおろか、他の授業にも影響が出ます。以前なら夜に時間を稼ぐなんてこともできましたけど、最近はそういうことをすると影響が長引くようになり、なかなか思うようにいきません。

そのことを今かかっているお医者さんに話したら、「それは不安感のせいではないか」と指摘されました。確かに、授業にしても研究発表にしても相手がいるわけで、その相手による評価は避けることができません。そして、私はそういうときに、極度の不安感を感じてしまうようです。その結果、準備に不必要なほど神経質になったり、準備そのものに手が付けられなかったりしてしまうんですね。

実はこの blog 自体も、うちの学生さん以外にも読んでいただいている方がいらっしゃることを知って、ここのところかなり気が重くなっていました。有意義なことを書こうと思うのですが、後で読むととても陳腐な内容に思えて情けなくなってしまいまうんです。これは本でも論文でも同じなんですけれど。

お医者さんには「手を抜く方法を見つけなさい」とアドバイスされたんですが、「手抜き」とか言っておきながら、本当はそういうことがなかなか難しい性分だったりするので、悩ましいところです。ま、でも、何もできなくなるのも困るので、学生さんには申し訳ないけど、もうちょっと力を抜いてやっていこうと思っています。この blog も、本来それを必要としている学生さんのニーズに絞って書くことにしたら、また何とか書き始められました。

ということで、今回はマルチテクスチャです。マルチテクスチャについては、以前にも簡単なサンプルを書きましたが、今回はもう少し実際的なサンプルを使った説明を書いてみたいと思います。

テクスチャをマッピングした箱を描く

雛形として、とりあえず下の左のようなテクスチャをマッピングした箱を描くプログラムを使うことにします。

箱にマッピングするテクスチャ テクスチャをマッピングした箱

複数のテクスチャを重ねてマッピングする

この箱にキューブマッピングによる環境マッピングを施すことを考えてみましょう。この箱には既にテクスチャがマッピングされています。そこに環境の映り込みを表現しようとすれば、もとのテクスチャの上に環境のテクスチャを重ねてマッピングしなければなりません。その際には重なるテクスチャの合成を行う必要もあります。

マルチテクスチャはこれらの機能を提供します。これは(プログラム可能なハードウェアが出現するまでの)リアルタイムグラフィックスにおいて、中心的な役割を果たすものでした。

Windows の場合

マルチテクスチャはキューブマッピング同様 OpenGL 1.3 で標準機能に取り入れられたので、Visual C++ 6.0 の gl.h には必要な宣言や定義が含まれていません。そこで、今回も OpenGL® Sample Implementation にある glext.h#include します。また、マルチテクスチャで用いる glActiveTexture() も用意されていないので、この関数ポインタ変数 glActiveTexture の定義も追加しておきます。

#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)
PFNGLACTIVETEXTUREPROC glActiveTexture;
#  endif
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

テクスチャユニット

テクスチャマッピングの処理を行うハードウェアのことを、テクスチャユニットと呼びます。マルチテクスチャは、複数のテクスチャユニットの処理結果を合成することで実現されます。このテクスチャユニットの数はグラフィックスハードウェアごとに異なっており、これが重ね合わせることのできるテクスチャの枚数になります。使用できるテクスチャユニットの数は、glGetIntegerv( GL_MAX_TEXTURE_UNITS, &n ) によって、GLint 型の変数 n に得ることができます。

テクスチャユニットのうち、0番目のテクスチャユニット(テクスチャユニット0)は最初から利用可能になっており、これがベースのテクスチャマッピングとして使用されます。テクスチャユニット1以上を使用する場合は、glActiveTexture() によって指定します。glActiveTexture() の呼び出しによって、それ以降のテクスチャ画像やテクスチャ座標の自動生成、テクスチャ環境、テクスチャ変換行列に関連した命令を、指定したテクスチャユニットが実行するようになります。

テクスチャユニットを指定したテクスチャの設定

複数のテクスチャを扱うので、テクスチャオブジェクトを使います。そのために、テクスチャ名の配列変数を用意しておきます。

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

初期化の関数 init() の中にある、ベースのテクスチャを設定している部分に続けて、キューブマッピングの設定を追加します。この設定はテクスチャユニット1に対して行うので、glActiveTexture() を使って、テクスチャユニット1を指定します。なお Windows の場合は、これに先立って関数ポインタ変数 glActiveTextureglActiveTexture() の実体のエントリポイントを代入しておきます。

/*
** 初期化
*/
static void init()
{
  /* テクスチャの読み込みに使う配列 */
  GLubyte texture[TEXHEIGHT * TEXWIDTH * 4];

  /* テクスチャ画像はワード単位に詰め込まれている */
  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

  /* テクスチャ画像の読み込み */
  FILE* fp = fopen(texture_file, "rb");
  if (fp != NULL) {
    fread(texture, sizeof texture, 1, fp);
    fclose(fp);
  }

#if defined(_WIN32)
  glActiveTexture =
  (PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture");
#endif

まず、テクスチャ名を2つ作ります。そして、テクスチャユニット0 (GL_TEXTURE0) を有効にして、1つ目のテクスチャ名に元々の2次元テクスチャを割り当てます。テクスチャオブジェクトでは、元々のテクスチャは無名テクスチャのままにしておいて、キューブマッピングのテクスチャの方だけテクスチャオブジェクトを作成していました。今回も別にそれでも構わないのですが、無名テクスチャは今後は使うべきではないので、元々のテクスチャもテクスチャオブジェクトにしておきます。

  /* テクスチャ名を2つ作る */
  glGenTextures(2, texname);

  /* 1つ目のテクスチャ名には2次元テクスチャを割り当てる */
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, texname[0]);

そのあと、テクスチャを作成(テクスチャメモリの確保)します。

  /* テクスチャの割り当て */
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXWIDTH, TEXHEIGHT, 0,
    GL_RGBA, GL_UNSIGNED_BYTE, texture);

  /* テクスチャを拡大・縮小する方法の指定 */
  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_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

このテクスチャユニット0 (GL_TEXTURE0) では、このテクスチャを下地の色に対してどのように合成するかを指定します。ここでは下地の色である陰影を反映するために、GL_MODULATE を指定します。glTexEnvi() はアクティブなテクスチャユニットに対する設定なので、これは glGenTextures() の直後あたりに置いた方が(可読性などの点で)良いかもしれません。

  /* テクスチャユニット0のテクスチャ環境 */
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

次に、2つ目のテクスチャ名に追加するキューブマッピングのテクスチャを割り当てます。

  /* 2つ目のテクスチャにはキューブマップを割り当てる*/
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_CUBE_MAP, texname[1]);

そしてこの後にキューブマッピングのテクスチャの設定を行います。この設定はテクスチャユニット1に対して行います。

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

    FILE* fp = fopen(textures[i], "rb");
    if (fp != NULL) {
      /* テクスチャ画像の読み込み */
      fread(texture, 128 * 128 * 4, 1, fp);
      fclose(fp);
      
      /* キューブマッピングのテクスチャの割り当て */
      glTexImage2D(target[i], 0, GL_RGBA, 128, 128, 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);

  /* テクスチャの繰り返し方法の指定 */
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP);
  glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP);

  /* テクスチャユニット1のテクスチャ環境 */
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

  /* キューブマッピング用のテクスチャ座標を生成する */
  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);

テクスチャユニット1 (GL_TEXTURE1) でも、テクスチャを下地の色との合成に GL_MODULATE を指定します。この設定に関しては、またあとで議論します。

  /* テクスチャユニット1のテクスチャ環境 */
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

  /* 初期設定 */
  glClearColor(0.3, 0.3, 1.0, 0.0);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);

  /* 光源の初期設定 */
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, lightcol);
  glLightfv(GL_LIGHT0, GL_SPECULAR, lightcol);
  glLightfv(GL_LIGHT0, GL_AMBIENT, lightamb);
}

テクスチャを合成してマッピングする

テクスチャの設定が完了したら、今度は図形の表示の際に複数のテクスチャユニットを有効にして、合成したテクスチャがマッピングされるようにします。テクスチャユニットには、そのテクスチャユニットがアクティブなときに、最後に glBindTexure() で結合されたテクスチャが保持されています。

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

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

  /* テクスチャユニット0をアクティブにする */
  glActiveTexture(GL_TEXTURE0);

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

次にテクスチャユニット1を有効にして、キューブマッピングを開始します。

  /* テクスチャユニット1をアクティブにする */
  glActiveTexture(GL_TEXTURE1);

  /* キューブマッピング開始 */
  glEnable(GL_TEXTURE_CUBE_MAP);
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);
  glEnable(GL_TEXTURE_GEN_R);

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

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

図形の描画が終わったら、テクスチャマッピングを終了します。

  /* キューブマッピング終了 */
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glDisable(GL_TEXTURE_GEN_R);
  glDisable(GL_TEXTURE_CUBE_MAP);

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

こんな感じになります。

マルチテクスチャで環境をマッピングした結果

環境をマッピングする際のテクスチャ環境に GL_MODULATE を使っているので、模様の付いたメタリックな表面に環境が映り込んでいるような結果になります。非金属のつややかな表面への映り込みの場合は、もとのテクスチャに環境のテクスチャを加算する必要がありますが、これに関しては次回をお楽しみに。

マルチテクスチャで環境をマッピングした場合