HLSL


~ High Level Shader Language ~






■はじめに

DirectX 9.0 が昨日公開されました。
早速、遊んでみましょう。

下のファイルがソースと実行ファイルです。

まぁ、いつものように適当にファイルが入っています。
ほとんどが APP WIZARD から出力されるファイルです。
以下のファイルの上から3つのファイル以外は無視してください。

hlsl.fxシェーダの入ったエフェクトファイル
DirectX9Application1.hアプリケーションのヘッダ
DirectX9Application1.cppアプリケーションのソース
d3dapp.hアプリケーションの基底クラス
d3dapp.cppアプリケーションの基底クラス
dxutil.hSAFE_RELEASE とかの便利関数
dxutil.cppSAFE_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 は、結構複雑ですが、デバイスの選択などは適切に行ってくれますし、 フォントもサポートされているので、最初に AppWizard による雛形をつくり、それを改造していくのがいいのではないでしょうか。

VC++で、[ファイル][新規作成][プロジェクト]で、新しいプロジェクトを作成しようとすると、プロジェクト名などを選択するダイアログボックスが出てきます。 DirectX9 をインストールすると、Visual C++ プロジェクトに「DirectX9 Visual C++ Wizard」が追加されるので、 それを選択して、後は保存するフォルダの場所やプロジェクト名を指定します。
今回は、ディフォルトのプロジェクト名を使用しました。
では、OKを押して次に進みましょう。

次の画面では、DirectXのどのコンポーネントを使うか選択します。
今回は、何も設定せずに「Finish」を押しました。
必要に応じて DirectInput や DirectMusic の使用をやめたり、ティーポットは使わないような設定を切り替えましょう。

すると、指定したフォルダにばーっとファイルが出来上がります。
あとは、実行すれば、ティーポットが表示されます。

ここまでは、固定機能です。

■HLSL シェーダ

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





もどる

imagire@gmail.com