Pixel Shader を既に取り扱いましたが、より、幅を広げるために、
ここで、バーテックスシェーダーをやっておこうと思います。
バーテックスシェーダーは、自前で座標計算と、ライティングをするための仕組みです。
Pixel Shader では、形の変形は出来ませんが、バーテックスシェーダーはポリゴンそのものをゆがませることができます。
まぁ、最終的には Displacement Mapping すなわち Per Vertex( & Pixel) Shader に統合されるでしょうが、
数世代先の話をしてもしょうがないので、今できることをします。
次のソースを、ダウンロードしてください。
今回は、テクスチャーを張った3次元ポリゴンを表示します。
既に取り扱った例題ですが、その分バーテックスシェーダーの使い方が分かると思います。
例によって、draw.cpp を見ていきます。
今回追加したオブジェクトは、hVertexShader です。
いわゆるハンドルと言う奴で、バーテックスシェーダーを識別するのに Windows の内部で使われます。
LPDIRECT3DVERTEXBUFFER8 pVB = NULL; // 頂点バッファ LPDIRECT3DTEXTURE8 pTexture = NULL; // テクスチャー DWORD hVertexShader = ~0; // シェーダープログラムの情報
hVertexShader は ~0 (=0xffffffff) で初期化します。 他のサンプルなどを見ると、どうやら、これが無効なハンドルの値らしいです。 このまま使いましょう。
今回は、頂点バッファや、テクスチャーを設定した後に、バーテックスシェーダーの初期化が入ります。
HRESULT InitRender(LPDIRECT3DDEVICE8 lpD3DDEV) { CUSTOMVERTEX vertices[] = { // x, y, z, tu tv {-16.0f, 12.0f, 0.0f, 0.0f,0.0f,}, { 16.0f, 12.0f, 0.0f, 1.0f,0.0f,}, {-16.0f,-12.0f, 0.0f, 0.0f,1.0f,}, { 16.0f,-12.0f, 0.0f, 1.0f,1.0f,}, }; HRESULT hr; hr = lpD3DDEV->CreateVertexBuffer( 4*sizeof(CUSTOMVERTEX),0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &pVB); if(FAILED(hr)) return E_FAIL; VOID* pVertices; hr = pVB->Lock( 0, sizeof(vertices), (BYTE**)&pVertices, 0); if(FAILED(hr)) return E_FAIL; memcpy( pVertices, vertices, sizeof(vertices) ); pVB->Unlock(); D3DXCreateTextureFromFileEx(lpD3DDEV, "sakura.bmp",0,0,0,0,D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, 0, NULL, NULL, &pTexture); // バーテックスシェーダーを作成する DWORD dwDecl[] = { D3DVSD_STREAM(0), D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ), //D3DVSDE_POSITION, 0 D3DVSD_REG(D3DVSDE_TEXCOORD0,D3DVSDT_FLOAT2 ), //D3DVSDE_TEXCOORD0, 7 D3DVSD_END() }; ID3DXBuffer* pshader; hr = D3DXAssembleShader( VertexShader , sizeof(VertexShader) - 1, 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->SetRenderState( D3DRS_LIGHTING, FALSE ); // 頂点の色を使うので、ライトは使わない lpD3DDEV->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); // 両面表示 return S_OK; }
D3DXAssembleShader から後が、バーテックスシェーダーの部分です。
D3DXAssembleShader でプログラムを読み込みます。読んだ情報が pshader に入ります。
今回は、ソース中の文字列である、const char VertexShader[] を使いました。
プログラムが、何をやるかは、次に説明します。
ここでは、D3DXAssembleShader がバーテックスシェーダーのプログラムを読み込むところだと覚えてください。
読み込んだら、CreateVertexShader でプログラムからシェーダーを生成します。 できたシェーダーは、CreateVertexShader で設定すれば、使えるようになります。 ウィンドウズから割り当てられたメモリが、ハンドルとして hVertexShader に渡されます。 後で設定するときに、このハンドルを使います。
CreateVertexShader で、dwDecl という、DWORD 配列を使っています。
これは、ポリゴンのフォーマットをバーテックスシェーダーのプログラムに教えます。
バーテックスシェーダーは、今回使っている頂点フォーマット
struct CUSTOMVERTEX { FLOAT x, y, z; // 位置 FLOAT tu, tv; // テクスチャーの画像の位置 };
を、構造体としての CUSTOMVERTEX や、
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)
の形で、理解することが出来ません。はっきりいって、おバカさんです。
われわれが調教しなくてはいけなせん。
それが、dwDecl です。
DWORD dwDecl[] = { D3DVSD_STREAM(0), D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ), //D3DVSDE_POSITION, 0 D3DVSD_REG(D3DVSDE_TEXCOORD0,D3DVSDT_FLOAT2 ), //D3DVSDE_TEXCOORD0, 7 D3DVSD_END() };
dwDecl は、D3DVSD_STREAM(0) で始まり、D3DVSD_END() で終わります。
まぁ、いつも使う時は、コピー&ペーストしましょう。
その間が、頂点フォーマットの指定です。
CUSTOMVERTEX の中身の順番通りに指定します。
最初は、座標の x, y, z です。float 型 3 つを、D3DVSDT_FLOAT3 として指定します。
こいつらは、座標なので、D3DVSDE_POSITION で指定します。
あとはテクスチャー座標です。tu, tv のfloat 型 2 つなので、D3DVSDT_FLOAT2 で指定します。
1つ目の(1つしかないですが、)テクスチャー座標なので、D3DVSDE_TEXCOORD0 に設定します。
以上です。
ここで、D3DVSDE_POSITION及び、D3DVSDE_TEXCOORD0 を使いましたが、
これは、DirectX が勝手に定義している定義名です。直接数字で、D3DVSD_REG(3, D3DVSDT_FLOAT3 ) と
しても構いません。
その場合は、バーテックスシェーダープログラムでは、v3 に座標が入るので、v3 をポリゴンの頂点座標として扱えばバッチリです。
まぁ、読みやすさの点から言っても、定義名を使っていくほうがいい事が多いとは思います。
と言うわけで、VertexShader[] や、dwDecl を使って、バーテックスシェーダーを作成したので、この後の描画で使いましょう。
ここで、シェーダープログラムを見ましょう。
プログラムコードは、次のようになっています。
文字列がそのままプログラムになっています。
" や \n がないと考えて、プログラムを考えてください。
const char VertexShader[] = "vs.1.0 // バージョン情報 \n"\ "dp4 oPos.x , v0 , c4 // x成分の透視変換 \n"\ "dp4 oPos.y , v0 , c5 // y成分の透視変換 \n"\ "dp4 oPos.z , v0 , c6 // z成分の透視変換 \n"\ "dp4 oPos.w , v0 , c7 // w成分の透視変換 \n"\ "mov oT0.xy , v7 // テクスチャーのUV値をコピー\n";
1 行目は バーテックスシェーダーのバージョン設定です。
2 行目から、3行目で、透視変換をします。
v0 にポリゴンの座標が入るように D3DVSD_REG で、D3DVSDE_POSITION(=0) に設定しました。
oPos が透視変換した後に出力する場所です。あとで、c4~c7 に変換行列を入れます。
行列計算として記すと、次のようになります。
┌c4.x c5.x c6.x c7.x ┐ [oPos.x oPos.y oPos.z oPos.w] = [v0.x v0.y v0.z v0.w]|c4.y c5.y c6.y c7.y │ │c4.z c5.z c6.z c7.z │ └c4.w c5.w c6.w c7.w ┘
あとは、テクスチャーで、mov 命令で、v7 の xy 成分だけを、そのままコピーします。
v7 は初期化の時点で、テクスチャーのuv値が入るように、(D3DVSDE_TEXCOORD0(=7))として設定しました。
以上が、バーテックスシェーダーの計算です。後々、くわしくやっていきましょう。
いよいよ、表示の部分です。
今回は、3次元ポリゴンを表示した時のように、mWorld, mView, mProj を求めますが、
その後、SetTransform はしません。
全てを掛けて、1つの行列にした後、SetVertexShaderConstant で、バーテックスシェーダーの定数レジスタにほおりこみます。
実は、通常のポリゴンの計算も、内部で、mWorld, mView, mProj を同じように掛けて、一つにまとめて計算します。
なぜ、SetTransform で3つを別々にするかと言えば、人間にわかりやすいからです
(他にも、カメラを複数持つのに便利だとか、スポットライトの計算に便利だとかありますが、本質的に1つの行列で十分です)。
そんなこんなで、SetVertexShaderConstant で、変換に使う行列を設定した後、
SetVertexShader で、バーテックスシェーダープログラムを透視変換に使うように設定します。
あとは、普通に表示すれば、バーテックスシェーダーを使った結果が得られます。
void Render(LPDIRECT3DDEVICE8 lpD3DDEV) { D3DXMATRIX mWorld, mRotX, mRotY, mTrans; D3DXMatrixTranslation(&mTrans, 0,0,0); float time = (float)timeGetTime(); D3DXMatrixRotationY(&mRotY, time/600.0f); D3DXMatrixRotationX(&mRotX, 0.0f); mWorld = mRotX * mRotY * mTrans; // ワールド、ビュー座標系の変換 D3DXMATRIX mView, mProj; D3DXMatrixLookAtLH(&mView ,&D3DXVECTOR3(0,0.0f,-50.0f) // カメラ位置 ,&D3DXVECTOR3(0,0,0) // カメラの注目点 ,&D3DXVECTOR3(0,1,0) // 上の向き ); // ビュー、スクリーン座標系の変換 D3DXMatrixPerspectiveFovLH(&mProj ,60.0f*D3DX_PI/180.0f // 視野角 ,(float)WIDTH/(float)HEIGHT // アスペクト比 ,0.01f // 最近接距離 ,100.0f // 最遠方距離 ); D3DXMATRIX m = mWorld * mView * mProj; D3DXMATRIX trans; D3DXMatrixTranspose( &trans , &m); lpD3DDEV->SetVertexShaderConstant( 4 , &trans , 4 ); // バーテックスシェーダーの c4 ~ c7 レジスタの設定 lpD3DDEV->SetVertexShader( hVertexShader ); lpD3DDEV->SetTexture( 0, pTexture); lpD3DDEV->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); lpD3DDEV->SetStreamSource( 0, pVB, sizeof(CUSTOMVERTEX) ); lpD3DDEV->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2); }
後片付けに、hVertexShader で使ったメモリの開放を追加します。
void CleanRender(LPDIRECT3DDEVICE8 lpD3DDEV) { if ( hVertexShader != ~0 ){ lpD3DDEV->DeleteVertexShader( hVertexShader ); hVertexShader = ~0; } RELEASE(pTexture); RELEASE(pVB); }
あとは、いつもと変わりありません。
さぁ、バーテックスシェーダーが使えるようになりました。 今後、(Pixel Shader との複合技を含めて)バリエーションを増やしていきましょう 。