Direct3Dの頂点シェーダでは、頂点の位置、色、テクスチャ座標などを決定できます。 頂点シェーダを使うことでGPUでの並列計算が可能になり、処理時間を短縮できます。
板がくるくる回るだけのプログラムですが、
頂点シェーダで頂点の座標変換をしています。
・頂点の座標変換
・頂点色の編集(照明の計算など)
・テクスチャ座標の編集(テクスチャスクロールなど)
頂点シェーダを手軽に実装できる方法として、今回はアセンブルのテキスト読み込みによる実装例を紹介しています。 しかし複雑なプログラミングをする場合は上位レベルシェーダ言語(HLSL)を使ったほうが扱いやすいようです。
頂点シェーダの実装を次の手順で紹介していきます。
・頂点シェーダプログラムの準備
・Direct3Dが頂点シェーダを使えるかチェックする
・頂点シェーダが処理する頂点データを定義
・頂点シェーダオブジェクトの作成
・頂点シェーダの適用
・頂点シェーダに渡すデータをセット
・描画する
頂点シェーダのプログラムを記述したファイルを用意します。
ファイルはテキスト形式にします。拡張子は何でもいいです。
このプログラム例では、外部から渡されてきた座標変換行列を使って頂点を座標変換しています。
そして頂点色とテクスチャ座標は、元々の頂点データの内容をそのまま転送しています。
(転送は一見無駄な操作ですが記述しないと適用されません)
vs_1_1 ;頂点シェーダのバージョン
dcl_position v0 ;v0を座標頂点として定義したことを宣言
dcl_color v1 ;v1を色として定義したことを宣言
dcl_texcoord v2 ;v2をテクスチャ座標として定義したことを宣言
;c0にはセットしておいた座標変換行列が渡ってくる
m4x4 oPos, v0, c0 ;v0をc0で座標変換をしてoPosに書き込む
mov oD0, v1 ;頂点色はそのまま使う(oD0に書き込まないと適用されない)
mov oT0, v2 ;テクスチャ座標もそのまま使う
古かったりゲーム用途でない安価なグラフィックボードでは頂点シェーダを使うことができません。また頂点シェーダにはバージョンがあり使える機能も異なりますので、頂点シェーダが使えるか事前にチェックします。
LPDIRECT3DDEVICE9 device;
D3DCAPS9 caps;
device->GetDeviceCaps(&caps);
//頂点シェーダのバージョンを調べる
if ( caps.VertexShaderVersion < D3DVS_VERSION(1,1) ) {
::MessageBox(NULL,_T("頂点シェーダに対応していません。"),_T(""),MB_OK);
}
Direct3Dで使う頂点はプログラマーがある程度自由に定義できます。そのため定義した頂点の内容は頂点シェーダにも知らせなければいけません。
Direct3Dで使う頂点データの例
struct CUSTOMVERTEX
{
D3DVECTOR pos; //頂点の位置
DWORD color; //頂点の色
float tu; //テクスチャ座標
float tv;
};
CUSTOMVERTEXで定義した頂点の内容に従って頂点シェーダにその構造を知らせます。
頂点シェーダが処理する頂点データの構造を定義
LPDIRECT3DDEVICE9 device;
HRESULT hr;
LPDIRECT3DVERTEXDECLARATION9 pVertexDeclaration; //頂点シェーダの頂点定義
//パイプラインに渡す頂点データの構造を定義
D3DVERTEXELEMENT9 decl[] = {
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, //位置
{ 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, //色
{ 0, 16, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, //テクスチャ座標
D3DDECL_END() //最後に必ずD3DDECL_END()をつける
};
//頂点の定義オブジェクトを作成する
hr = device->CreateVertexDeclaration( decl, &pVertexDeclaration );
if ( FAILED(hr) ) {
throw _T("error");
}
//頂点定義をセット
device->SetVertexDeclaration( pVertexDeclaration );
特にD3DVERTEXELEMENT9の二番目の値に注意して下さい。 0,12,16と入っているこの値は、構造体(CUSTOMVERTEX)の先頭からのオフセット(先頭から何バイトずれているか)を差しています。
シェーダプログラムファイルを読み込んでDirect3Dの頂点シェーダオブジェクトを作成します。
LPDIRECT3DDEVICE9 device;
HRESULT hr;
LPCWSTR path = _T("");//任意のパス
LPDIRECT3DVERTEXSHADER9 pVertexShader; //頂点シェーダオブジェクト
//ソースの読み込み
LPD3DXBUFFER pShaderSource;
LPD3DXBUFFER pErrMessage = NULL;
hr = D3DXAssembleShaderFromFile( path, NULL, NULL, 0, &pShaderSource, &pErrMessage );
if ( FAILED(hr) ) {
//エラーメッセージの文字コードはANSI
::MessageBoxA(NULL,( LPCSTR )( pErrMessage->GetBufferPointer()),"",MB_OK);
pErrMessage->Release();
throw _T("error");
}
//頂点シェーダ作成
hr = device->CreateVertexShader( ( DWORD* )pShaderSource->GetBufferPointer(), &pVertexShader );
if ( FAILED(hr) ) {
throw _T("error");
}
//頂点シェーダを作ったらソースは解放して良い
pShaderSource->Release();
頂点シェーダオブジェクトを作成したら、IDIRECT3DDEVICE9::SetVertexlShader()で有効になります。
また、SetVertexlShaderの引数をNULLにすることで頂点シェーダを解除できます。
LPDIRECT3DDEVICE9 device;
LPDIRECT3DPIXELSHADER9 pVertexShader;
device->SetVertexShader( pVertexShader );
今回の例では頂点シェーダへ座標変換行列を渡しています。
頂点シェーダを使う場合、Direct3Dがこれまで勝手にやってくれたビューやプロジェクション行列の合成を自分でしなければいけません。
さらに、できあがった行列は頂点シェーダの仕様上そのままでは使えませんので、転置行列に変換します。
LPDIRECT3DDEVICE9 device;
//移動、回転、ビュー、プロジェクションすべてを合成した行列を作る
D3DXMATRIX mat;
D3DXMATRIX matRotete,matMove,matView,matProjection;//各行列はあらかじめ作成しておきます
mat = matMove; //回転
mat *= matRotete; //移動
mat *= matView; //ビュー
mat *= matProjection; //プロジェクション
//転置行列に変換
D3DXMatrixTranspose( &mat, &mat );
//行列を頂点シェーダに渡す
device->SetVertexShaderConstantF( 0, ( float* )&mat, 4 );
頂点シェーダオブジェクトとデータのセットができたら完成です。
あとは普通にポリゴンを描画するだけで頂点シェーダが適用されます。