DirectX 9.0 が昨日公開されました。
早速、遊んでみましょう。
下のファイルがソースと実行ファイルです。
まぁ、いつものように適当にファイルが入っています。
ほとんどが APP WIZARD から出力されるファイルです。
以下のファイルの上から3つのファイル以外は無視してください。
hlsl.fx | シェーダの入ったエフェクトファイル |
DirectX9Application1.h | アプリケーションのヘッダ |
DirectX9Application1.cpp | アプリケーションのソース |
d3dapp.h | アプリケーションの基底クラス |
d3dapp.cpp | アプリケーションの基底クラス |
dxutil.h | SAFE_RELEASE とかの便利関数 |
dxutil.cpp | SAFE_RELEASE とかの便利関数 |
d3dutil.h | カメラとかの便利クラス |
d3dutil.cpp | カメラとかの便利クラス |
d3denumeration.h | デバイス管理の補助 |
d3denumeration.cpp | デバイス管理の補助 |
d3dfile.h | メッシュの読み込み |
d3dfile.cpp | メッシュの読み込み |
d3dfont.h | フォントの描画 |
d3dfont.cpp | フォントの描画 |
d3dsettings.h | メニューによる設定 |
d3dsettings.cpp | メニューによる設定 |
diutil.h | 入力管理 |
diutil.cpp | 入力管理 |
dmutil.h | 音楽管理 |
dmutil.cpp | 音楽管理 |
あと、実行ファイル及び、プロジェクトファイルが入っています。
今回から、Microsoft Visual C++ .NET 用のプロジェクトファイルにしました。
今回は、初めてということもあって、AppWizard を使ってみました。
AppWizard は、結構複雑ですが、デバイスの選択などは適切に行ってくれますし、
フォントもサポートされているので、最初に AppWizard による雛形をつくり、それを改造していくのがいいのではないでしょうか。
VC++で、[ファイル][新規作成][プロジェクト]で、新しいプロジェクトを作成しようとすると、プロジェクト名などを選択するダイアログボックスが出てきます。
DirectX9 をインストールすると、Visual C++ プロジェクトに「DirectX9 Visual C++ Wizard」が追加されるので、
それを選択して、後は保存するフォルダの場所やプロジェクト名を指定します。
今回は、ディフォルトのプロジェクト名を使用しました。
では、OKを押して次に進みましょう。
次の画面では、DirectXのどのコンポーネントを使うか選択します。
今回は、何も設定せずに「Finish」を押しました。
必要に応じて DirectInput や DirectMusic の使用をやめたり、ティーポットは使わないような設定を切り替えましょう。
すると、指定したフォルダにばーっとファイルが出来上がります。
あとは、実行すれば、ティーポットが表示されます。
ここまでは、固定機能です。
HLSL は、Cg言語と同じように、C言語ライクなシェーダ言語です。
Cg 言語に慣れている人は、すぐに移行できるのではないでしょうか(そもそも、そんな奴は、ほとんどいないだろうがな)。
HLSL は、エフェクトファイルと呼ばれるテキストファイルにシェーダを書いて、プログラマブルシェーダを実行します。
エフェクトファイルの特徴は、頂点シェーダやピクセルシェーダを統一的に扱えるだけではなく、
テクスチャのバイリニアサンプリングの設定などもできます。
ためしに見てみるのが早いでしょうか。
今回のシェーダは次のプログラムになります。
Cg と似たような構成ですが、technique と呼ばれる部分が追加されています。
hlsl.fx 0007: // ------------------------------------------------------------- 0008: // グローバル変数 0009: // ------------------------------------------------------------- 0010: float4x4 mWVP; // ローカルから射影空間への座標変換 0011: 0012: // ------------------------------------------------------------- 0013: // 頂点シェーダからピクセルシェーダに渡すデータ 0014: // ------------------------------------------------------------- 0015: struct VS_OUTPUT 0016: { 0017: float4 Pos : POSITION; 0018: float4 Col : COLOR0; 0019: }; 0020: 0021: // ------------------------------------------------------------- 0022: // 頂点シェーダプログラム 0023: // ------------------------------------------------------------- 0024: VS_OUTPUT VS( 0025: float4 Pos : POSITION, // モデルの頂点 0026: float3 Normal : NORMAL // モデルの法線 0027: ){ 0028: VS_OUTPUT Out = (VS_OUTPUT)0; // 出力データ 0029: float4 diffuse = { 1.0f, 0.0f, 0.0f, 1.0f}; 0030: float4 amb = { 0.3f, 0.3f, 0.3f, 1.0f}; 0031: float3 dir = {-0.6f, 0.6f,-0.6f}; 0032: 0033: Out.Pos = mul(Pos, mWVP); // 座標変換 0034: Out.Col = diffuse * max( dot(dir, Normal), 0) + amb; // 定数色 0035: 0036: return Out; 0037: } 0038: // ------------------------------------------------------------- 0039: // ピクセルシェーダプログラム 0040: // ------------------------------------------------------------- 0041: float4 PS( 0042: float4 Col : COLOR0 ) : COLOR 0043: { 0044: return Col; 0045: } 0046: 0047: // ------------------------------------------------------------- 0048: // テクニック 0049: // ------------------------------------------------------------- 0050: technique TShader 0051: { 0052: pass P0 0053: { 0054: // シェーダ 0055: VertexShader = compile vs_1_1 VS(); 0056: PixelShader = compile ps_1_1 PS(); 0057: } 0058: }
今回は、頂点シェーダプログラムで、射影変換と、Lambert 拡散光をべたで実装しました。
拡散光がローカル座標なので、オブジェクトに光が張り付いているのはご愛嬌ということで、今回は許してください。
Cg と同じように、頂点シェーダ出力の構造体を定義し、シェーダプログラムの返り値に指定します。
命令はCgと同じように、積計算 mul や、内積 dot や 最大値 max があります。
ピクセルシェーダは入力された色をそのまま出力します。
出力の指定が特殊ですね。
丸暗記で乗り越えましょう。
後は、テクニック宣言で、それぞれのシェーダをコンパイルして使うように指定します。
コンパイルしないアセンブラ命令も使えます。
後は、呼び出し側のアプリケーションプログラムを扱います。
DirectX9 は、DirectX8 とほとんど同じなので、一般的な事柄は省略させてもらいます。
さて、シェーダを使うのに必要なのは、エフェクトファイルを管理するためのオブジェクト(へのポインタ) LPD3DXEFFECT と、
シェーダのグローバルオブジェクトをアプリケーション側から指定するための D3DXHANDLE (今回は、float4x4 の mWVP へ値を代入します)です。
あと、m_hmWVPへ値を入れるために必要な各種行列も持っておきます。
DirectX9Application1.h 0056: class CMyD3DApplication : public CD3DApplication 0057: { 0058: // ★シェーダが書かれたエフェクト 0059: LPD3DXEFFECT m_pEffect; // ★シェーダが書かれたエフェクト 0060: D3DXHANDLE m_hmWVP; // ★ワールド×ビュー×射影行列 0061: 0062: // ★計算で使う行列 0063: D3DXMATRIX m_matWorld; 0064: D3DXMATRIX m_matView; 0065: D3DXMATRIX m_matProj; 以下略
さて、エラーを感知しやすくするための値の初期化ですが、NULLにしておけば大丈夫です。
DirectX9Application1.cpp 0121: CMyD3DApplication::CMyD3DApplication() 0122: { 0123: // ★初期化 0124: m_pEffect = NULL; 0125: m_hmWVP = NULL; 以下略
当然、最後にはメモリを開放します。
DirectX9Application1.cpp 0985: HRESULT CMyD3DApplication::DeleteDeviceObjects() 0986: { 0987: // ★エフェクトの開放 0988: SAFE_RELEASE( m_pEffect ); 0989: 以下略
グローバルオブジェクトのハンドルは開放がいらないみたいですね(ここら辺はまだきちんとは調べていません)。
さて、シェーダの読み込み部分を見ましょう。
D3DXCreateEffectFromFile で、シェーダを読み込みます。シェーダが間違っていればエラーを返します。
あとは、シェーダのグローバル オブジェクトに対応するアプリケーション側のハンドルを調べます。
GetDesc を使ってグローバル変数などの数を調べてから、strcmpi で名前を比較して、エフェクトのパラメータ(m_pEffect->GetParameter)をハンドルにします。
これで、シェーダのグローバル変数にアクセスできるようになります。
DirectX9Application1.cpp 0394: HRESULT CMyD3DApplication::InitDeviceObjects() 0395: { 0396: // TODO: create device objects 0397: 0398: HRESULT hr; 0399: 0400: // Init the font 0401: m_pFont->InitDeviceObjects( m_pd3dDevice ); 0402: 0403: // Create a teapot mesh using D3DX 0404: if( FAILED( hr = D3DXCreateTeapot( m_pd3dDevice, &m_pD3DXMesh, NULL ) ) ) 0405: return DXTRACE_ERR( "D3DXCreateTeapot", hr ); 0406: 0407: // ★シェーダの読み込み 0408: if( FAILED( hr = D3DXCreateEffectFromFile( m_pd3dDevice, "hlsl.fx", NULL, NULL, 0409: 0, NULL, &m_pEffect, NULL ) ) ) 0410: { 0411: return hr; 0412: } 0413: D3DXEFFECT_DESC m_EffectDesc; 0414: D3DXPARAMETER_DESC ParamDesc; 0415: D3DXHANDLE hParam; 0416: m_pEffect->GetDesc( &m_EffectDesc ); 0417: for( UINT iParam = 0; iParam < m_EffectDesc.Parameters; iParam++ ) 0418: { 0419: hParam = m_pEffect->GetParameter ( NULL, iParam ); 0420: m_pEffect->GetParameterDesc( hParam, &ParamDesc ); 0421: if( ParamDesc.Name != NULL && 0422: ( ParamDesc.Class == D3DXPC_MATRIX_ROWS || ParamDesc.Class == D3DXPC_MATRIX_COLUMNS ) ) 0423: { 0424: if( strcmpi( ParamDesc.Name, "mWVP" ) == 0 ) 0425: m_hmWVP = hParam; 0426: } 0427: } 0428: 0429: return S_OK; 0430: }
あと、HLSL用に直した部分としては、固定機能の行列を設定する必要は無いので、その部分をコメントアウトしたり、
ライトの設定は使わないので、その部分をコメントアウトします。
そういえば、テクスチャを使わないので、SetSamplerState 系の命令もいりませんね。
ただ、サンプルを見ると、OnResetDevice()が、追加されているようなので、その部分を忘れずに追加します。
DirectX9Application1.cpp 0444: HRESULT CMyD3DApplication::RestoreDeviceObjects() 0445: { 0446: // TODO: setup render states 0447: HRESULT hr; 0448: 0449: // Setup a material 0450: D3DMATERIAL9 mtrl; 0451: D3DUtil_InitMaterial( mtrl, 1.0f, 0.0f, 0.0f ); 0452: m_pd3dDevice->SetMaterial( &mtrl ); 0453: 0454: // Set up the textures 0455: m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); 0456: m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); 0457: m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); 0458: m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); 0459: m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); 0460: m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); 0461: m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); 0462: m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); 0463: 0464: // Set miscellaneous render states 0465: m_pd3dDevice->SetRenderState( D3DRS_DITHERENABLE, FALSE ); 0466: m_pd3dDevice->SetRenderState( D3DRS_SPECULARENABLE, FALSE ); 0467: m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE ); 0468: // m_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x000F0F0F ); // ★いらない 0469: m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE ); // ★ 光源計算なし 0470: 0471: // ワールド行列を設定する 0472: D3DXMATRIX matIdentity; 0473: D3DXMatrixIdentity( &matIdentity ); 0474: // m_pd3dDevice->SetTransform( D3DTS_WORLD, &matIdentity ); // ★いらない 0475: 0476: // Set up our view matrix. A view matrix can be defined given an eye point, 0477: // a point to lookat, and a direction for which way is up. Here, we set the 0478: // eye five units back along the z-axis and up three units, look at the 0479: // origin, and define "up" to be in the y-direction. 0480: D3DXMATRIX matView; 0481: D3DXVECTOR3 vFromPt = D3DXVECTOR3( 0.0f, 0.0f, -5.0f ); 0482: D3DXVECTOR3 vLookatPt = D3DXVECTOR3( 0.0f, 0.0f, 0.0f ); 0483: D3DXVECTOR3 vUpVec = D3DXVECTOR3( 0.0f, 1.0f, 0.0f ); 0484: // D3DXMatrixLookAtLH( &matView, &vFromPt, &vLookatPt, &vUpVec ); 0485: D3DXMatrixLookAtLH( &m_matView, &vFromPt, &vLookatPt, &vUpVec ); 0486: // m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); // ★いらない 0487: 0488: // Set the projection matrix 0489: D3DXMATRIX matProj; 0490: FLOAT fAspect = ((FLOAT)m_d3dsdBackBuffer.Width) / m_d3dsdBackBuffer.Height; 0491: D3DXMatrixPerspectiveFovLH( &m_matProj, D3DX_PI/4, fAspect, 1.0f, 100.0f ); 0492: // D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, fAspect, 1.0f, 100.0f ); 0493: // m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj ); // ★いらない 0494: 0495: #if 0 // ★いらない 0496: // Set up lighting states 0497: D3DLIGHT9 light; 0498: D3DUtil_InitLight( light, D3DLIGHT_DIRECTIONAL, -1.0f, -1.0f, 2.0f ); 0499: m_pd3dDevice->SetLight( 0, &light ); 0500: m_pd3dDevice->LightEnable( 0, TRUE ); 0501: m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE ); 0502: #endif 0503: 0504: // Restore the font 0505: m_pFont->RestoreDeviceObjects(); 0506: 0507: if( !m_bWindowed ) 0508: { 0509: // Create a surface for configuring DInput devices 0510: if( FAILED( hr = m_pd3dDevice->CreateOffscreenPlainSurface( 640, 480, 0511: m_d3dsdBackBuffer.Format, D3DPOOL_DEFAULT, 0512: &m_pDIConfigSurface, NULL ) ) ) 0513: return DXTRACE_ERR( "CreateOffscreenPlainSurface", hr ); 0514: } 0515: 0516: // ★エフェクトのリセット 0517: if( m_pEffect != NULL ) m_pEffect->OnResetDevice(); 0518: 0519: return S_OK; 0520: }
いよいよ描画です。
SetTechnique でシェーダを指定して、m_pEffect->Begin(), m_pEffect->End() で、シェーダを使って描画します。
エフェクトファイルはマルチパスの指定が可能になっていて、その回数分まわすようにしておくと、一般的な記述にできます。
シェーダのグローバル変数である変換行列は、m_pEffect->SetMatrix()で指定します。
Cg と比べて汎用性は下がっていますが、こちらの方が使いやすいですかねぇ。
DirectX9Application1.cpp 0788: HRESULT CMyD3DApplication::Render() 0789: { 0790: // Clear the viewport 0791: m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0792: 0x000000ff, 1.0f, 0L ); 0793: 0794: // Begin the scene 0795: if( SUCCEEDED( m_pd3dDevice->BeginScene() ) ) 0796: { 0797: #if 1 0798: UINT nPasses; 0799: UINT iPass; 0800: 0801: if( m_pEffect != NULL ) 0802: { 0803: // ★シェーダの設定 0804: D3DXHANDLE hTechnique = m_pEffect->GetTechniqueByName( "TShader" ); 0805: m_pEffect->SetTechnique( hTechnique ); 0806: 0807: // ★行列の設定 0808: D3DXMATRIX m = m_matWorld*m_matView*m_matProj; 0809: if( m_hmWVP != NULL ) 0810: m_pEffect->SetMatrix( m_hmWVP, &m ); 0811: 0812: // ★描画 0813: m_pEffect->Begin( &nPasses, 0 ); 0814: for( iPass = 0; iPass < nPasses; iPass ++ ) 0815: { 0816: m_pEffect->Pass( iPass ); 0817: // ティーポットのレンダリング 0818: m_pD3DXMesh->DrawSubset( 0 ); 0819: } 0820: m_pEffect->End(); 0821: } 0822: #else 0823: // Render the teapot mesh 0824: m_pD3DXMesh->DrawSubset(0); 0825: #endif 0826: 0830: // End the scene. 0831: m_pd3dDevice->EndScene(); 0832: } 0833: 0834: return S_OK; 0835: }
あと、サンプルプログラムをみていたら、設定が変わったときの処理も必要らしいので、忘れずに追加しましょう。
DirectX9Application1.cpp 0964: HRESULT CMyD3DApplication::InvalidateDeviceObjects() 0965: { 0966: // TODO: Cleanup any objects created in RestoreDeviceObjects() 0967: m_pFont->InvalidateDeviceObjects(); 0968: SAFE_RELEASE( m_pDIConfigSurface ); 0969: 0970: // ★ビデオメモリリソースへの参照を解放する。 0971: if( m_pEffect != NULL ) m_pEffect->OnLostDevice(); 以下略
以上で、HLSLが使えます。
あと、テクスチャやメッシュの使いかたを勉強しなくてはなりませんが、ぼちぼちやっていきましょう。
DirectX9 ご祝儀ということで、目玉機能の一つである HLSL を扱いました。
まぁ、言語なんてものは些細なもので、その中で何をするかが問題なので、使い方を覚えて楽しく遊びましょう。