正規化キューブマップという言葉はよく聞きますが、実際に見たことがありません。 では、ちょっと見てみましょう。
まぁ、いつものように適当にファイルが入っています。
vs.vsh | 頂点シェーダー。 |
ps.psh | ピクセルシェーダー。 |
draw.cpp | メインの描画部分。 |
draw.h | 描画の各関数の定義。 |
bg.cpp | 背景の描画。 |
main.h | 基本的な定数など。 |
main.cpp | 描画に関係しないシステム的な部分。 |
load.cpp | ロード。 |
load.h | ロードのインターフェイス。 |
あと、実行ファイル及び、プロジェクトファイルが入っています。
正規化キューブマップとは、ベクトルを正規化するために使います。
実際の使い方としては、頂点シェーダで法線ベクトル等をピクセルシェーダに渡すことが多いです。
渡されたベクトルはピクセル単位では、正規化されていないので、
正規化キューブマップを使って正規化してスペキュラ計算などを行ないます。
正規化キューブマップとは、次の色をしたキューブマップです(切り取って張り合わせてみよう!)。
正規化キューブマップは、キューブマップの各ピクセルの位置に関して正規化したベクトルを、 法線マップのように色落とし込みます。
正規化キューブマップを使って、ワールド座標での法線ベクトルをマッピングすると、
になります。
手前の面が青、上の面が緑、左側が赤と、
法線ベクトルの向きに応じて色が付けられているのが見て取れます。
ハーフベクトルなら
になります。
ハーフベクトルは、視線ベクトルが頂点座標で決まるので、位置に関して滑らかな変化を見せます。
肝心の正規化キューブマップの作り方ですが、基本的にはキューブマップなので、
あらかじめ、LPDIRECT3DCUBETEXTURE8のオブジェクトを用意しておき、
CreateCubeTextureでテクスチャを生成します。
あとは、算術的にテクスチャを作成する関数のD3DXFillCubeTextureを使ってテクスチャを塗りつぶします。
draw.cpp 0031: LPDIRECT3DCUBETEXTURE8 pCubeMap=NULL; 0242: // 正規化キューブマップの作成 0243: hr = lpD3DDev->CreateCubeTexture( 512, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, 0244: D3DPOOL_DEFAULT , &pCubeMap ); 0245: if ( FAILED(hr) ) return hr; 0246: hr = D3DXFillCubeTexture( pCubeMap, make_normalize_cubemap, NULL ); 0247: if ( FAILED(hr) ) return hr;
キューブマップを塗りつぶす部分は、次のようになります。
テクスチャの座標を正規化した後に、0~1に値をクランプして、色として出力します。
draw.cpp 0207: //----------------------------------------------------------------------------- 0208: // 正規化キューブマップの塗りつぶし 0209: //----------------------------------------------------------------------------- 0210: void make_normalize_cubemap( D3DXVECTOR4 *pOut, D3DXVECTOR3 *pTexCoord, 0211: D3DXVECTOR3 *pTexelSize, LPVOID pData) 0212: { 0213: D3DXVECTOR3 n; 0214: D3DXVec3Normalize( &n, pTexCoord ); 0215: 0216: pOut->x = 0.5f*(n.x + 1.0f); 0217: pOut->y = 0.5f*(n.y + 1.0f); 0218: pOut->z = 0.5f*(n.z + 1.0f); 0219: pOut->w = 1.0f; 0220: }
実際の使い方は、普通のテクスチャと何ら変わりません。 SetTexture で、設定して、テクスチャを使います。
0403: lpD3DDev->SetTexture( 0, pCubeMap ); 0404: lpD3DDev->SetTexture( 1, pCubeMap );
では、表示するためのシェーダを見ていきましょう。
今回は、oT0 に法線ベクトル、oT1にハーフベクトルを出力します。
vs.vsh 0001: ; c0-3 -- world + ビュー + 透視変換行列 0002: ; c4-7 -- world 0003: ; c12 -- {0.0, 0.5, 1.0, 2.0} 0004: ; 0005: ; v0 頂点の座標値 0006: ; v3 法線ベクトル (w成分は1.0f) 0007: 0008: vs.1.0 0009: 0010: ;座標変換 0011: m4x4 oPos, v0, c0 0012: 0013: ; Lambert 拡散 0014: dp4 r0, v3, c13 0015: max r0, r0, c13.w 0016: mul oD0, r0, c14 0017: 0018: ; 視線ベクトル 0019: m4x4 r0, v0, c4 0020: add r0, c15, -r0 0021: dp3 r0.w, r0, r0 ; 正規化 0022: rsq r0.w, r0.w 0023: mul r0, r0, r0.w 0024: 0025: ; Half ベクトル 0026: add oT1, r0, c11 0027: 0028: ; ワールド座標での法線ベクトル 0029: m3x3 oT0, v3, c4
座標変換と、拡散光は普通に計算します。
あとは、正規化した視線ベクトルとライトベクトルを足して、ハーフベクトルを計算し、
正規化は正規化キューブマップがやってくれるので、正規化しないで出力します。
法線ベクトルは、ワールド座標系に変換してから出力します。
ここまで作って気が付いたのですが、ローカル座標系で計算すれば命令数が減らせそうですね。
ピクセルシェーダでは、tex 命令を使えばキューブマップをサンプリングしてくれるので、 法線、ハーフベクトル共に正規化キューブマップで正規化して、べき乗して鏡面反射成分を求めます。
ps.psh 0001: ; ps.psh 0002: 0003: ps.1.1 0004: 0005: def c0, 0.7f, 0.7f, 0.7f, 1.0f ; ライトの色 0006: 0007: tex t0 0008: tex t1 0009: 0010: dp3_sat r0, t1_bx2, t0_bx2 ; (N・H) 0011: mul r0, r0, r0 ; (N・H)^2 0012: mul r0, r0, r0 ; (N・H)^4 0013: mul r0, r0, r0 ; (N・H)^8 0014: ;mul r0, r0, r0 ; (N・H)^16 0015: ;mul r0, r0, r0 ; (N・H)^32 0016: 0017: mad r0, r0, c0, v0 ; out = co*(N・H)^n + 拡散光
_bx2修飾子をつけると、サンプリングした色をそのままベクトルとして使えます。
後は、内積の負の部分は要らないので、_sat修飾子で負の部分を切り取りました。
さて、タイトルの絵のキューブマップの大きさは、512x512です。 このテクスチャは、512x512x6(枚)x4(bytes/pixel)=6Mbytes のメモリを使っています。 現在の128MBが標準のビデオメモリとしては5%程度の大きさですが、皆さんは、この大きさを許せますか?
では、サイズが変わったときの変化を見ていきましょう。
テクスチャのサイズを16x16にすると、次の画像が得られます。
中央の車の窓に注目してください。ひし形の模様が見えると思います。
これが解像度が小さいテクスチャの結果です。
動かしてみるとわかるのですが、このひし形の模様は車が動いてもそれほど動かず、
結構目立ちます。
比較として、512x512の画像を見ると、
のように、模様は目立ちませんが、ざらついています。
個人的には、この画像で、1024x1024の大きさを持つと非常に滑らかになると思います
(24Mですね。勇気がいるサイズです)。
では、中間を見てみますと、128x128で
になります。ぱっと見、模様は見えませんが、やはり模様はあって、
見る人が見れば気になるレベルになっています。
なんともいえませんね。
メモリと相談して、サイズを決めてください。
あっ、そういえば、ミップマップをしていないので、それで変わるかも。
さて、正規化キューブマップをやってみました。
これからのスペキュラ計算は、ピクセルシェーダによる計算に移っていくとは思いますが、
Xbox や、DirectX8世代のグラフィックチップの対応にまだまだ使われる技術だと思います。
一個作っておけば、使いまわせるので、1つ用意されてはいかがでしょうか。