今回は、キューブ環境マップを用いた、反射と屈折です。
知っている人は、『頂点シェーダを使った屈折エフェクト~ 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
の変更を受けます。
このベクトルを図示すると、
のように、反射ではなく、内部にもぐりこむ屈折の作用を引き起こします。
但し、物体から出て行くときの効果は何も考慮されていないので、擬似的なものに過ぎないのですが、
ゲームや、デモとして使われる分には、必要十分な効果でしょう。
今回は、屈折のデモを解説してみました。
屈折のデモをまじめに勉強したのは今回が初めてだったのですが、
小手先のテクニックでずいぶんやれるものだと感心しています。
こんな、一寸のネタで大いに驚かすような物を作りたいですね。