四面体環境マップ


~ Tetrahedron environment mapping~






■はじめに

ドジ研の花見会Masaさんやtejimaさん相手に「パラボロイド最高」と叫んでいたのですが、Masaさん的には、非線形な座標変換はお嫌いらしく(シェーダを倍の数だけ書かないといけなくなりますしね)、「全景360度をワールド基準で4面体程度で線形にレンダリングして、それを二次元画像として非線型変換-->デュアルパラボロイド-->ミップマップ&フィルタというのが、今後のスタンダードかも知れません。」ということです。
4面レンダリングしてしまうと、パラボロイドにするうまみが減ってしまいそうなので、なんとも微妙ですが(ピクセル処理はテクスチャサイズで調整できますが、頂点処理はパス数に比例しますから)、ちょっと実装してみましょう。

で、いつものようにプログラムです。

フレネル処理のためにps_2_0になっていますが、その部分を何とかすれば、ps_1_Xで可能ですので、適当に修正してください。

カーソルキーで、カメラが回って、zやxでズームが変えられます。
pキーでティーポットの動きを止めることができます。

ソースには、いつものように適当にファイルが入っています。 大事なファイルは次のものです。

main.hアプリケーションのヘッダ
main.cppアプリケーションのソース
bg.cpp四角形のメュシュのソース
hlsl.fxシェーダプログラム
paraboloid.fxDual paraboloid マップのためのシェーダ

■何をやっているの

閉じたオブジェクトを作るためには、最低4面必要なことは良く知られています。
特に、一番ゆがみが小さな形をしたものは、正四面体になります。
さて、正四面体に環境マップを作れば良いのですが、どのようにすればよいのでしょうか?
その答えはダイヤモンドにあります。
ダイヤモンドは、ある原子から4方向に別の原子へ足が伸びています。 この足は、別の足とarccos(-1/3)だけ平等に離れています。
これこそがまさに我々が欲するレンダリングする方向です。
このぞれぞれの方向へカメラを向けてレンダリングし、視野角をarccos(-1/3)にしてレンダリングすれば、我々の欲する環境マップを得ることができます。

あとは、作成した4枚のテクスチャをパラボロイドマップにメッシュで張り込んで、2枚のパラボロイドマップを作成します。
このとき、zテストを効かせて描画すれば、中心に近い、よりゆがみが少ない画像が参照されます。
今回は、ゆがみを均等にするために、テクスチャの境界線の中心がパラボロイドマップの中心を通過するようにしてみました。

実際の描画時は、デュアルパラボロイドマッピングを行います。

今回は、パラボロイドマップですが、ひょっとしたらキューブマップに張り込んだほうがハードウェアのサポートが得られるので、お得かもしれません。
また、張るときもメッシュで張り込むのではなく、テクスチャを使ったルックアップテーブルを使うほうがひょっとしたらいいのかもしれません。

■ビュー行列

ソースを全て解説すると長くなるので、肝となるビュー及びア射影行列の作成に注力します。
4つのカメラの向きは、arccos(-1/3)だけ離れていることは分かっているので、1つをz軸に、残りをそこからarccos(-1/3)まわしたものを作成します。
その後、パラボロイドに射影したときに境界の中心がパラボロイドの中心になるように、全体をarccos(-1/3)/2だけまわします。
今回の方法のほかにも、ダイアモンドの構造のそれぞれの位置から直接カメラの向きを求める方法も考えられます。

main.cpp
0313:     // Tetrahedron Matrix
0314:     D3DXVECTOR3 lookat[4];
0315:     D3DXVECTOR3 center;
0316:     D3DXMATRIX mRot;
0317:     float theta = 109.0f * D3DX_PI / 180.0f;
0318:     center = D3DXVECTOR3(0,0,0);
0319:     lookat[0] = D3DXVECTOR3(0,0,1);
0320:     lookat[1] = D3DXVECTOR3(sinf(theta)*cosf(0.0*D3DX_PI/3.0f),sinf(theta)*sinf(0.0*D3DX_PI/3.0f), cosf(theta));
0321:     lookat[2] = D3DXVECTOR3(sinf(theta)*cosf(2.0*D3DX_PI/3.0f),sinf(theta)*sinf(2.0*D3DX_PI/3.0f), cosf(theta));
0322:     lookat[3] = D3DXVECTOR3(sinf(theta)*cosf(4.0*D3DX_PI/3.0f),sinf(theta)*sinf(4.0*D3DX_PI/3.0f), cosf(theta));
0323:     
0324:     D3DXMatrixRotationY( &mRot, -acosf(-1.0f/3.0f)/2.0f );
0325:     D3DXVec3TransformCoord( &lookat[0], &lookat[0], &mRot);
0326:     D3DXVec3TransformCoord( &lookat[1], &lookat[1], &mRot);
0327:     D3DXVec3TransformCoord( &lookat[2], &lookat[2], &mRot);
0328:     D3DXVec3TransformCoord( &lookat[3], &lookat[3], &mRot);
0329:     for( i = 0; i < 4; i++ )
0330:     {
0331:         D3DXMatrixLookAtLH( &m_mViewTetrahedron[i], &center, &lookat[i], &lookat[(i+1)&3] );
0332:     }

アップベクトルは、別のカメラの向きを採用しました。このようにすると、張り合わせるときに、自分が採用される部分が逆3角形になることが分かるので、今後、使わない部分を除去する最適化などを図ることができます。

射影行列ですが、視野角をarccos(-1/3)、アスペクト比を1になるように作成します。
と思ったのですが、実際にやってみると、138度ほど広げないと隙間が開いてしまいました???

main.cpp
0317:     float theta = 109.0f * D3DX_PI / 180.0f;
0333:     D3DXMatrixPerspectiveFovLH( &m_mProjTetrahedron, 1.27f * theta, 1.0f, 0.1f, 100.0f );

■最後に

ということで、線型なレンダリングでキューブマップよりも負荷が低いテトラヘドロン環境マップですが、やっぱり1回別の環境マップへ射影するのではなくて、ハードウェアサポートされて欲しいですね。

それにしても、これからレースゲームとかで使われるようになるのでしょうか?





もどる

imagire@gmail.com