凝ったスポットライトを作る
投影マッピング(プロジェクションマッピング)は、スポットライトを当てるように、物体表面にテクスチャを投影するマッピング方法です。OpenGL 本来のスポットライトは物体が細かくポリゴンに分割されていないと使い物になりませんが、投影マッピングを使えば粗いポリゴンにも凝ったスポットライトを当てることができます。なお、これはテクスチャ座標の自動生成により行うことができますが、ここでは自分で座標を設定してみます。
3次元のテクスチャ座標
既に述べたように、テクスチャ座標においても物体の座標と同様に座標変換が行えます。このことはテクスチャ座標が、内部的には3次元(同次座標系なので4要素)で処理されていることを示しています。このため、glTexCoord*() で指定するテクスチャ座標とテクスチャ空間上の位置との関係は、glVertex*() で指定する頂点の座標とスクリーン空間上の位置との関係と非常に似たものになっています。ただし、スクリーン空間上の (-1, -1)-(1, 1) の領域が画面表示の対象になるのに対し、テクスチャ空間上では (0, 0)-(1, 1) の領域にあるテクスチャがマッピングマッピングの対象になります。

頂点の座標値をテクスチャ座標に使う
そこで、試しに頂点の座標値をそのままテクスチャ座標に使ってみましょう。前回の最後のところで用意したサンプルプログラムに含まれる box.cpp の glTexCoord2dv() を glTexCoord3dv() に変更し、その引数に与えているテクスチャ座標の配列 texcoord を頂点の座標値の配列 vertex に置き換えてください。
/*
** 箱の描画
*/
void box(double x, double y, double z)
{
...
/* 四角形6枚で箱を描く */
glBegin(GL_QUADS);
for (j = 0; j < 6; ++j) {
glNormal3dv(normal[j]);
for (i = 0; i < 4; ++i) {
/* テクスチャ座標の指定 */
glTexCoord3dv(vertex[j][i]);
/* 対応する頂点座標の指定 */
glVertex3dv(vertex[j][i]);
}
}
glEnd();
}
これをコンパイルして実行すると、次のような結果が得られます。これはテクスチャを立方体の正面から平行投影してマッピングしている状態です。

このような結果が得られる理由は、関数 box() で描かれる立方体をテクスチャ空間上に平行投影し、それをもとにテクスチャをサンプリングしているからです。

それでは、このテクスチャが立方体の正面いっぱいにマッピングされるよう、テクスチャ座標を変換します。まず、正面(xy 平面)から見た立方体の大きさを (0.5, 0.5) 倍します。そして、その領域がテクスチャの領域と一致するよう (0.5, 0.5) に平行移動します。

main.cpp の変更は、次のようになります。この場合も、プログラムのコーディング上の順序が逆順になっていることに注意してください。
/*************************
** GLUT のコールバック関数 **
*************************/
/* アニメーションのサイクル */
#define FRAMES 360
static void display()
{
/* フレーム数をカウントして時間として使う */
static int frame = 0; /* フレーム数 */
double t = (double)frame / (double)FRAMES; /* 時間とともに 0→1 に変化 */
/* アニメーションのサイクルごとにフレーム数をリセットする */
if (++frame >= FRAMES) frame = 0;
/* モデルビュー変換行列の設定 */
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
/* 光源の位置を設定 */
glLightfv(GL_LIGHT0, GL_POSITION, lightpos);
/* 視点の移動(物体の方を奥に移動)*/
glTranslated(0.0, 0.0, -3.0);
/* トラックボール処理で図形を回転 */
glMultMatrixd(trackballRotation());
/* テクスチャ行列の設定 */
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glTranslated(0.5, 0.5, 0.0); /* テクスチャと重なるよう平行移動 */
glRotated(t * 360.0, 0.0, 0.0, 1.0); /* 回転 */
glScaled(0.5, 0.5, 1.0); /* 立方体の正面像を縮小 */
/* 画面クリア */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* シーンの描画 */
scene();
/* ダブルバッファリング */
glutSwapBuffers();
}
投影方向の変更
テクスチャ座標値とテクスチャ(空間)との関係が、物体の頂点の座標値とスクリーン(空間)との関係と類似したものなら、テクスチャの投影方向を視線の方向と同様な手段で設定できるかも知れません。つまり、これに gluLookAt() が使える可能性があります。試しに、「テクスチャの視点」を、この箱の上部に移動してみましょう。
...
/* トラックボール処理で図形を回転 */
glMultMatrixd(trackballRotation());
/* テクスチャ行列の設定 */
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glTranslated(0.5, 0.5, 0.0);
glRotated(t * 360.0, 0.0, 0.0, 1.0);
glScaled(0.5, 0.5, 1.0);
/* テクスチャ行列にビュー変換行列を掛ける */
gluLookAt(0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
/* 画面クリア */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
...
視線は箱の上 (0, 2, 0) から下 (0, 0, 0) を見るように設定しているので、テクスチャは上下の面に投影されます。

スポットライトの実現
スポットライトのように光を1点から特定の方向に投射するような効果は、テクスチャ座標値をテクスチャ空間に透視投影して実現できます。スクリーン空間への透視投影には gluPerspective() や glFrustum() を使いますが、テクスチャ空間への透視投影にもこれらが使えます。
...
/* トラックボール処理で図形を回転 */
glMultMatrixd(trackballRotation());
/* テクスチャ行列の設定 */
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glTranslated(0.5, 0.5, 0.0);
glRotated(t * 360.0, 0.0, 0.0, 1.0);
glScaled(0.5, 0.5, 1.0);
/* テクスチャ行列に透視変換行列とビュー変換行列を掛ける */
gluPerspective(60.0, 1.0, 0.1, 10.0);
gluLookAt(0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
/* 画面クリア */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
...
こうすると、「テクスチャの視点」(いや、光源か?)の位置から離れるに連れて、テクスチャが拡大します。

画角 (fovy) を 30.0 から 60.0 に変更すると、テクスチャの広がりが大きくなるので、こんな具合になります。

なんだかよくわかんないんで、アルファテストをオフにして始点の位置を変更し、さらに「テクスチャの視点」の位置を少し横にずらすとこうなります。

んで、テクスチャを斜めからマッピングするようにして、ついでにテクスチャをスポットライトっぽくすると、こんな具合です。あんまり具合よくありませんが。

なお、このマッピングは物体のローカル座標系で行われます。したがってスポットライトをワールド座標系上に設定するには、物体に対して行ったのと同じモデルビュー変換をテクスチャ空間でも実行する必要があります。
また実際にこの方法でスポットライトを実現するには、他の処理をいくつか組み合わせる必要があります。例えば、テクスチャを貼ったポリゴンにこの方法でスポットライトを当てるには、複数のテクスチャを重ね合わせる処理が必要になります。これはマルチテクスチャを使って実現できます。あと、裏側や影の部分にはテクスチャが投影されないように工夫する必要もあります。
