VertexDeclarationとFVF

私がよく見ている掲示板で気になる書き込みがありました。 プログラマ独自のシェーダを使うときFVFは使えないので VertexDeclaration で頂点宣言を行わないといけない、といったものです。 さすがにそれはないだろう・・・といった声もあるかと思われますが、シェーダプログラムがスキップされる等といった反論がありましたので実証してみました。

ここ数週間プログラマブルシェーダばかり扱っていたため、ほとんど VertexDeclaration を使用していたのでFVFの宣言方法を確認しておきます。 MSDNにある情報を元に定義しました。

頂点宣言・頂点バッファの生成

まず、VertexDeclaration では頂点の設定を以下のように行います。

  • 頂点要素、使用法を決定
  • 頂点フォーマットとして宣言
D3DVERTEXELEMENT9 vertexElements[] = {  // 頂点要素の決定
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_POSITION, 0},  // 頂点位置座標として使用する
D3DDECL_END()  // 頂点要素終了
};
// 頂点フォーマットを宣言
LPDIRECT3DVERTEXDECLARATION9 pVertexDecl;
pD3DDevice->CreateVertexDeclaration( vertexElements, &pVertexDecl );

続いてこれと同様の頂点宣言をFVFで行います。 FVFは VertexDeclarationの ラッパー (だと思っている) なので、さらに簡単に宣言されます。

pD3DDevice->SetFVF( D3DFVF_XYZ );  //
float3の要素をもつ頂点フォーマットを宣言

これで頂点宣言は終了しました。続いてこれに基づく頂点バッファを生成します。

// VertexDeclarationでは以下のように頂点バッファを生成します
pD3DDevice->CreateVertexBuffer( 3*sizeof(MY_VERTEX_POS),  //
作成する頂点バッファサイズ
D3DUSAGE_WRITEONLY,       // 使用法を定義
0,                        // FVFを使用しない
D3DPOOPL_MANAGED,         // リソースの管理方法
&pVB_POS,                 // 生成される頂点バッファ
NULL )
// FVFでは以下のように頂点バッファを生成します
pD3DDevice->CreateVertexBuffer( 3*sizeof(MY_VERTEX_POS),  //
作成する頂点バッファサイズ
D3DUSAGE_WRITEONLY,       // 使用法を定義
D3DFVF_XYZ,               // float3要素を持つFVFを指定
D3DPOOPL_MANAGED,         // リソースの管理方法
&pVB_POS,                 // 生成される頂点バッファ
NULL )

これでバッファ生成は終了しました。次の描画処理は共通です。

描画処理

//頂点バッファの中身を埋める
MY_VERTEX_POS* v0;
pVB_POS->Lock( 0, 0, (void**)&v0, 0 );
// 各頂点の位置
v0[0].p = D3DXVECTOR3(-1.0f,  1.0f, 0.0f );
v0[1].p = D3DXVECTOR3( 1.0f, -1.0f, 0.0f );
v0[2].p = D3DXVECTOR3(-1.0f, -1.0f, 0.0f );
pVB_POS->Unlock();
// ビューポートの取得
D3DVIEWPORT9    vp;
if(FAILED(pD3DDevice->GetViewport(&vp))) {
return E_FAIL;
}
// アスペクト比の計算
float aspect;
aspect = (float)vp.Width / (float)vp.Height;
// プロジェクション行列の初期化
D3DXMatrixIdentity(&m_proj);
D3DXMatrixPerspectiveFovLH(&m_proj, D3DXToRadian(45.0f), aspect,
1.0f, 1000.0f);
// ビューイング行列の初期化
D3DXMatrixIdentity(&m_view);
D3DXMatrixLookAtLH(&m_view,
&D3DXVECTOR3( 0.0f, 0.0f, -6.0f),
&D3DXVECTOR3( 0.0f, 0.0f,  0.0f),
&D3DXVECTOR3( 0.0f, 1.0f,  0.0f));
// エフェクトの読み込み
LPD3DXBUFFER    errors = 0;
D3DXCreateEffectFromFile(pD3DDevice, TEXT("copy.fx"), 0, 0, D3DXSHADER_DEBUG, 0,
&m_pEffect, &errors);
if(errors){
return E_FAIL;
}
// テクニックのハンドルの取得
m_hTech     = m_pEffect->GetTechniqueByName("BasicTech");
// シェーダープログラムのグローバル変数のハンドルの取得
m_hWvp      = m_pEffect->GetParameterByName(0, "g_wvp");    // world * view *
proj
m_hColor0   = m_pEffect->GetParameterByName(0, "g_color");

この後の描画設定で頂点ストリームの設定を行います VertexDeclarationで定義した場合には使用する頂点宣言を設定します。FVFでは必要ありません。

// VertexDeclで定義した場合下をコメントを外す
// pD3DDevice->SetVertexDeclaration(pVertexDecl);   
pD3DDevice->SetStreamSource( 0, m_pVB_POS, 0, sizeof(MY_VERTEX_POS) );
// 色を設定→白
D3DXVECTOR4 tmpColor;
tmpColor.x = 1.0f;
tmpColor.y = 1.0f;
tmpColor.z = 1.0f;
tmpColor.w = 1.0f;
// テクニックの設定
m_pEffect->SetTechnique(m_hTech);
// シェーダーのグローバル変数の値の設定
m_pEffect->SetMatrix(m_hWvp, &(m_view*m_proj));
m_pEffect->SetVector(m_hColor0, &tmpColor);
m_pEffect->CommitChanges();
// テクニックの実行
m_pEffect->Begin(0, 0);
m_pEffect->BeginPass(0);
// 三角形の描画処理
pD3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1);
m_pEffect->EndPass();
m_pEffect->End();

結果

結果は以下の画像のとおりです。 今回使用したシェーダは入力された色情報 (r, g, b) それぞれに 0.5 を掛けて出力するだけのものです。 VertexDeclaration でもFVFでも同様にシェーダが適用されています。

また今回使用したソースコード全文はこちらです。
ファイルをダウンロード

担当: 松浦 (自分が何を実装しているか知らないほど怖いことはない)