まぁ、今まで引き伸ばしていたネタですが、ハードウェア影マップです。
今までのテクスチャーのα成分を使っていたのとは違って、深度バッファの 24bits を使うので、3倍の解像度を手に入れたことになります。
また、今回は、円形のテクスチャーを使って、スポットライトの効果を出しています。
まぁ、いつものように適当にファイルが入っています。
vs.vsh | 深度描画の頂点シェーダー。 |
ps.psh | 深度描画のピクセルシェーダー。 |
shadow.vsh | 影作成の頂点シェーダー。 |
shadow.psh | 影作成のピクセルシェーダー。 |
draw.cpp | メインの描画部分。 |
draw.h | 描画の各関数の定義。 |
bg.cpp | 背景の描画。 |
main.h | 基本的な定数など。 |
main.cpp | 描画に関係しないシステム的な部分。 |
load.cpp | ロード。 |
load.h | ロードのインターフェイス。 |
あと、モデルのファイル、実行ファイル及び、プロジェクトファイルが入っています。
BBXで、「DirectX9 + GeForce FX でシャドウマップ」というタイトルで、このサンプルがGeForceFXで動作しないという報告があがっていました。
GeForceFX では、GeForce3,4から、DirectX8で未定義だった部分に関して変更が図られています。
GeForce3,4では、テクスチャ命令(tex)で読み込まれる方法が「射影テクスチャ参照(HLSLのtex2Dproj)」だったものが、「2D のテクスチャ参照(HLSLのtex2D)」に変更されました。
正直言って、射影テクスチャ参照だけあれば十分だったので、この変更はいたいところです。
この対応のために、頂点シェーダのテクスチャ座標の計算を
shadow.vsh 0024: ; シャドウバッファ 0025: m4x4 oT1, v0, c4 ; 影マップ 0026: m4x4 oT2, v0, c4 ; ライトテクスチャー
から、
shadow.vsh 0027: m4x4 r1, v0, c4 0028: rcp r0.w, r1.w 0029: mul oT1, r1, r0.w 0030: mul oT2, r1, r0.w
に変更する必要があります。
影の生成部分では問題が無いのですが、ライトテクスチャのサンプリングでは、射影計算を行ってからピクセル単位の補間を行うので、この計算では誤差が生じてしまいます。
正直言って、射影テクスチャ参照だけあれば十分だったので、この変更はいたいところです。
他にも、テクスチャにNULLが設定されていたときのサンプリング結果が、0から1に変更されていました。
今回は、テクスチャしかない場合と、頂点色しかない場合でピクセルシェーダプログラムをきちんと分けて、推奨されるプログラムに変更しました。
なお、今回は、DirectX 8のまま対応しましたが、DirectX 9では、行列に掛けるバイアスに関してビット深度分のスケーリングをしなくてよくなったようです。つまり、DirectX 9で実装する場合は、
main.cpp 0345: unsigned int range = 0xFFFFFFFF >> (32 - 24);
を
main.cpp 0345: unsigned int range = 1;
に変更してください。
なお、今回の変更でも、シャドウバッファのClearが数十回に1回しか行われないなどの、不十分な変更になっています。
ハードウェアシャドウマップはDirectX 8世代での効果的な影の作成方法なので、DirectX 9世代では、別の方法を考察したほうがよいかもしれません。
今回の件に関して、はりまお さん、CRAKF さん、非常に参考になりました。ありがとうございました。
最初にHWシャドウマップをサポートしているかチェックしましょう。
初期化時にCheckDeviceFormat で、深度バッファをテクスチャーとして使えるかチェックします。
main.cpp 0136: ;//----------------------------------------------------------------------------- 0137: ;// Name: InitD3D() 0138: ;// Desc: Direct3D の初期化 0139: ;//----------------------------------------------------------------------------- 0140: HRESULT InitD3D( HWND hWnd ) 0141: { 0142: ;// Direct3D オブジェクトを作成 0143: if (NULL == (s_lpD3D = Direct3DCreate8(D3D_SDK_VERSION))){ 0144: MessageBox(NULL,"Direct3D の作成に失敗しました。",CAPTION,MB_OK | MB_ICONSTOP); 0145: return E_FAIL; 0146: } 0147: 0148: ;// 現在の画面モードを取得 0149: D3DDISPLAYMODE d3ddm; 0150: if( FAILED( s_lpD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) ) return E_FAIL; 0151: ;// ビデオカードの能力を調べる 0152: D3DCAPS8 caps; 0153: s_lpD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps); 0154: 途中略 0184: 0185: ;// ハードウェアシャドウマップのサポートのチェック 0186: if(FAILED(s_lpD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,D3DFMT_X8R8G8B8, 0187: D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE,s_d3dpp.AutoDepthStencilFormat))) 0188: return -1; 0189: 0190: DWORD vs[] = {D3DCREATE_HARDWARE_VERTEXPROCESSING, D3DCREATE_MIXED_VERTEXPROCESSING, D3DCREATE_SOFTWARE_VERTEXPROCESSING,0 }; 0191: D3DDEVTYPE ps[] = {D3DDEVTYPE_HAL, D3DDEVTYPE_REF, (D3DDEVTYPE)0}; 0192: 0193: DWORD v = (caps.VertexShaderVersion < D3DVS_VERSION(1,0)) ? 1 : 0; 0194: DWORD p = (caps.PixelShaderVersion < D3DPS_VERSION(1,0)) ? 1 : 0; 0195: 0196: for(;ps[p];p++){ 0197: for(;vs[v];v++){ 0198: if(SUCCEEDED(s_lpD3D->CreateDevice(D3DADAPTER_DEFAULT, ps[p],hWnd,vs[v],&s_d3dpp,&s_lpD3DDEV))) goto set_up; 0199: } 0200: } 0201: set_up: 0202: return S_OK; 0203: }
成功したら、CreateTexture で、テクスチャーを生成します。
色成分のテクスチャーと深度のテクスチャーの2つを作る必要があります。
深度バッファのテクスチャーは、D3DFMT_D24S8 の深度のフォーマットを使って作成します。
また、レンダリングする時は、サーフェスを指定する必要があるので、同時にサーフェスも生成します。
あと、ライトのテクスチャーもロードします(この部分はnVIDIAのサンプルの丸写しがばればれですな)。
draw.cpp 0166: //----------------------------------------------------------------------------- 0167: // Name: InitRenderTexture() 0168: // Desc: レンダリングテクスチャー用の下準備 0169: //----------------------------------------------------------------------------- 0170: HRESULT InitRenderTexture(LPDIRECT3DDEVICE8 lpD3DDev) 0171: { 0172: HRESULT hr; 0173: DWORD i; 0174: 途中略 0202: 0203: // 描画用テクスチャーを用意する 0204: if( FAILED(hr = lpD3DDev->GetRenderTarget(&pBackbuffer))) return hr; 0205: if(FAILED(lpD3DDev->GetDepthStencilSurface(&lpZbuffer))) return hr; 0206: 0207: if(FAILED(hr = lpD3DDev->CreateTexture(SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT, 1 0208: , D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8 0209: , D3DPOOL_DEFAULT, &pTextureCol))) 0210: return hr; 0211: if(FAILED(hr = lpD3DDev->CreateTexture(SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT, 1 0212: , D3DUSAGE_DEPTHSTENCIL, D3DFMT_D24S8 0213: , D3DPOOL_DEFAULT, &pTextureZ))) 0214: return hr; 0215: 0216: if(FAILED(pTextureCol->GetSurfaceLevel(0, &pTextureSurfaceCol))) return E_FAIL; 0217: if(FAILED(pTextureZ->GetSurfaceLevel(0, &pTextureSurfaceZ))) return E_FAIL; 0218: 0219: 0220: if(FAILED(D3DXCreateTextureFromFileEx(lpD3DDev, 0221: "spotlight.bmp", 0222: SHADOWMAP_WIDTH, 0223: SHADOWMAP_HEIGHT, 0224: 1, 0225: D3DUSAGE_RENDERTARGET, 0226: D3DFMT_A8R8G8B8, 0227: D3DPOOL_DEFAULT, 0228: D3DX_DEFAULT, 0229: D3DX_DEFAULT, 0230: 0, 0231: NULL, 0232: NULL, 0233: &pLightTexture))) return hr; 0234: 0235: 0236: // シェ-ダーのロード 0237: if ( FAILED( CPixelShaderMgr::Load(lpD3DDev, "shadow.psh", &hShadowPixelShader)) ) return hr; 0238: if ( FAILED(CVertexShaderMgr::Load(lpD3DDev, "shadow.vsh", &hShadowVertexShader, dwDecl)) ) return hr; 0239: 0240: return S_OK; 0241: }
SetTextureStageState も初期化時に設定しましょう。
それぞれのテクスチャーの拡大縮小フィルターを双線形にします。
特に、影マップの時には、このフィルターを切って最近接テクセルの色にすると、影ががたがたした色になります。
影マップと、ライトテクスチャーのテクスチャアドレッシング D3DTSS_ADDRESSU(V) は、D3DTADDRESS_CLAMPにして、
範囲を飛び出たテクセルに関して最外テクセルの色を使うようにしないと遠くのテクスチャーに関する結果がおかしなものになります。
draw.cpp 0272: // デカール 0273: lpD3DDev->SetTextureStageState(0, D3DTSS_MINFILTER, D3DTEXF_LINEAR); 0274: lpD3DDev->SetTextureStageState(0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); 0275: lpD3DDev->SetTextureStageState(0, D3DTSS_MIPFILTER, D3DTEXF_NONE); 0276: 0277: // 影マップ 0278: lpD3DDev->SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); 0279: lpD3DDev->SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); 0280: lpD3DDev->SetTextureStageState(1, D3DTSS_MINFILTER, D3DTEXF_LINEAR); 0281: lpD3DDev->SetTextureStageState(1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); 0282: lpD3DDev->SetTextureStageState(1, D3DTSS_MIPFILTER, D3DTEXF_NONE); 0283: 0284: // ライトテクスチャー 0285: lpD3DDev->SetTextureStageState(2, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); 0286: lpD3DDev->SetTextureStageState(2, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); 0287: lpD3DDev->SetTextureStageState(2, D3DTSS_MINFILTER, D3DTEXF_LINEAR); 0288: lpD3DDev->SetTextureStageState(2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); 0289: lpD3DDev->SetTextureStageState(2, D3DTSS_MIPFILTER, D3DTEXF_NONE);
では、描画部分です。2パスで描画します。
SetRenderTarget で、レンダリング対象をテクスチャーに指定します。
深度部分は、今回の肝である深度に関するテクスチャーのサーフェスを指定します。
次にビューポートを変更します。
今回は、描画するビューポートとテクスチャーの解像度が同じなので、設定しなくても良いですが、いつもこんな状況ではないので、指定しておきましょう。
その後、色成分の書き込みを禁止します。
深度テクスチャーだけが必要になります。
レンダリングする量を減らすために、深度だけを更新して速度を稼ぎます。
後は、Clear 命令で深度を初期化します。
その後、シェーダーを設定してモデルを描画します。
2パス目は、1パス目で作成した影マップをテクスチャーに指定します。 また、ライトのテクスチャーも指定します。
draw.cpp 0469: //----------------------------------------------------------------------------- 0470: // Name: Render() 0471: // Desc: Draws the scene 0472: //----------------------------------------------------------------------------- 0473: VOID Render(LPDIRECT3DDEVICE8 lpD3DDev) 0474: { 0475: // ------------------------------------------------------------------------ 0476: // 影マップの作成 0477: // ------------------------------------------------------------------------ 0478: // テクスチャーにレンダリングする 0479: lpD3DDev->SetRenderTarget(pTextureSurfaceCol, pTextureSurfaceZ); 0480: // ビューポートの変更 x y w h min_z max_z 0481: D3DVIEWPORT8 newViewport = {0,0,SHADOWMAP_WIDTH, SHADOWMAP_HEIGHT, 0, 1}; 0482: D3DVIEWPORT8 oldViewport; 0483: lpD3DDev->GetViewport(&oldViewport); 0484: lpD3DDev->SetViewport(&newViewport); 0485: // 高速化のために、深度だけ更新する 0486: lpD3DDev->SetRenderState(D3DRS_COLORWRITEENABLE, 0); 0487: // 深度を初期化する 0488: lpD3DDev->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0x000000, 1.0f, 0L); 0489: 0490: // シェーダーの設定 0491: lpD3DDev->SetVertexShader(hVertexShader); 0492: lpD3DDev->SetPixelShader(hPixelShader); 0493: 0494: DrawModel(lpD3DDev, 1); 0495: 0496: // ------------------------------------------------------------------------ 0497: // シーンの作成 0498: // ------------------------------------------------------------------------ 0499: // 描画を戻す 0500: lpD3DDev->SetRenderTarget(pBackbuffer, lpZbuffer ); 0501: lpD3DDev->SetViewport(&oldViewport); 0502: lpD3DDev->SetRenderState(D3DRS_COLORWRITEENABLE, 0x7); 0503: lpD3DDev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x000000, 1.0f, 0L); 0504: 0505: // シェーダーの設定 0506: lpD3DDev->SetVertexShader(hShadowVertexShader); 0507: lpD3DDev->SetPixelShader(hShadowPixelShader); 0508: 0509: lpD3DDev->SetTexture( 1, pTextureZ ); // 影マップ 0510: lpD3DDev->SetTexture( 2, pLightTexture ); // ライトテクスチャー 0511: DrawModel(lpD3DDev, 0); 0512: 0513: // 他でレンダリングしても問題が出ない設定にする 0514: lpD3DDev->SetTexture( 0, NULL ); 0515: lpD3DDev->SetTexture( 1, NULL ); 0516: lpD3DDev->SetTexture( 2, NULL ); 0517: lpD3DDev->SetPixelShader(0); 0518: }
モデルの描画部分を見てみましょう。
draw.cpp 0301: VOID DrawModel(LPDIRECT3DDEVICE8 lpD3DDev, BOOL bShadowMap) 0302: { 0303: D3DXMATRIX mVP, mWorld, mView, mProj; 0304: D3DXMATRIX mVPL, mLight, mViewL, mProjL; 0305: D3DXMATRIX m; 0306: D3DXVECTOR4 vl; 0307: DWORD i;
カメラからの行列が必要です。これは、いつものものです。
0309: // 0310: // 通常レンダリング用の行列を作成 0311: // 0312: // ビュー行列 0313: D3DXVECTOR3 eye = D3DXVECTOR3(0.0f,1.4f*MeshRadius,2.5f*MeshRadius); 0314: D3DXVECTOR3 lookAt = D3DXVECTOR3(0.0f, 0.0f, 0.0f); 0315: D3DXVECTOR3 up = D3DXVECTOR3(0.0f, 1.0f, 0.0f); 0316: D3DXMatrixLookAtLH(&mView, &eye, &lookAt, &up); 0317: // 射影行列 0318: D3DXMatrixPerspectiveFovLH(&mProj 0319: , D3DXToRadian(60.0f) // 視野角 0320: , (float)WIDTH/(float)HEIGHT // アスペクト比 0321: , z_min, z_max // 最近接距離,最遠方距離 0322: ); 0323: 0324: mVP = mView * mProj;
ライトからの行列も作ります。
0326: // 0327: // ライト方向からのレンダリング用の行列を作成 0328: // 0329: // ビュー行列 0330: D3DXVECTOR3 l_eye = 30.0f*lightDir; 0331: D3DXVECTOR3 l_lookAt = D3DXVECTOR3(0.0f, 0.0f, 0.0f); 0332: D3DXVECTOR3 l_up = D3DXVECTOR3(0.0f, 1.0f, 0.0f); 0333: D3DXMatrixLookAtLH(&mViewL, &l_eye, &l_lookAt, &l_up); 0334: // 射影行列 0335: D3DXMatrixPerspectiveFovLH(&mProjL 0336: , D3DXToRadian(60.0f) // 視野角 0337: , (float)SHADOWMAP_WIDTH/(float)SHADOWMAP_HEIGHT// アスペクト比 0338: , 1.0f, 70.0f // 最近接距離,最遠方距離 0339: ); 0340: mVPL = mViewL * mProjL;
次に、テクスチャー座標系に関する変換行列を用意します。
射影空間が中心原点なので、左上を中心とするテクスチャー空間に変換するために挿入します。
また、深度に関しては、0から2深度バッファの解像度-1がテクスチャー空間の範囲になります。
一方、射影空間では0~1が深度の範囲なので拡大して範囲をあわせます。
また、オフセットもここで追加します。
ところで気づいたのですが、逆カリングを使うときは、物体のライトから裏のポリゴンに関して影の境界があります。
その部分の影がちらついてしまいます。従って、オフセットを裏の部分がちらつかないように調整しなくてはなりません。
0342: // 射影空間から、テクスチャーの空間に変換する 0343: float fOffsetX = 0.5f + (0.5f / (float)SHADOWMAP_WIDTH); 0344: float fOffsetY = 0.5f + (0.5f / (float)SHADOWMAP_HEIGHT); 0345: unsigned int range = 0xFFFFFFFF >> (32 - 24); 0346: float fBias = +0.000001f * (float)range;// カリングを反対にするので、通常とは逆のオフセット 0347: D3DXMATRIX mScaleBias( 0.5f, 0.0f, 0.0f, 0.0f, 0348: 0.0f, -0.5f, 0.0f, 0.0f, 0349: 0.0f, 0.0f, (float)range, 0.0f, 0350: fOffsetX, fOffsetY, fBias, 1.0f ); 0351: 0352: mLight = mVPL * mScaleBias;
次に、逆カリングの設定をします。
0354: // 0355: // レンダリング状態の設定 0356: // 0357: if(bShadowMap){ 0358: lpD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW); 0359: }else{ 0360: lpD3DDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); 0361: }
いよいよモデルの描画です。 ワールド行列を作ります。
0363: // 0364: // 外側もでる描画 0365: // 0366: int t = timeGetTime()%314159; 0367: D3DXMatrixTranslation(&m, 1.5f*MeshRadius, 0,0); 0368: D3DXMatrixRotationY( &mWorld, t/1000.0f ); 0370: mWorld = m * mWorld;
次に、影マップか、バックバッファへの描画かによって、変換する行列を切り替えます。
バックバッファへの描画かのときは、ライトからのテクスチャー座標への変換行列も設定します。
0372: if(bShadowMap){ 0373: m = mWorld * mVPL; 0374: }else{ 0375: m = mWorld * mLight; 0376: D3DXMatrixTranspose( &m , &m); 0377: lpD3DDev->SetVertexShaderConstant(4,&m, 4); 0378: 0379: m = mWorld * mVP; 0380: } 0381: D3DXMatrixTranspose( &m , &m); 0382: lpD3DDev->SetVertexShaderConstant(0,&m, 4);
あとは、ライトの設定をして、
0384: D3DXMatrixInverse( &m, NULL, &mWorld); 0385: D3DXVec4Transform(&vl, &lightDir, &m); 0386: D3DXVec4Normalize(&vl, &vl); 0387: vl[3] = 0.1f;// w成分は環境色の強さ 0388: lpD3DDev->SetVertexShaderConstant(13, &vl, 1);
モデルを描画します。
0390: lpD3DDev->SetStreamSource(0, pMeshVB, sizeof(D3D_CUSTOMVERTEX)); 0391: lpD3DDev->SetIndices(pMeshIndex,0); 0392: 0393: for(i=0;i<dwNumMaterials;i++){ 0394: //色をセット 0395: D3DXVECTOR4 vl; 0396: vl.x = pMeshMaterials[i].Diffuse.r; 0397: vl.y = pMeshMaterials[i].Diffuse.g; 0398: vl.z = pMeshMaterials[i].Diffuse.b; 0399: lpD3DDev->SetVertexShaderConstant(14, &vl, 1); 0400: 0401: lpD3DDev->SetTexture(0,pMeshTextures[i]); 0402: lpD3DDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0403: pSubsetTable[i].VertexStart, 0404: pSubsetTable[i].VertexCount, 0405: pSubsetTable[i].FaceStart * 3, 0406: pSubsetTable[i].FaceCount); 0407: } 以降略
さて、合成するときのシェーダーです。
合成方法を見ましょう。
今回の(いままでもそうですが)モデルは、頂点色か、テクスチャーしかないので、 頂点色とデカールテクスチャーの色を加算で合成します。
影マップは、深度テストに合格したピクセルが白くレンダリングされます。
一部見ると、灰色の部分がありますということは、深度バッファでテストした結果を
テクセルサンプリングするときに双線形フィルタでサンプリングするようですね。
この結果に対して、定数を足してます。
影になる部分が完全に黒にはならないで、薄暗くなります。
モデルの色と影の結果を乗算して、影マップによる影をつけます。
次にライト方向からテクスチャー座標を算出したライトのテクスチャーの色と今の結果を掛け、最終的な結果を得ます。
今考えれば、影マップと、ライトテクスチャーを乗算合成した後定数を加算して、影の下限値をつけた後、
モデルの色をつけたほうが、影が重なったところが真っ黒にならなくて良いですね。
ピクセルシェーダーでは、今言ったことを計算します。
0001: ; shadow.psh 0002: ; 0003: ps.1.1 0004: 0005: def c0, 0.3f, 0.3f, 0.3f, 0.0f 0006: 0007: tex t0 ; デカール 0008: tex t1 ; 影マップ 0009: tex t2 ; ライトテクスチャー 0010: 0011: add r0, t0, v0 ; モデルの色は頂点+デカール 0012: add t1, t1, c0 ; 影マップの減衰リミットを決める 0013: mul r0, r0, t1 ; モデルの色*影 0014: mul r0, r0, t2 ; ライトの範囲を反映
頂点シェーダーでは、ピクセルシェーダーに送るテクスチャー座標などを設定します。
0001: ; Shadow.vsh 0002: ; c0-3 -- world + ビュー + 透視変換行列 0003: ; c4-7 -- world + ライトビュー + 透視変換行列 0004: ; c13 -- ライトのベクトル (w成分は環境光の強さ) 0005: ; c14 -- ライトの色(メッシュの色) 0007: ; 0008: ; v0 頂点の座標値 0009: ; v3 法線ベクトル (w成分は1.0f) 0010: ; v7 テクスチャ座標 0011: 0012: vs.1.0 0013: 0014: ;座標変換 0015: m4x4 oPos, v0, c0 0016: 0017: ; ((L・N) + L.w)*c14 (平行光源のライティング) 0018: dp4 r0.w, v3, c13 0019: mul oD0, r0.w, c14 0020: 0021: ; デカールテクスチャー 0022: mov oT0, v7 0023: ; シャドウバッファ 0024: m4x4 oT1, v0, c4 ; 影マップ 0025: m4x4 oT2, v0, c4 ; ライトテクスチャー
座標変換や、頂点色は普通の計算です。
テクスチャーは、デカールはモデルに設定されたテクスチャー座標を出力します。
影マップと、ライトのテクスチャーは頂点座標を透視変換して、出力します。
影マップのテクスチャーには、4次元ベクトルを出力する必要があります。
出力した深度値と、深度テクスチャーに保存された値とを比較して、描こうとするピクセルの深度がテクスチャーの値よりも小さいときに白色を出力します。
実は、ライトのテクスチャーには必要ないです(x、y、wの3つの座標値に関して出力すれば十分です)。マクロ命令で短く描きたかったので使っただけですので、
命令数を少なくしたい方は、展開してz成分に関する出力を無くしてください。
やっぱり24bitsあると綺麗ですね。
ただ、nVIDIA 専用というのがどうも…
将来、テクスチャーのフォーマットが浮動小数点数を扱えるようになったら、いらなくなるんでしょうね。