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 を扱いました。
まぁ、言語なんてものは些細なもので、その中で何をするかが問題なので、使い方を覚えて楽しく遊びましょう。