«第5回 テクスチャ座標 最新 ビデオカード発火»

床井研究室

※このブログは遅くとも 2027 年 3 月に管理者の定年退職により閉鎖します (移転先は管理者本人共々模索中)

■ 2004年09月20日 [OpenGL][テクスチャ] 第6回 投影マッピング

2006年12月21日 10:51更新

凝ったスポットライトを作る

投影マッピング(プロジェクションマッピング)は,スポットライトを当てるように,物体表面にテクスチャを投影するマッピング方法です.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 のコールバック関数 **
****************************/
 
/* トラックボール処理用関数の宣言 */
#include "trackball.h"
 
/* アニメーションのサイクル */
#define FRAMES 360
 
static void display(void)
{
  /* フレーム数をカウントして時間に使う */
  static int frame = 0;
  double t = (double)frame / (double)FRAMES;
  
  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); /* 回転              */
  glScaled(0.5, 0.5, 1.0);             /* 立方体の正面像を縮小      */
  
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  ・・・

投影方向の変更

テクスチャ座標値とテクスチャ(空間)との関係が,物体の頂点の座標値とスクリーン(空間)との関係と類似したものなら,テクスチャの投影方向を視線の方向と同様な手段で設定できるかも知れません.つまり,これに gluLookAt() が使える可能性があります.試しに,「テクスチャの視点」を,この箱の上部に移動してみましょう.

・・・
  /* テクスチャ行列の設定 */
  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);
  
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  ・・・

視線は箱の上 (0, 2, 0) から下 (0, 0, 0) を見るように設定しているので,テクスチャは上下の面に投影されます.

上から平行投影マッピング

スポットライトの実現

スポットライトのように光を1点から特定の方向に投射するような効果は,テクスチャ座標値をテクスチャ空間に透視投影して実現できます.スクリーン空間への透視投影には gluPerspective() や glFrustum() を使いますが,テクスチャ空間への透視投影にもこれらが使えます.

・・・
  /* テクスチャ行列の設定 */
  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(30.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);
  
  /* モデルビュー変換行列の設定 */
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  ・・・

こうすると,「テクスチャの視点」(いや,光源か?)の位置から離れるに連れて,テクスチャが拡大します.

上から透視投影マッピング

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

テクスチャを広げて立方体の側面にもマッピング

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

テクスチャを少しずらしてマッピング

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

スポットライトっぽいテクスチャを使う

なお,このマッピングは物体のローカル座標系で行われます.したがってスポットライトをワールド座標系上に設定するには,物体に対して行ったのと同じモデルビュー変換をテクスチャ空間でも実行する必要があります.

また実際にこの方法でスポットライトを実現するには,他の処理をいくつか組み合わせる必要があります.例えば,テクスチャを貼ったポリゴンにこの方法でスポットライトを当てるには,複数のテクスチャを重ね合わせる処理が必要になります.これはマルチテクスチャを使って実現できます.あと,裏側や影の部分にはテクスチャが投影されないように工夫する必要もあります.


編集 «第5回 テクスチャ座標 最新 ビデオカード発火»