Dynamical
Environment Mapping

98/11/07(土)  − Dynamical Environment Mapping − 動的環境マッピング

 3DFC'98 Fall での私の講演ネタその1「動的環境マッピングの試み」 についての詳細(という程でもないが)を述べることにする。

 環境マッピング(Environment Mapping) またはリフレクションマッピング(Reflection Mapping)は すでに多くの方がご存知のことと思う。 一応簡単に説明すると、 周囲360°の背景を描画した テクスチャ画像(環境マップ)を あらかじめ用意しておき、 描画時に、オブジェクトの頂点毎に 視線の正反射(鏡面反射)ベクトルを求め、 その方向のテクスチャ座標を指定することで、 あたかも周囲の背景がオブジェクトに映り込んでいるように見せかける 特殊なテクスチャマッピングである。
 実装方法などはご存知ない方が多いようなので ここで簡単に説明しておく。

 まず、 頂点の法線ベクトル視線ベクトルから、 視線の頂点における正反射ベクトルを計算する。 正反射ベクトルの計算は フォン(Phong)のモデルでも使われるが、 視線の逆方向ベクトル頂点の法線ベクトルとおくと、 視線の頂点における正反射ベクトル E’

E’=2(

で求めることができる。 ちなみに N・Eは 視線の逆方向ベクトルと 法線ベクトルの内積である。 この内積2つのベクトルの成す角度θとしたときの cosθに等しい。
 次に、求めた視線の正反射ベクトル E’ から、 環境マップ上の対応するテクスチャ座標(u, v)を 計算する。 つまり、 視線が反射した向きからテクスチャ座標を決定する訳だ。
 ところが実は困ったことに、この計算は 環境マップの形式によって いくつかの方法がある。 ここで環境マップの形式と書いたのは、 例えば 上下左右前後計6枚のテクスチャ画像を環境マップとして使用する 方法や、 地図の メルカトル図法のような極座標系を用いて 1枚のテクスチャで360°全景をカバーする方法などである。
 おそらく最も理解しやすいのは 極座標系を用いた方法であるが、 ここでは OpenGLが採用している 少々特殊な形式を利用することにする。
 OpenGLの環境マップは、一口で言うなら
「無限遠に配置した完全鏡面反射球に映り込んでいる円形の背景」
である。
これだけでは訳わかめなのでもう少し詳しく説明すると、 無限遠というのは、 理論上球体のどの部分に届く視線ベクトルも、 すべて平行になっている必要があるためである。 このような条件を満たしている場合、球体の 視点側の半球部分には、 360°全景が映り込むことになる。 下図を御覧いただきたい。

環境マップとして使用するのは映り込んだ円形部分であり、 視線反射ベクトル x,y,z(上の水色の→) から テクスチャ座標u,vを 求める変換は以下のようになる。

とした時

+1/2

+1/2

求まった テクスチャ座標(u, v)は、 (0, 0)〜(1, 1)の正方形に内接する 円領域に必ず収まる。 正方領域内でも、円領域以外の部分は まったく使用されない。 このため、テクスチャ画像には多少の無駄ができる。 しかし、この形式を利用すると 極座標系を用いる方法よりも 計算負荷が少ないという利点がある。
 極座標を用いた環境マップでは、 視線反射ベクトルから テクスチャ座標(u, v)を求める際、 逆三角関数が必要になる。 しかしこちらの方法では、負荷のかかる部分はルート計算1回くらいである。

 (u, v)が求まったら、 それをテクスチャ座標として 指定するだけである。環境マッピングの方法については以上だ。
 それにしても、OpenGLでは モデリング行列とビュー行列が1つにまとまっているのが 不便である。 おかげでカメラがローリングしても 映り込みは基本的に回転しない...。

 さて、ここでようやく本論に戻ろう。 環境マップはあらかじめ用意した静的な画像を 使うため、 通常動的に動く(つまりシーンの状態によって映り込むオブジェクトも変化する) ことはない。 これを結構真面目に計算して 動的に動かしてしまえ というのが今回のネタである。
 上で説明した変換式を利用すると、 シーン内のオブジェクトから環境マップを描画することができる。 最終的に環境マッピングを施したいオブジェクト (とりあえずターゲットのオブジェクトと呼ぶことにする)から見た シーン内のさまざまな オブジェクトの頂点座標をもとに、 環境マップ上の対応する(u, v)座標を計算できるからだ。
 ちょっと具体的に書くと(必要ないって?)、 他のオブジェクトの頂点座標からターゲットのオブジェクトの座標を 引いて 単位化すれば 方向ベクトルが求まるので、 それを上の式に当てはめて(u, v)を計算すれば良い。 つまり、 シーン内のオブジェクトにモデリング変換、ビューイング変換、 透視変換をかけてスクリーン座標を求める代わりに、 上の変換をかけて求めた(u, v)座標で描画するだけで 環境マップを作成できる訳だ。

 後は作成した環境マップをテクスチャに設定し、 環境マッピングを施して通常通り描画すれば良い。

これを毎フレーム行う訳だ。

 ところで既にお気づきの方も多いと思うが、 この方法で良好な結果を得ることができるのは、 あくまで ターゲットのオブジェクトに対して 環境マッピングした時だけである。 厳密には、 第一段階として環境マップを作成した際の、 ターゲットのオブジェクトの座標から 少しでもずれている頂点(通常ずれていない方が珍しいだろう)は、 すべておかしくなっている。 したがって実際に使用する際には、 このことを充分に考慮しておく必要がある。 例えばターゲットのオブジェクトにしか 環境マッピングを施さないという手もあるし、また、 環境マップ作成時に、 ある程度以上離れているオブジェクトしか環境マップに描き込まない という手もあるだろう。
 3DFC'98 Fallで デモをした、トンネルの中をグラス(?)が飛んでゆく サンプルでは、そもそも 環境マッピングを施しているオブジェクト 1つしかないため、見てわかる程の不自然さは出ていない。 実は、 屈折マッピング(Refraction Mapping)も同じ方法で 既に動いているのだが、 不自然さがあまりに目立ってしまったため、 今回はお見せできなかった。 実用可能にするためにはかなりの改良が必要である。

 さて、環境マップの描画には さまざまな方法が考えられる。 トンネルのデモでは、フレームバッファに描画 した後で、 glCopyTexImage2D() というOpenGLのコマンドを利用して フレームバッファの内容をテクスチャにコピー している。 実は今回のトンネルデモの場合、座標変換テーブルを 用意して環境マップを自前で描画した方が 高速だったと思う。 特にAGPに対応したカードでは、 確実にそちらの方が速いはずである。 ではなぜ、敢えてOpenGLコマンドを利用したのか?
 理由は単純明解、
「ドライバとハードウェアが対応すれば プログラムの変更なく高速化できる」
の一言に尽きる。 以上である。 現時点では自前でバッファを用意して レンダリングした方が高速でも、 将来速いハードが現れた時に逆転する可能性が高い。 おそらく今でも、ジオメトリエンジンを 搭載しているハイエンドグラフィクスカードなら、 OpenGLコマンドを利用した方が速いだろう。
 さらに別の方法として、Glide APIを 利用する方法もある。 今は時間がないので手を出せないが、 Glideを使えばそもそも描画した環境マップを テクスチャにコピーする 作業すら必要ないようである。 しかも ジオメトリ演算の概念すらない単なるラスタライザであるため、 可能な限り無駄を省くことができる。 おそらく今回のようなデモなら、比べ物にならないくらい高速化できるだろう。

 トンネルデモは、
http://lpserv01.bais.chubu.ac.jp:8080/~g93088/archives/tunnel.zip
からダウンロードできるので、興味のある人は試して頂くとよい。 背景のトンネルの模様が、 きちんと動的に中央のグラスに映り込んでいる ことが理解できると思う。 また、マウス右ボタンでメニューが出るので、適当にいろいろ試してみて欲しい。
 ただし、 Voodooやソフトウェア描画では正常に実行できないので 悪しからず御容赦を....。 何せglCopyTexImage2D()実行できないとその時点でアウトなうえに ソフトウェア描画のテクスチャマッピングはクリッピングでバグバグ。 明らかにバグなのに直らない...。