今回は、キューブ環境マップを用いた、反射と屈折です。
知っている人は、『頂点シェーダを使った屈折エフェクト~ Cutting Edge DX 8 - 第 3 回目 ~』のパクリだといってしまってはいけません。
今回のソースは、次のものです。
内容は次のとおりになっています。
main.h | 基本的な定数など。変更が無いので、今回も出番無し。 |
main.cpp | 描画に関係しないシステム的な部分。一度作ればあまり書き換えない。 |
draw.h | 描画の各関数の定義。特に意味無いので出番無し。 |
draw.cpp | メインの描画部分。主にここが説明される。 |
vs.vsh | オブジェクト(クマさん)用の頂点シェーダープログラム。これ大事。 |
cube.vsh | 背景用の頂点シェーダープログラム。てきと~。 |
あと、モデルとして、kuma.xと、背景用テクスチャーearth_0.bmp~earth_0.bmp、earth_u.bmp、earth_d.bmp
及び、earth.dds が環境マップ用のテクスチャーとして存在します。
勿論、VC++ でコンパイルするためのプロジェクトファイル MyBase.dsw MyBase.dsp や、実行ファイル MyBase.exe もついてきます。
各ソースは、リンクを張ったので、クリックしてみてください (最近、『Web 上でソースを見せない奴は、真にソースを公開していない』というのを見てカチンと来たため、 わざわざリンクを張ったというのは内緒です)。
キューブマップとは、6枚のテクスチャーを使って、立方体で取り囲んだテクスチャーです。
さて、こんなものをどうやって作るのでしょう。
実は、DirectX8 のSDK にツールが含まれています。
[Microsoft DirectX 8 SDK]-[DirectX Utilities]-[DirectX Texture Tool]
がそれです。
Texture Tool の[File]-[New Texture]を選択して、Type を Cubemap Textureにして、Surface は、A8R8G8B8 で、作ります。
その後は、テクスチャーを6枚用意して、{View]-[Cube Map Face]で、見る面を選択した後、
[File]-[Open Onto This Cubemap Face]を使って、各面のテクスチャーを読み込みます。
ちなみに、今回の場合は、下のテクスチャーに対して、
X Positive(正) | : earth_0.bmp |
Z Negative(負) | : earth_1.bmp |
X Negative(負) | : earth_2.bmp |
Z Positive(正) | : earth_3.bmp |
Y Positive(正) | : earth_u.bmp |
Y Negative(負) | : earth_d.bmp |
で、貼り付けています。
各画像は、メルカトル図法の元画像の
を、黄色の線のように切り刻んでから、
上下に関しては、PhotoShop の極座標の変形のフィルターを使って加工し、
サイズを調整して出力しました。
しっかし、めんどくさいっすね。何か方法はないのかしらん。
今回は、頂点シェーダーだけでなく、キューブマップをサポートしているかどうかも調べなければなりません。
めんどくさかったので、caps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP で、サポート具合を調べて、
サポートしていなければ、全部ソフトウェアで実行するようにしました。
よい子は、頂点シェーダーは別に調べて、適切な初期化をして下さい。
//----------------------------------------------------------------------------- // Desc: Direct3D の初期化 //----------------------------------------------------------------------------- HRESULT InitD3D( HWND hWnd ) { // Direct3D オブジェクトを作成 if (NULL == (s_lpD3D = Direct3DCreate8(D3D_SDK_VERSION))){ MessageBox(NULL,"Direct3D の作成に失敗しました。",CAPTION,MB_OK | MB_ICONSTOP); return E_FAIL; } // 現在の画面モードを取得 D3DDISPLAYMODE d3ddm; if( FAILED( s_lpD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) ) return E_FAIL; // Direct3D 初期化パラメータの設定 ZeroMemory(&s_d3dpp, sizeof(D3DPRESENT_PARAMETERS)); s_d3dpp.BackBufferCount = 1; if (FULLSCREEN){ s_d3dpp.Windowed = FALSE; s_d3dpp.BackBufferWidth = WIDTH; s_d3dpp.BackBufferHeight = HEIGHT; }else{ s_d3dpp.Windowed = TRUE; s_d3dpp.BackBufferWidth = 0; s_d3dpp.BackBufferHeight = 0; } // ウインドウ : 現在の画面モードを使用 s_d3dpp.BackBufferFormat = d3ddm.Format; s_d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; s_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; s_d3dpp.hDeviceWindow = hWnd; // Z バッファの自動作成 s_d3dpp.EnableAutoDepthStencil = TRUE; s_d3dpp.AutoDepthStencilFormat = D3DFMT_D16; D3DCAPS8 caps; s_lpD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps); if( 0 == ( caps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP ) ){ // キューブマップをサポートしていなかったら、REF s_lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&s_d3dpp,&s_lpD3DDEV); return S_OK; } if(caps.VertexShaderVersion < D3DVS_VERSION(1,0)){ //vertex shader 1.0 をhwサポートしない場合はD3DCREATE_SOFTWARE_VERTEXPROCESSINGする必要があります if(FAILED(s_lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,&s_d3dpp,&s_lpD3DDEV))){ if(FAILED(s_lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE,&s_d3dpp,&s_lpD3DDEV))){ if (FAILED(s_lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,&s_d3dpp,&s_lpD3DDEV))){ return FALSE; } } } }else{ // デバイスの作成 - T&L HAL if (FAILED(s_lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,hWnd,D3DCREATE_HARDWARE_VERTEXPROCESSING,&s_d3dpp,&s_lpD3DDEV))){ // 失敗したので HAL で試行 if (FAILED(s_lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&s_d3dpp,&s_lpD3DDEV))){ // 失敗したので REF で試行 if (FAILED(s_lpD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&s_d3dpp,&s_lpD3DDEV))){ // 結局失敗した MessageBox(NULL,"Direct3D の初期化に失敗しました。",CAPTION,MB_OK | MB_ICONSTOP); return E_FAIL; } } } } return S_OK; }
黄色い部分が、今回の変更点です。
最初に、周りを取り囲んでいる地球のテクスチャーを張った外壁を描画します。
頂点シェーダーで、オブジェクトが描ける人は、とっとと次に進んでください。
使用する(グローバルな)オブジェクトは、次の4つです。
頂点情報が入る頂点バッファ pCubeVB と、ポリゴンが頂点を指定するための pFaceIB、
各面のテクスチャーpCubeTex[6]と、頂点シェーダーhCubeVertexShaderです。
LPDIRECT3DVERTEXBUFFER8 pCubeVB = NULL; LPDIRECT3DINDEXBUFFER8 pFaceIB = NULL; LPDIRECT3DTEXTURE8 pCubeTex[6] = {NULL,NULL,NULL,NULL,NULL,NULL}; // 壁面用テクスチャー DWORD hCubeVertexShader=~0;
頂点シェーダー用に、定義する頂点情報は、次のものを用います。
typedef struct CubeVertex{ D3DXVECTOR3 Position; D3DXVECTOR2 Texture; } CubeVertex; DWORD dwCubeDecl[] = { D3DVSD_STREAM(0), D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ), //D3DVSDE_POSITION, 0 D3DVSD_REG(D3DVSDE_TEXCOORD0,D3DVSDT_FLOAT2 ), //D3DVSDE_TEXCOORD0, 7 D3DVSD_END() };
頂点と座標だけの、今まで取り扱ったものの中で、いちばん簡単なものになっています。
次のソースは、draw.cpp の各オブジェクトに関する初期化関数である InitRender の一部分です。
外壁に必要な部分だけを抜き出しました。
大きさが、2.0f の正方形の箱を作成します。
// ------------------------------------------------------------------------ // 外壁の初期化 // ------------------------------------------------------------------------ D3DXVECTOR3 CubePosition[] = { D3DXVECTOR3(-1.0f, -1.0f, -1.0f), D3DXVECTOR3(-1.0f, 1.0f, -1.0f), D3DXVECTOR3( 1.0f, 1.0f, -1.0f), D3DXVECTOR3( 1.0f, -1.0f, -1.0f), D3DXVECTOR3(-1.0f, -1.0f, 1.0f), D3DXVECTOR3(-1.0f, 1.0f, 1.0f), D3DXVECTOR3( 1.0f, 1.0f, 1.0f), D3DXVECTOR3( 1.0f, -1.0f, 1.0f), }; // 前(z=-1) 左(x=-1) 後(z=+1) 右(x=+1) 上(y=+1) 下(y=-1) WORD CubeVertList[] = {0,1,2,3, 4,5,1,0, 7,6,5,4, 3,2,6,7, 1,5,6,2, 4,0,3,7}; D3DXVECTOR2 CubeUV[] = { D3DXVECTOR2(1.0f, 1.0f), D3DXVECTOR2(1.0f, 0.0f), D3DXVECTOR2(0.0f, 0.0f), D3DXVECTOR2(0.0f, 1.0f), }; // 頂点バッファの作成 CubeVertex *pDstCube; lpD3DDEV->CreateVertexBuffer( NUM_VERTICES * sizeof(CubeVertex), D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC , 0, D3DPOOL_DEFAULT, &pCubeVB ); // 頂点をセットアップ WORD k=0; pCubeVB->Lock ( 0, 0, (BYTE**)&pDstCube, D3DLOCK_NOOVERWRITE ); for (DWORD i = 0; i < NUM_FACES; i++) { for (DWORD j = 0; j < NUM_VERTICES_PERFACE; j++) { pDstCube->Position = CubePosition[CubeVertList[k++]]; pDstCube->Texture = CubeUV[j]; pDstCube++; } } pCubeVB->Unlock (); // インデックス バッファを作成 WORD *pIndex; lpD3DDEV->CreateIndexBuffer( NUM_INDICES_PERFACE * NUM_FACES * sizeof(WORD), D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC , D3DFMT_INDEX16, D3DPOOL_DEFAULT, &pFaceIB ); // インデックスをセットアップ pFaceIB->Lock ( 0, 0, (BYTE**)&pIndex, D3DLOCK_NOOVERWRITE ); for ( WORD m = 0; m < NUM_FACES; m++) { *pIndex++ = 0 + NUM_VERTICES_PERFACE * m; *pIndex++ = 3 + NUM_VERTICES_PERFACE * m; *pIndex++ = 2 + NUM_VERTICES_PERFACE * m; *pIndex++ = 2 + NUM_VERTICES_PERFACE * m; *pIndex++ = 1 + NUM_VERTICES_PERFACE * m; *pIndex++ = 0 + NUM_VERTICES_PERFACE * m; } pFaceIB->Unlock (); // テクスチャをセットアップ D3DXCreateTextureFromFileEx(lpD3DDEV, "earth_1.bmp" , 0,0,0,0,D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR,0, NULL, NULL , &pCubeTex[0]);// 前(z=-1) D3DXCreateTextureFromFileEx(lpD3DDEV, "earth_2.bmp" , 0,0,0,0,D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR,0, NULL, NULL , &pCubeTex[1]);// 左(x=-1) D3DXCreateTextureFromFileEx(lpD3DDEV, "earth_3.bmp" , 0,0,0,0,D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR,0, NULL, NULL , &pCubeTex[2]);// 後(z=+1) D3DXCreateTextureFromFileEx(lpD3DDEV, "earth_0.bmp" , 0,0,0,0,D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR,0, NULL, NULL , &pCubeTex[3]);// 右(x=+1) D3DXCreateTextureFromFileEx(lpD3DDEV, "earth_u.bmp" , 0,0,0,0,D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR,0, NULL, NULL , &pCubeTex[4]);// 上(y=+1) D3DXCreateTextureFromFileEx(lpD3DDEV, "earth_d.bmp" , 0,0,0,0,D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR,0, NULL, NULL , &pCubeTex[5]);// 下(y=-1) // 頂点シェーダの作成 ID3DXBuffer* pshader; hr = D3DXAssembleShaderFromFile("cube.vsh", 0,NULL,&pshader,NULL); if ( FAILED(hr) ) return hr; hr = lpD3DDEV->CreateVertexShader( dwCubeDecl, (DWORD*)pshader->GetBufferPointer(), &hCubeVertexShader, 0 ); RELEASE(pshader); if ( FAILED(hr) ) return hr;
CreateVertexBuffer で、頂点用のメモリを確保してから、
Lock して、他のプログラムからそのメモリを使えなくしておいて、値を代入します。
次に、Index バッファを作成して、各ポリゴンがどの頂点を使うか指定します。
その後、それぞれの面のテクスチャーを読み込みます。
最後に、使用する頂点シェーダーを読み込みます。
では、描画です。
ビュー行列、射影行列を作ったら、その積を計算して、頂点シェーダーのc0~c3レジスタに入れます。
後は、頂点シェーダーを設定した後、Index バッファを指定して、頂点シェーダーに描画してもらいます。
各面を描く時に、テクスチャーを指定しなおして(張り替えて)描画します。
また、他で何があるか分からないので、テクスチャーレジスタの設定をテクスチャーの色だけを使う(頂点色は使わない)設定にしました。
//----------------------------------------------------------------------------- // Desc: ポリゴンの描画 //----------------------------------------------------------------------------- void Render(LPDIRECT3DDEVICE8 lpD3DDEV) { D3DXMATRIX mWorld, mView, mProj; D3DXVECTOR3 eye, lookAt, up; eye.x = 0.0f; eye.y = 0.0f; eye.z = -2.0f; lookAt.x = 0.0f; lookAt.y = 0.0f; lookAt.z = 0.0f; up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; D3DXMatrixLookAtLH(&mView, &eye, &lookAt, &up); D3DXMatrixPerspectiveFovLH(&mProj ,60.0f*PI/180.0f // 視野角 ,(float)WIDTH/(float)HEIGHT // アスペクト比 ,0.01f // 最近接距離 ,100.0f // 最遠方距離 ); // ------------------------------------------------------------------------ // 立方体 // ------------------------------------------------------------------------ if(NULL != pCubeVB){ // ワールド・ビュー・射影 行列を設定 D3DXMATRIX mat, matT; mat = mView * mProj; D3DXMatrixTranspose(&matT, &mat); lpD3DDEV->SetVertexShaderConstant(0, &matT, 4); // 頂点シェーダを設定 lpD3DDEV->SetVertexShader( hCubeVertexShader ); lpD3DDEV->SetTextureStageState(0,D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); lpD3DDEV->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE); // 立方体を描画 lpD3DDEV->SetStreamSource(0, pCubeVB, sizeof(CubeVertex) ); lpD3DDEV->SetIndices( pFaceIB, 0 ); for (DWORD i= 0; iSetTexture(0, pCubeTex[i]); lpD3DDEV->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, NUM_VERTICES, NUM_INDICES_PERFACE * i , NUM_PRIMS_PERFACE ); } } // ------------------------------------------------------------------------ // オブジェクト // ------------------------------------------------------------------------ // 略 }
今回の頂点シェーダーはあっさりです。
透視変換して、テクスチャーのUV値をそのままコピーするだけです。
vs.1.0 ;座標変換 dp4 oPos.x, v0, c0 dp4 oPos.y, v0, c1 dp4 oPos.z, v0, c2 dp4 oPos.w, v0, c3 ; メッシュのテクスチャー mov oT0, v7
後片付けは、以上グローバルなオブジェクトを開放するだけです。
//----------------------------------------------------------------------------- // Name: CleanRender() //----------------------------------------------------------------------------- void CleanRender(LPDIRECT3DDEVICE8 lpD3DDEV) { // 他の処理 // ------------------------------------------------------------------------ // 外壁 // ------------------------------------------------------------------------ RELEASE(pCubeVB); RELEASE(pFaceIB); if ( hCubeVertexShader != ~0 ){ lpD3DDEV->DeleteVertexShader( hCubeVertexShader ); hCubeVertexShader = ~0; } RELEASE(pCubeTex[0]); RELEASE(pCubeTex[1]); RELEASE(pCubeTex[2]); RELEASE(pCubeTex[3]); RELEASE(pCubeTex[4]); RELEASE(pCubeTex[5]); }
では、ボスキャラのクマさんの描画です。
まず、ミラーコーティングをした完全反射のクマさんを表示します。
pEnvTexture というテクスチャー用のブローバル変数を用意します。
D3DXCreateCubeTextureFromFileEx で、キューブマップ earth.dds を読み込みます。
後は、頂点シェーダーを用いて、モデルを描画する時と同じです。
今回は一寸高速化を意識して、頂点シェーダーの定数レジスタのうち、値が変化しない c12~c14 レジスタを先に設定しました。
LPDIRECT3DCUBETEXTURE8 pEnvTexture = NULL; // 環境マップのテクスチャー //----------------------------------------------------------------------------- // Name: Render() // Desc: ポリゴンの初期化 //----------------------------------------------------------------------------- HRESULT InitRender(LPDIRECT3DDEVICE8 lpD3DDEV) { HRESULT hr; // ------------------------------------------------------------------------ // 外壁の初期化 // ------------------------------------------------------------------------ // 済み // ------------------------------------------------------------------------ // キューブ環境マップ テクスチャのセットアップ // ------------------------------------------------------------------------ D3DXCreateCubeTextureFromFileEx(lpD3DDEV, "earth.dds", D3DX_DEFAULT, 0, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, 0, NULL, NULL, &pEnvTexture); // ------------------------------------------------------------------------ // クマさんの初期化 // ------------------------------------------------------------------------ if ( FAILED(LoadXFile("kuma.x", lpD3DDEV)) ) return hr; // バーテックスシェーダーを作成する hr = D3DXAssembleShaderFromFile("vs.vsh", 0,NULL,&pshader,NULL); if ( FAILED(hr) ) return hr; hr = lpD3DDEV->CreateVertexShader( dwDecl, (DWORD*)pshader->GetBufferPointer(), &hVertexShader, 0 ); RELEASE(pshader); if ( FAILED(hr) ) return hr; // レンダリングの状態の設定 lpD3DDEV->SetRenderState( D3DRS_ZENABLE, TRUE); lpD3DDEV->SetVertexShaderConstant(12, D3DXVECTOR4(0.0f, 0.5f, 1.0f, -1.0f), 1);// 便利な定数 // 屈折用の設定 lpD3DDEV->SetVertexShaderConstant(13, D3DXVECTOR4(1.0f, 1.0f, 1.0f, 0.0f), 1);// 屈折ファクタ (完全反射) lpD3DDEV->SetVertexShaderConstant(14, D3DXVECTOR4(0.0f, 0.0f,-2.0f, 0.0f), 1);// カメラの位置 return S_OK; }
次に描画部分です。
今度は、(大きすぎたので)少し小さくしたり、回したり、位置を調整するワールド行列を作ります。
それ以外は、設定する行列が多かったり、テクスチャーが、環境マップの pEnvTexture であること意外は同じです。
// ---------------------------------------------------------------------------- // Name: Render() // Desc: ポリゴンの描画 //----------------------------------------------------------------------------- void Render(LPDIRECT3DDEVICE8 lpD3DDEV) { D3DXMATRIX mWorld, mView, mProj; D3DXVECTOR3 eye, lookAt, up; eye.x = 0.0f; eye.y = 0.0f; eye.z = -2.0f; lookAt.x = 0.0f; lookAt.y = 0.0f; lookAt.z = 0.0f; up.x = 0.0f; up.y = 1.0f; up.z = 0.0f; D3DXMatrixLookAtLH(&mView, &eye, &lookAt, &up); D3DXMatrixPerspectiveFovLH(&mProj ,60.0f*PI/180.0f // 視野角 ,(float)WIDTH/(float)HEIGHT // アスペクト比 ,0.01f // 最近接距離 ,100.0f // 最遠方距離 ); // ------------------------------------------------------------------------ // 立方体 // ------------------------------------------------------------------------ // 済み // ------------------------------------------------------------------------ // オブジェクト // ------------------------------------------------------------------------ if(NULL != pMeshVB){ D3DXMATRIX m, m2; D3DXMatrixScaling(&mWorld, 0.2f, 0.2f, 0.2f); D3DXMatrixRotationY( &m, timeGetTime()/1000.0f ); D3DXMatrixTranslation(&m2, 0, -0.7f, 0); mWorld = mWorld * m * m2; m = mWorld * mView * mProj; D3DXMatrixTranspose( &m , &m); lpD3DDEV->SetVertexShaderConstant(0,&m, 4); // world + ビュー + 透視変換行列 D3DXMatrixTranspose( &m , &mWorld); lpD3DDEV->SetVertexShaderConstant(4, &m, 4); // world 行列 D3DXMatrixInverse( &m, NULL, &mWorld); lpD3DDEV->SetVertexShaderConstant(8, &m, 4); // world の逆転置行列 lpD3DDEV->SetVertexShader(hVertexShader); //メッシュの描画 lpD3DDEV->SetStreamSource(0, pMeshVB, sizeof(D3D_CUSTOMVERTEX)); lpD3DDEV->SetIndices(pMeshIndex,0); lpD3DDEV->SetTexture(0, pEnvTexture); for(DWORD i=0;iDrawIndexedPrimitive(D3DPT_TRIANGLELIST, pSubsetTable[i].VertexStart, pSubsetTable[i].VertexCount, pSubsetTable[i].FaceStart * 3, pSubsetTable[i].FaceCount); } } }
外壁と大きく違うのは、頂点シェ-ダーです。
座標変換するだけでなく、法線を用いてテクスチャー座標の計算をします。
; c0-3 -- world + ビュー + 透視変換行列 ; c4-7 -- world 行列 ; c8-11 -- world の逆転置行列 ; c12 -- {0.0, 0.5, 1.0, -1.0} ; c14 -- カメラの位置 ; c13 -- 屈折ファクタ N = c13.w * n ; c15 -- メッシュの色 ; ; v0 頂点の座標値 ; v3 法線ベクトル ; v7 テクスチャ座標0 vs.1.0 ; 位置座標を射影空間にトランスフォームし、レジスタに出力 dp4 oPos.x, v0, c0 dp4 oPos.y, v0, c1 dp4 oPos.z, v0, c2 dp4 oPos.w, v0, c3 ; 頂点位置をワールド空間にトランスフォーム dp4 r0.x, v0, c4 dp4 r0.y, v0, c5 dp4 r0.z, v0, c6 dp4 r0.w, v0, c7 ; 法線ベクトルをワールド空間にトランスフォーム dp3 r1.x, v3, c8 dp3 r1.y, v3, c9 dp3 r1.z, v3, c10 ; ワールド空間の法線ベクトルを正規化 dp3 r1.w, r1, r1 rsq r1.w, r1.w mul r1, r1, r1.w ; r1 はワールド空間での規格化された法線 ; 頂点から視点へのベクトル sub r3, c14, r0 dp3 r2.w, r3, r3 rsq r2.w, r2.w mul r2, r3, r2.w ; r1 規格化された頂点から視点へのベクトルE ; 屈折ファクタをセット mul r4.xyz, r1, c13 ; r4 = N (c13 は 1.0f) ; 2*(E dot N)*N - E を計算し、レジスタに出力 dp3 r3, r2, r4 ; r3 = E dot N add r3, r3, r3 ; r3 = 2 * (E dot N) mad oT0.xyz, r4, r3, -r2 ; oT0.xyz = 2 * (E dot N) * N - E mov oT0.w, c12.z ; oT0.w = 1.0f
出力したテクスチャー座標は、
oT0.xyz = 2 * (E dot N) * N - E
です。キューブマップで指定するテクスチャー座標は、このベクトルを代入します。
このベクトルは、法線Nを持つ平面に関して、視線を反射させた時のベクトルです。
まぁ、よくありがちな反射ベクトルを説明する絵ですが、この説明で理解できるでしょうか?
複雑と思われるかもしれませんが、ここから先、自分で各テクスチャーの各UV値を求めるのは至難の業です。
ここまでサポートしてくれるなら、大いに喜ぼうではありませんか。
さて、屈折です。
下の画像を見てください。
インドネシアが透けています。さらにオーストラリアも引き伸ばされて表示されています。
では、屈折と反射がどこが違うかというと、実は一箇所だけです。
それは、頂点シェーダーの固定レジスタ c13 を変えます。
// 屈折用の設定 lpD3DDEV->SetVertexShaderConstant(13, D3DXVECTOR4(1.0f, 1.0f, 1.0f, 0.0f), 1);// 屈折ファクタ (完全反射) ↓ // 屈折用の設定 lpD3DDEV->SetVertexShaderConstant(13, D3DXVECTOR4(0.5f, 0.5f, 0.5f, 0.0f), 1);// 屈折ファクタ (屈折)
これによる変化は、テクスチャー座標です。
反射ベクトルが、
oT0.xyz = 2 * (E dot c13*N) * c13*N - E = 0.5 * (E dot N) * N - E
の変更を受けます。
このベクトルを図示すると、
のように、反射ではなく、内部にもぐりこむ屈折の作用を引き起こします。
但し、物体から出て行くときの効果は何も考慮されていないので、擬似的なものに過ぎないのですが、
ゲームや、デモとして使われる分には、必要十分な効果でしょう。
今回は、屈折のデモを解説してみました。
屈折のデモをまじめに勉強したのは今回が初めてだったのですが、
小手先のテクニックでずいぶんやれるものだと感心しています。
こんな、一寸のネタで大いに驚かすような物を作りたいですね。