環境/屈折マッピング


~映り込みってやつです~




■はじめに

今回は、キューブ環境マップを用いた、反射と屈折です。
知っている人は、『頂点シェーダを使った屈折エフェクト~ 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 上でソースを見せない奴は、真にソースを公開していない』というのを見てカチンと来たため、 わざわざリンクを張ったというのは内緒です)。

■キューブマップ (earth.dds) の作り方

キューブマップとは、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

の変更を受けます。
このベクトルを図示すると、

のように、反射ではなく、内部にもぐりこむ屈折の作用を引き起こします。
但し、物体から出て行くときの効果は何も考慮されていないので、擬似的なものに過ぎないのですが、 ゲームや、デモとして使われる分には、必要十分な効果でしょう。

■最後に

今回は、屈折のデモを解説してみました。
屈折のデモをまじめに勉強したのは今回が初めてだったのですが、 小手先のテクニックでずいぶんやれるものだと感心しています。
こんな、一寸のネタで大いに驚かすような物を作りたいですね。




もどる

imagire@gmail.com