頂点シェーダの使い方 : Part 1

Philip Taylor
Microsoft Corporation

February 27, 2001

この記事のソースコードをダウンロードする。
Base.exe および Basevs.exe

「Driving DirectX」 にようこそ。先月は、プログラマブルな頂点シェーダとピクセル シェーダの概要について説明しました。今月は、頂点シェーダについてさらに詳しく解説します。頂点シェーダを使用すると、開発者は頂点トランスフォームとライティング パイプラインを細かく制御できます。頂点シェーダは、固定機能トランスフォームとライティング パイプラインの代替手段として DirectX® 8 で使用できます。まず、新しいサンプルのフレームワークを調べ、これを頂点シェーダのサンプルと頂点シェーダを開発するためのベースとします。

DirectX 8 Graphics のサンプル フレームワーク

DirectX 8 Direct3D SDK Graphics のサンプル フレームワークは、DirectX 7 Graphics のサンプル フレームワーク D3DFrame (「Driving DirectX」 の 5 月号で説明済み) から発展したもので、細部を更新できるように追加のカバレッジが少し確保されています。

まず、SDK のレイアウトが変更されました。SDK の既定のサンプルは、\Mssdk\Samples\Multimedia にインストールされます。図 1 は新しい SDK のレイアウトです。

directx02192001-f01.gif

1. DirectX 8 SDK サンプル   フォルダのレイアウト

目的のフォルダは、Common と Direct3D です。サンプル フレームワークは、Common フォルダにあり、Graphics フレームワークをベースにした Direct3D サンプルは、Direct3D フォルダにあります。Graphics フレームワークを構成するヘッダ ファイルとソース ファイルは、Common フォルダ内の Include フォルダと Src フォルダにそれぞれあります。このヘッダ ファイルとソース ファイルは、DirectX のほかのコンポーネントを表示するサンプルもサポートしていますが、このコラムの範囲外なので、ここでは取り上げません。

サンプルのある場所が確認できたので、その内容について説明します。サンプルの機能ではなく、その動作の様子を理解するには、サンプル フレームワークの基本的な仕組みを理解する必要があります。これから、Graphics フレームワークのサンプルについて高度な説明をします。これは、独自のサンプルの作成に必要なフレームワークを理解し、使用する場合に欠かせません。DirectX 8 Graphics フレームワークは、図 1 の Multimedia フォルダ内の Common\src フォルダにあるソース モジュールから構成されています。Direct3D サンプル フレームワークが入っているファイルは、次のとおりです。

  • d3dapp.cpp - アプリケーション クラスである CD3DApplication を実装します。D3D サンプルの基底クラスとして使用します。

  • d3dfile.cpp - サンプルで X ファイルをロードできるように X ファイルをサポートしています。CD3Dmesh クラスと CD3DFrame クラスが特に重要です。

  • d3dfont.cpp - CD3DFont クラスを提供しています。基本フォントの出力をサポートして、統計表示などを使用できるようにします。

  • d3dutil.cpp - マテリアル、ライト、テクスチャのヘルパー関数など一般に有用な 3D 関数を提供します。

  • dxutil.cpp - メディア、レジストリ、タイマのヘルパー関数など有用な DirectX 関数を提供します。

こうしたファイルに対応するヘッダ ファイルは、Common\Include フォルダにあります。

5 月号の記事では、次のようにそれぞれのサンプルで CD3DApplication (通常の名前は CMyD3DApplication) のサブクラスと、「オーバーライド可能な」 メソッドのセットを実装しました。

// アプリケーションで作成される 3D シーン用のオーバーライド可能な関数
virtual HRESULT ConfirmDevice(D3DCAPS8*,DWORD,D3DFORMAT)   { return S_OK; }
virtual HRESULT OneTimeSceneInit()                         { return S_OK; }
virtual HRESULT InitDeviceObjects()                        { return S_OK; }
virtual HRESULT RestoreDeviceObjects()                     { return S_OK; }
virtual HRESULT FrameMove()                                { return S_OK; }
virtual HRESULT Render()                                   { return S_OK; }
virtual HRESULT InvalidateDeviceObjects()                  { return S_OK; }
virtual HRESULT DeleteDeviceObjects()                      { return S_OK; }
virtual HRESULT FinalCleanup()                             { return S_OK; }

こうしたメソッドのプロトタイプは、 CD3Dapplication クラスの d3dapp.h にあります。サンプル フレームワークを使用して新しい D3D アプリケーションを作成するには、新しいプロジェクトを作成し、関数を新しく実装します。こうした関数を、サンプル フレームワーク用の「オーバーライド可能な」インターフェイスと呼びます。D3D SDK サンプルでは、これを行います。こうしたメソッドは、基本的に 5 月号の記事で取り上げたものと同じです。詳細については、5 月号の記事を参照してください。InitDeviceObjects と RestoreDeviceObjects との違い、および InvalidateDeviceObjects と DeleteDeviceObjects との違いを覚えておいてください。InitDeviceObjects はデバイスを完全に作成するときに呼び出し、DeleteDeviceObjects はデバイスを完全に破棄するときに呼び出します。また、RestoreDeviceObjects はデバイスの Reset を呼び出す前に呼び出し、InvalidateDeviceObjects はデバイスの Reset を呼び出した後に呼び出します。これを行うのは、デバイスの一部を変更する場合や、消失デバイスを処理する必要がある場合です。新しい適切なフレームワークのサンプルを作成するには、コードを記述する箇所を理解してください。たとえば、管理されたテクスチャは、InitDeviceObjects で作成し、DeleteDeviceObjects で破棄しますが、管理されていないテクスチャは、RestoreDeviceObjects で作成し、InvalidateDeviceObjects で破棄する必要があります。

Base フレームワークのサンプル

このような詳細事項を念頭に置いて、実験の基礎として使用する簡単なサンプルを作成します。このサンプルは、SDK の Direct3D フォルダに置いてください。使用するパス情報がすべてあるため修正の手間が省けます。次の図 2 は、次回からの記事で何回か使用する 「Base」 サンプル用の Visual C++® 6 のプロジェクト ビューを示しています。

directx02192001-f02.gif

2. DirectX 8 Base   サンプルのプロジェクト   ビュー

完全を期すために、ここでは DirectX Graphics の 5 つのサンプル フレームワーク モジュールをすべて使用します。実際は d3dfile.cpp は不要です。このサンプルのクラス定義では、既に説明し、また次に示す 「オーバーライド可能な」 関数を単純にオーバーロードしています。

class CMyD3DApplication : public CD3DApplication
{ 
protected:
    CD3DFont*     m_pFont;              // 文字描画用のフォント
    LPDIRECT3DVERTEXBUFFER8 m_pVB;       // 頂点を保持するためのバッファ
    HRESULT ConfirmDevice( D3DCAPS8*, DWORD, D3DFORMAT );
    HRESULT OneTimeSceneInit();
    HRESULT InitDeviceObjects();
    HRESULT RestoreDeviceObjects();
    HRESULT InvalidateDeviceObjects();
    HRESULT DeleteDeviceObjects();
    HRESULT FinalCleanup();
    HRESULT Render();
    HRESULT FrameMove();
public:
    CMyD3DApplication();
};

この 「Base」 サンプルの場合、2 つの三角形から構成された単純な 「四角形」 をレンダリングすることによって、フレームワークの使用方法を十分に示すことができます。シェーダ関連事項は、すべてここから始まります。「四角形」 は頂点バッファにあります。DirectX 8 の場合、ジオメトリ データのコンテナが頂点バッファです。頂点バッファの使用方法については、Direct3D のヘルプを参照してください。

四角形の定義とその頂点情報は、次に示すようにきわめて簡単です。

// 四角形
#define NUM_VERTS 4
#define NUM_TRIS  2
// カスタム頂点タイプの構造体
struct CUSTOMVERTEX
{
    FLOAT x, y, z;      // 頂点の未トランスフォーム位置
    DWORD color;        // 頂点カラー
};
// カスタム頂点の構造体を記述したカスタム FVF
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)
// QUAD の頂点を初期化します
CUSTOMVERTEX g_Vertices[] =
{
   //  x     y     z       ディフューズ
   { -1.0f,-1.0f, 0.0f, 0xff00ff00, },//bl
   {  1.0f,-1.0f, 0.0f, 0xff00ff00, },//br
   {  1.0f, 1.0f, 0.0f, 0xffff0000, },//tr
   { -1.0f, 1.0f, 0.0f, 0xff0000ff, },//tl
};

四角形を完全に指定するには、 2 つの三角形と、三角形ファンの 4 つの頂点を使用します。頂点情報は、{x, y, z} 3D ベクトル位置情報だけでなくディフューズ色からも構成されています。位置情報の初期条件によって、四角形は起点を中心に配置されます。次に、「オーバーライド可能な」関数を見ていきます。

//---------------------------------------------------------------------
// 名前: CMyD3DApplication()
// 説明: アプリケーションのコンストラクタ。アプリケーションの属性を設定
//---------------------------------------------------------------------
CMyD3DApplication::CMyD3DApplication()
{
    m_strWindowTitle    = _T("Base: D3D Basic Example");
    m_bUseDepthBuffer   = TRUE;
    m_pVB            = NULL;
    m_pFont           = new CD3DFont( _T("Arial"), 12, D3DFONT_BOLD );
}

上記の CMyD3DApplication クラス コンストラクタでは、ウィンドウ タイトルを初期化し、深度バッファを使用可能にし、事前に頂点バッファ インターフェイス ポインタを NULL へ初期化し、デバッグ出力に使用するフォントを設定します。

//---------------------------------------------------------------------
// 名前: OneTimeSceneInit()
// 説明: アプリケーションの初期起動時に呼び出されます。
//       この関数は永続的なすべての初期化を実行します。
//---------------------------------------------------------------------
HRESULT CMyD3DApplication::OneTimeSceneInit()
{
    return S_OK;
}

アプリケーションの起動時に呼び出される OneTimeSceneInit は、D3DDevice に関連付けられていない構造体を初期化するのに使用します。このサンプルでは、1 回だけの初期化は必要はないため、OneTimeSceneInit メソッドは上記のように何もしていません。

//---------------------------------------------------------------------
// 名前: FrameMove()
// 説明: 1 フレームにつき 1 回呼び出します。この呼び出しは、
//       シーンをアニメーションするためのエントリ ポイントです。
//---------------------------------------------------------------------
HRESULT CMyD3DApplication::FrameMove()
{
    // ワールド座標に対し、y 軸を中心にオブジェクトを回転させます。
    D3DXMATRIX matWorld;
    D3DXMatrixRotationY( &matWorld, m_fTime * 4.0f );
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
    return S_OK;
}

上記の FrameMove メソッドを使用すると、1 つのフレームに 1 回実行するアニメーション アクションを指定することができます。このサンプルでは、起点にある Y 軸を中心に 「四角形」 を回転させる y 回転を設定しています。

//-----------------------------------------------------------------------
// 名前: Render()
// 説明: 1 フレームにつき 1 回呼び出されます。この呼び出しは、
//       3D レンダリングのエントリ ポイントです。この関数はレンダリングの
//       ステートを設定し、ビューポートをクリアして、シーンをレンダリングします。
//------------------------------------------------------------------------
HRESULT CMyD3DApplication::Render()
{
    // ビューポートをクリアします。
    m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 
      D3DCOLOR_XRGB(0,0,128), 1.0f, 0L );
    // シーンを開始します。
    if( SUCCEEDED( m_pd3dDevice->BeginScene() ) )
    {
     // 頂点バッファであるストリーム 0 のソースを指定します。
     // 次に、使用する頂点シェーダを D3D に知らせます。
     // 実際にレンダリングする DrawPrimitive() を呼び出します。
     m_pd3dDevice->SetStreamSource( 0, m_pVB, sizeof(CUSTOMVERTEX) );
     m_pd3dDevice->SetVertexShader( D3DFVF_CUSTOMVERTEX );
     m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, NUM_TRIS );
     // 統計データを出力します。
        m_pFont->DrawText( 2,  0, D3DCOLOR_ARGB(255,255,255,0),
                           m_strFrameStats );
        m_pFont->DrawText( 2, 20, D3DCOLOR_ARGB(255,255,255,0), 
                           m_strDeviceStats );
        // シーンを終了します。
        m_pd3dDevice->EndScene();
    }
    return S_OK;
}

上記の Render メソッドは、Clear を使用してまずビューポートをクリアします。次に、BeginScene/EndSceneSetStreamSource の間で SetStreamSource を呼び出して、カスタム頂点タイプのサイズと同じストライドを使って頂点バッファ m_pVB を使用することを D3D デバイスに伝えます。次に、固定機能 FVF シェーダを使用することを D3D デバイスに伝えます。最後に、 DrawPrimitive を呼び出して、四角形をレンダリングします。

//------------------------------------------------------------------------
// 名前: InitDeviceObjects()
// 説明: 管理されたテクスチャや管理されたオブジェクトなど、デバイスに依存する
//       管理されたオブジェクトをすべて作成します。
//------------------------------------------------------------------------
HRESULT CMyD3DApplication::InitDeviceObjects()
{
    // フォントの内部テクスチャを初期化します。
    m_pFont->InitDeviceObjects( m_pd3dDevice );
    return S_OK;
}

上記のサンプルの InitDeviceObjects メソッドは、対応する InitDeviceObjects メソッドを使用して、デバッグ出力用のフォントを初期化するだけです。

//------------------------------------------------------------------------
// 名前: RestoreDeviceObjects()
// 説明: デバイスを作成したり、そのサイズを元に戻した後で、
//       デバイス メモリ オブジェクトとそのステートを元に戻します。
//------------------------------------------------------------------------
HRESULT CMyD3DApplication::RestoreDeviceObjects()
{
    // フォント用のデバイス オブジェクトを元に戻します。
    m_pFont->RestoreDeviceObjects();
    // 頂点バッファを作成します。3 つのカスタム頂点を保持できるだけの
    // メモリを既定のプールから割り当てます。また、FVF を指定して、
    // このメモリに保持するデータを頂点バッファに知らせます。
    if( FAILED( m_pd3dDevice->CreateVertexBuffer( 
                                  NUM_VERTS*sizeof(CUSTOMVERTEX),
                                  D3DUSAGE_WRITEONLY, 
                                  D3DFVF_CUSTOMVERTEX,
                                  D3DPOOL_DEFAULT, &m_pVB ) ) )
    {
        return E_FAIL;
    }
    // 頂点バッファにデータを入れます。これには、VB をロックして
    // 頂点にアクセスする必要があります。頂点バッファはデバイス 
    // メモリに置けるためこの方法が必要になります。
    VOID* pVertices;
    if( FAILED( m_pVB->Lock( 0, sizeof(g_Vertices), 
     (BYTE**)&pVertices, 0 ) ) )
        return E_FAIL;
    memcpy( pVertices, g_Vertices, sizeof(g_Vertices) );
    m_pVB->Unlock();
    // 射影行列を設定します。
    D3DXMATRIX matProj;
    FLOAT fAspect = m_d3dsdBackBuffer.Width / 
                    (FLOAT)m_d3dsdBackBuffer.Height;
    D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, fAspect, 
                                 1.0f, 100.0f );
    m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
    // ビュー行列を設定します。ビュー行列を定義するには、
    // 視点、注視点、および上にする方向を指定します。
    // ここでは、z 軸に沿った 4 つの視点ユニットと 1 つの上方ユニットを設定し、
    // 原点を注視点とし、y 方向に「上方」を
    // 定義します。
    D3DXMATRIX matView;
    D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3( 0.0f, 1.0f,-4.0f ), //視点
                                  &D3DXVECTOR3( 0.0f, 0.0f, 0.0f ), //注視点
                                  &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ));//上方
    m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
    // 既定のテクスチャ ステートを設定します。
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,
            D3DTOP_SELECTARG2 );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, 
            D3DTA_TEXTURE ); 
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, 
            D3DTA_DIFFUSE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,   
            D3DTOP_SELECTARG1 );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, 
            D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP,
            D3DTOP_DISABLE );
    m_pd3dDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP,
            D3DTOP_DISABLE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, 
            D3DTEXF_LINEAR );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, 
            D3DTEXF_LINEAR );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSU,  
            D3DTADDRESS_CLAMP );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSV,  
            D3DTADDRESS_CLAMP );    
    m_pd3dDevice->SetRenderState( D3DRS_DITHERENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_ZENABLE,      TRUE );
    // 独自の頂点カラーを使用するため、D3D ライティングを無効にします。
    m_pd3dDevice->SetRenderState( D3DRS_LIGHTING,     FALSE );
    // カリングを無効にして、三角形の前面と背面が見えるようにします。
    m_pd3dDevice->SetRenderState( D3DRS_CULLMODE,     D3DCULL_NONE );
    return S_OK;
}

上記の RestoreDeviceObjects は、このサンプルで大きな働きをします。まず、CreateVertexBuffer を使用して頂点バッファ m_pVB を作成します。次に、頂点バッファをロックし、四角形のデータを入力してから、ロックを解除します。さらに、ビュー行列と射影行列を初期化し、両方の行列の SetTransform を使用して、デバイスでそれぞれの行列を設定します。最後に、 SetTextureStageStateSetRenderState を使用して、一連のレンダリング ステートを初期化します。このサンプルではマルチテクスチャは使われていないため、ステージが 0 のステートだけが設定されます。

//------------------------------------------------------------------------
// 名前: InvalidateDeviceObjects()
// 説明: デバイス依存オブジェクトが消失する場合に呼び出します。
//------------------------------------------------------------------------
HRESULT CMyD3DApplication::InvalidateDeviceObjects()
{
    m_pFont->InvalidateDeviceObjects();
    SAFE_RELEASE ( m_pVB );
    return S_OK;
}

上記の InvalidateDeviceObjects は、SAFE_RELEASE マクロを使用して頂点バッファを解放し、フォントの InvalidateDeviceObjects メソッドを呼び出して、フォントが作成した内部オブジェクトを解放します。

//------------------------------------------------------------------------
// 名前: DeleteDeviceObjects()
// 説明: アプリケーションを終了する場合またはデバイスを変更する場合に呼び出します。
//       この関数はデバイス依存オブジェクトを検出します。
//------------------------------------------------------------------------
HRESULT CMyD3DApplication::DeleteDeviceObjects()
{    
    m_pFont->DeleteDeviceObjects();
    return S_OK;
}

上記の DeleteDeviceObjects は、フォントの DeleteDeviceObjects メソッドを呼び出します。

//------------------------------------------------------------------------
// 名前: FinalCleanup()
// 説明: アプリケーションを終了する前に呼び出します。この関数によって
//       アプリケーションは、自らの後処理をすることができます。
//------------------------------------------------------------------------
HRESULT CMyD3DApplication::FinalCleanup()
{
    SAFE_DELETE( m_pFont );
    return S_OK;
}

上記の FinalCleanup は、SAFE_DELETE マクロを使用して、デバッグ用の表示フォントの最終処理を行います。

//------------------------------------------------------------------------
// 名前: ConfirmDevice()
// 説明: デバイスの初期化時に呼び出します。このコードは、デバイスに
//       機能の最小セットがあるかどうかをチェックします。
//------------------------------------------------------------------------
HRESULT CMyD3DApplication::ConfirmDevice( D3DCAPS8* pCaps, 
                                          DWORD dwBehavior,
                                          D3DFORMAT Format )
{
    return S_OK;
}

上記の ConfirmDevice は空のスタブです。つまり、このサンプル フレームワークでは、機能、頂点処理タイプ、またはバック バッファ フォーマットに関係なく、すべての D3D デバイスは有効に使用できると見なされます。

図 3 の Base のスクリーンショットは、「虹」 のディフューズ色エフェクトでレンダリングされた四角形を表示します。

directx02192001-f03.gif

3. DirectX 8 Base   サンプル、ディフューズ色シェーディングによる四角形のレンダリング

BaseVS 頂点シェーダのサンプル

これで、DirectX 8 Graphics フレームワークのサンプルで四角形をレンダリングできるため、頂点シェーダに移ります。ここでは、ソフトウェアの頂点シェーダを使用することと、AMD および Intel から高度なハードウェア製品が提供されていることを覚えておいてください。ソフトウェアの頂点シェーダは、移植性のある SIMD コードと見なすことができます。これからハードウェア シェーダ カードを購入することも適切な方法ですが、頂点シェーダを使用したプロトタイピング作業のほとんどはこのままで実行できます。ハードウェアがないからといって、実験を中止することはありません。

頂点シェーダの構文と能力について知るため、またシェーダの使い方になれるために、この BaseVS というサンプルでは、頂点シェーダをいくつか使用します。すべてのパス情報が生かせるため、このサンプルも SDK の Direct3D フォルダに置いてください。次の図 4 は、BaseVS プロジェクトのウィンドウを示しています。

directx02192001-f04.gif

4. BaseVS プロジェクト

紙面の都合上、フレームワークのすべての関数を改めて説明することはできないため、ダウンロードしたコードで個々に確認してください。ダウンロード ファイルには、サンプルを正しく動作させるために DX SDK メディア ディレクトリにコピーするビットマップ画像 dx8_logo.bmp が入っています。下記に示す ConfirmDevice メソッドには注意してください。デバイスでハードウェア頂点処理または混合頂点処理がサポートされていても、ハードウェアによるシェーダがサポートされていない場合、デバイスは拒否されます。これにより、geForce2 のような、ソフトウェア頂点処理機能を備えたハードウェア TnL デバイス上でも、ハードウェア頂点処理機能を備えたシェーダ カード上でも実行することができます。

ソフトウェアによる頂点シェーダでも、うまく実行できます。これは、AMD と Intel が、それぞれのプロセッサ用のパイプラインを向上させたからです。ソフトウェア頂点シェーダは、移植性のある SIMD コードを簡単に得るための方法と見なすことができます。しかし、シェーダ カードが入手できれば、熱心な開発者なら間違いなくこのカードを使用するものと思われます。

//-----------------------------------------------------------------------------
// 名前: ConfirmDevice()
// 説明: デバイスの初期化時に呼び出します。このコードは、デバイスに
//       機能の最小セットがあるかどうかをチェックします。
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::ConfirmDevice( D3DCAPS8* pCaps, 
                                          DWORD dwBehavior,
                                          D3DFORMAT Format )
{    
    if( (dwBehavior & D3DCREATE_HARDWARE_VERTEXPROCESSING ) ||
        (dwBehavior & D3DCREATE_MIXED_VERTEXPROCESSING ) )
    {
        if( pCaps->VertexShaderVersion < D3DVS_VERSION(1,0) )
            return E_FAIL;
    }
    return S_OK;
}

頂点シェーダについて見ていきます。ここでは、頂点シェーダを使用する場合に理解しておく点をいくつか説明します。

  • API の使い方

  • 頂点の定義

  • シェーダの宣言

  • シェーダの定義

API の使い方

前回のコラムで、シェーダを使用するための新しい API メソッドについて簡単に触れました。このメソッドは次のとおりです。

  • D3DXAssembleShader

  • IDirect3DDevice8::CreateVertexShader

  • IDirect3DDevice8::SetVertexShaderConstant

  • IDirect3DDevice8::SetStreamSource

  • IDirect3DDevice8::SetVertexShader

  • IDirect3DDevice8::DeleteVertexShader

D3DXAssembleShader and IDirect3DDevice8::CreateVertexShader  はシェーダの実行時表現を生成するために、IDirect3DDevice8::CreateVertexShader はシェーダのハンドルを返すために使用します。次のコードで示す使い方では、 SimpleVertexShader0 はシェーダ定義を持ち、 dwDecl0 はシェーダ宣言を持つことを前提としています (詳細は後で説明します)。

ID3DXBuffer*   pshader0; 
ID3DXBuffer*   perrors;
// シェーダをアセンブルします。
rc = D3DXAssembleShader( SimpleVertexShader0 , 
sizeof(SimpleVertexShader0)-1, 0 , NULL , 
&pshader0 , perrors );
// 頂点シェーダのハンドルを作成します。
rc = m_pd3dDevice->CreateVertexShader( dwDecl0, 
                     (DWORD*)pshader0->GetBufferPointer(),
                     &m_hVertexShader0, 0 );

IDirect3DDevice8::SetVertexShaderConstant は、シェーダが使用する定数定義のロードに使用します。IDirect3DDevice8::SetStreamSource は、頂点成分のソース、つまり頂点成分を格納している頂点バッファと、シェーダで期待するデータのサイズであるストライドに関する事項をランタイムに伝えます。 IDirect3DDevice8::SetVertexShader は、 IDirect3DDevice8::CreateVertexShader で作成されたシェーダのハンドルを受け取り、このハンドルをデバイスの現在の頂点シェーダにします。次のコードでは、次のメソッドを使用しています。

//シェーダの定数を設定します。
float   color[4] = {0,1,0,0};
m_pd3dDevice->SetVertexShaderConstant( 8 , color , 1 );
//シェーダ ストリームを設定します。
m_pd3dDevice->SetStreamSource( 0, m_pVBVertexShader0,
 sizeof(VERTEXSHADER0VERTEX) );
//シェーダのハンドルを設定します。
m_pd3dDevice->SetVertexShader( m_hVertexShader0 );

別の使用方法としては、次に示すようにストリーム 0 を使用して、 IDirect3DDevice8::SetVertexShader で FVF コードを受け取って、固定機能頂点シェーダを有効にします。

//FVF 頂点シェーダを設定します。
m_pd3dDevice->SetVertexShader( D3DFVF_VERTEXSHADER0VERTEX );

最後に、IDirect3DDevice8::DeleteVertexShader を使用して、デバイスから頂点シェーダを削除します。既に作成したものを孤立させないでデバイスを「クリーン」にしておくためにこの操作は重要です。次のコードは、このメソッドの呼び出し方を示しています。

//デバイスからシェーダを削除します。
m_pd3dDevice->DeleteVertexShader( m_hVertexShader0 );

これで API が使えるようになりました。次にシェーダを作成するために RestoreDeviceObjects で呼び出す BuildVertexShaders 関数と、シェーダを破棄するために InvalidateDeviceObjects で呼び出す DestroyVertexShaders 関数について説明します。ここでは、シェーダの細部について説明します。

頂点の定義

まず、Base サンプルの 「四角形」 を使用して、いくつかある単純なシェーダ用に頂点定義を拡張します。また、四角形をレンダリングします。

//四角形
#define NUM_VERTS 4
#define NUM_TRIS  2

四角形には別々のシェーダを 4 つ使用します。

// 頂点シェーダ 0 の頂点タイプ用の構造体
struct VERTEXSHADER0VERTEX
{
    FLOAT x, y, z;      // 頂点の未トランスフォーム位置
    DWORD color;        // 頂点色
};
// このカスタム FVF には、頂点シェーダ 0 の頂点構造体を記述します。
#define D3DFVF_VERTEXSHADER0VERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)
// 四角形をレンダリングするための頂点を初期化します。
VERTEXSHADER0VERTEX g_VertexShaderVertices0[] =
{
   //  x     y     z       ディフューズ
    { -1.0f,-1.0f, 0.0f, 0xff00ff00, },//bl
    {  1.0f,-1.0f, 0.0f, 0xff00ff00, },//br
    {  1.0f, 1.0f, 0.0f, 0xffff0000, },//tr
    { -1.0f, 1.0f, 0.0f, 0xff0000ff, },//tl
};
// 頂点シェーダ 2 の頂点タイプ用の構造体
struct VERTEXSHADER2VERTEX
{
    FLOAT x, y, z;      // 頂点の未トランスフォーム位置
    DWORD color;        // 頂点色
    FLOAT tu, tv;       // テクスチャ座標
};
// このカスタム FVF には、頂点シェーダ 2 の頂点構造体を記述します。
#define D3DFVF_VERTEXSHADER2VERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
// 四角形をレンダリングするための頂点を初期化します。
VERTEXSHADER2VERTEX g_VertexShaderVertices2[] =
{
   //  x     y     z      ディフューズ    u1    v1 
    { -1.0f,-1.0f, 0.0f, 0xff00ff00, 1.0f, 1.0f,},//bl
    {  1.0f,-1.0f, 0.0f, 0xff00ff00, 0.0f, 1.0f,},//br
    {  1.0f, 1.0f, 0.0f, 0xffff0000, 0.0f, 0.0f,},//tr
    { -1.0f, 1.0f, 0.0f, 0xff0000ff, 1.0f, 0.0f,},//tl
};
// 頂点シェーダ 3 の頂点タイプ用の構造体
struct VERTEXSHADER3VERTEX
{
    FLOAT x, y, z;      // 頂点の未トランスフォーム位置
    FLOAT nx, ny, nz;   // 頂点法線
    DWORD color;        // 頂点色
    FLOAT tu, tv;       // テクスチャ座標
};
// このカスタム FVF には、頂点シェーダ 2 の頂点構造体を記述します。
#define D3DFVF_VERTEXSHADER3VERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE|D3DFVF_TEX1)
// 四角形をレンダリングするための頂点を初期化します。
VERTEXSHADER3VERTEX g_VertexShaderVertices3[] =
{
   //  x     y     z     nx   ny    nz      ディフューズ    u1    v1 
    { -1.0f,-1.0f, 0.0f, 0.0f,0.0f, 1.0f, 0xff00ff00, 1.0f, 1.0f,},//bl
    {  1.0f,-1.0f, 0.0f, 0.0f,0.0f, 1.0f, 0xff00ff00, 0.0f, 1.0f,},//br
    {  1.0f, 1.0f, 0.0f, 0.0f,0.0f, 1.0f, 0xffff0000, 0.0f, 0.0f,},//tr
    { -1.0f, 1.0f, 0.0f, 0.0f,0.0f, 1.0f, 0xff0000ff, 1.0f, 0.0f,},//tl
};

次の 4 つの頂点タイプを定義しました。

  • VERTEXSHADER0VERTEX

  • VERTEXSHADER1VERTEX

  • VERTEXSHADER2VERTEX

  • VERTEXSHADER3VERTEX

この 4 つの頂点タイプは、4 つの頂点シェーダに対応しています。ここで使用する頂点定義には、構造体、FVF コード、および初期化済み頂点を記述しました。

シェーダ定義

  • 頂点シェーダの宣言部には、シェーダの静的な外部インターフェイスを定義します。この場合、頂点シェーダの宣言には、次の情報を指定します。

  • シェーダを現在のシェーダとして設定するときの定数メモリへのデータのロード。

  • 頂点シェーダの入力レジスタへのストリーム データのバインド。

それぞれの定数トークンには、1 つまたは複数の連続した 4-DWORD 定数レジスタの値を指定します。これにより、シェーダで定数メモリの任意のサブセットを更新することができ、定数メモリの現在値が格納されているデバイス ステートが上書きされます。こうした値は、特定のシェーダがデバイスにバインドされているときに IDirect3DDevice8::SetVertexShaderConstant メソッドを呼び出すことにより、 IDirect3DDevice8::DrawPrimitive 呼び出しの間に上書きすることができます。シェーダが使用する単純な定数には、この手順を使用します。

ストリームのバインド情報には、各データ ストリームにある各要素の型と頂点入力レジスタの割り当てを定義します。タイプには、算術データ型と、次元数 (1 つ、2 つ、3 つ、または 4 つの値) を指定します。値の数が 3 以下のストリーム データ要素は、0 個または 1 個以上の 0.0f 値と、1 個の 1.0f 値によって、4 つの値に常に拡張されます。わかりやすくするために、ここではストリームを 1 つだけ使用します。

このサンプルにはシェーダが 4 つあるため、次のようにシェーダ宣言を 4 つ記述してください。

//シェーダ宣言
float   c[4] = {0.0f,0.5f,1.0f,2.0f};
DWORD dwDecl0[] =
{
    D3DVSD_STREAM(0),
    D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ),       //D3DVSDE_POSITION,0  
    D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR ),      //D3DVSDE_DIFFUSE, 5
    D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
                      *(DWORD*)&c[2],*(DWORD*)&c[3],
    D3DVSD_END()
};
DWORD dwDecl1[] =
{
    D3DVSD_STREAM(0),
    D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ),       //D3DVSDE_POSITION,0  
    D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR ),      //D3DVSDE_DIFFUSE, 5
    D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
                      *(DWORD*)&c[2],*(DWORD*)&c[3],
    D3DVSD_END()
};
DWORD dwDecl2[] =
{
    D3DVSD_STREAM(0),
    D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ),       //D3DVSDE_POSITION,  0  
    D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR ),      //D3DVSDE_DIFFUSE,   5  
    D3DVSD_REG(D3DVSDE_TEXCOORD0, D3DVSDT_FLOAT2 ),      //D3DVSDE_TEXCOORD0, 7
    D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
                      *(DWORD*)&c[2],*(DWORD*)&c[3],
    D3DVSD_END()
};
DWORD dwDecl3[] =
{
    D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
                      *(DWORD*)&c[2],*(DWORD*)&c[3],
    D3DVSD_STREAM(0),
    D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ),       //D3DVSDE_POSITION,  0   
    D3DVSD_REG(D3DVSDE_NORMAL, D3DVSDT_FLOAT3 ),         //D3DVSDE_NORMAL,    3  
    D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR ),      //D3DVSDE_DIFFUSE,   5  
    D3DVSD_REG(D3DVSDE_TEXCOORD0, D3DVSDT_FLOAT2 ),      //D3DVSDE_TEXCOORD0, 7
    D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
                      *(DWORD*)&c[2],*(DWORD*)&c[3],
    D3DVSD_END()
};

ここで使用するシェーダ宣言には、すべて定数を定義しています。こうしたシェーダ全部によって、シェーダでデータ ストリーム 0 が使用できるように設定されます。このサンプルの場合のように明示的なシェーダ宣言を使用する場合は、 D3DVSD_REG プリプロセッサ マクロで、頂点レジスタに頂点成分マッピングを定義します。また、FVF 頂点バッファを使用するので、定義済み D3DVSDE_ マクロを使用して、レジスタを指定します。

次に、最初の 2 つのシェーダ宣言 dwDecl0 と dwDecl1 では、 D3DVSD_REG マクロと明示的なレジスタの割り当てを使用して、レジスタ 0 とレジスタ 7 での位置とディフューズ色を定義します。g_VertexShaderVertices0 頂点初期化の VERTEXSHADER0VERTEX, D3DFVF_VERTEXSHADER0VERTEX 頂点定義と、g_VertexShaderVertices1 頂点初期化 の VERTEXSHADER1VERTEX, D3DFVF_VERTEXSHADER1VERTEX 頂点定義との対応関係に注意してください。

次の 2 つのシェーダ宣言 dwDecl2dwDecl3 は、少し複雑になります。dwDecl2 宣言では、位置、ディフューズ、およびテクスチャ座標を定義します。これは、テクスチャ頂点シェーダを実行することを示しています。VERTEXSHADER2VERTEX , D3DFVF_VERTEXSHADER2VERTEX  頂点定義と g_VertexShaderVertices2  頂点初期化が対応する様子に注意してください。最後に、dwDecl3 の宣言では、位置、ディフューズ、およびテクスチャ座標を定義します。これは、ライティングを使用したテクスチャ頂点シェーダを実行することを示しています。VERTEXSHADER3VERTEX , D3DFVF_VERTEXSHADER3VERTEX  頂点定義と g_VertexShaderVertices3  頂点初期化が対応する様子に注意してください。

ここでは、この最後の頂点シェーダでライティングを行いますが、これ以降、ライティング操作の大半はピクセル シェーダで行います。ただし、効率を高めるために、アンビエントなど、グローバルで標準的な特定のライティング操作は頂点シェーダで実行します。

シェーダ定義

このサンプルには 4 つのシェーダがあるため、頂点定義とシェーダ宣言に対応する 4 つのシェーダ定義を記述する必要があります。シェーダ定義については、ここでは簡単に触れ、後で詳しく説明します。 SimpleVertexShader0  は、m4x4 命令を使用して 4 x 4 行列トランスフォームを実行し、 oPos  位置出力レジスタの結果をコピーします。次に、 SimpleVertexShader0  は、コンスタント カラーを oD0 ディフューズ色出力レジスタにコピーします。 SimpleVertexShader1  は、トランスフォーム フェーズに対して同じ操作を実行しますが、各成分に対しては dp4 命令を使う 4 x 1 内積を使用してこの操作を行い、結果を oPos  出力レジスタに書き込みます。コンスタント カラーをコピーする代わりに、頂点に指定したディフューズ色を使用して、oD0 ディフューズ色出力レジスタにコピーします。  SimpleVertexShader2  はさらに少し複雑になります。同様にトランスフォームには dp4 命令を使用し、その結果得られた位置を oPos  位置出力レジスタに書き込みます。このシェーダは、テクスチャリングとテクスチャ座標をミックスに追加し、テクスチャ座標を使用して oTO 出力テクスチャ座標レジスタを更新します。最後に、SimpleVertexShader3  は、トランスフォームに対して dp4 命令を使用して、同じトランスフォーム フェーズ操作を実行し、その結果得られた位置を oPos  位置出力レジスタに書き込みます。カラー値やテクスチャ値をコピーする代わりに、このシェーダは、「四角形」がライトの方向を向いているかどうかを判定し、向いていない場合はレンダリングを行いません。この場合、法線成分を使う dp3 命令と、定数レジスタに格納されているライトの方向を使用します。

以上のシェーダのソースは次のとおりです。

// 単純な頂点シェーダ 0
// 定数
//      reg c0   = (0,0.5,1.0,2.0)
//      reg c4-7   = WorldViewProj 行列
//      reg c8    = コンスタント カラー
// ストリーム 0
//     reg v0   = 位置 ( 4 x 1 ベクトル)
//     reg v5   = ディフューズ色
const char SimpleVertexShader0[] =
"vs.1.0                            // シェーダ バージョン 1.0   \n"\
"m4x4    oPos , v0   , c4          // 射影後の位置を出力        \n"\
"mov     oD0  , c8                 // ディフューズ色 = c8       \n";
// 単純な頂点シェーダ 1
// 定数
//      reg c0   = (0,0.5,1.0,2.0)
//      reg c4-7   = WorldViewProj 行列
//      reg c8    = コンスタント カラー
// ストリーム 0
//     reg v0   = 位置 ( 4 x 1 ベクトル)
//     reg v5   = ディフューズ色
const char SimpleVertexShader1[] =
"vs.1.0                           // シェーダ バージョン 1.0    \n"\
"dp4     oPos.x , v0   , c4       // 射影後の x 位置を出力    \n"\
"dp4     oPos.y , v0   , c5       // 射影後の y 位置を出力    \n"\
"dp4     oPos.z , v0   , c6       // 射影後の z 位置を出力    \n"\
"dp4     oPos.w , v0   , c7       // 射影後の w 位置を出力    \n"\
"mov     oD0    , v5              // ディフューズ色 = 頂点色    \n";
// 単純な頂点シェーダ 2
// 定数
//      reg c0   = (0,0.5,1.0,2.0)
//      reg c4-7   = WorldViewProj 行列
//      reg c8    = コンスタント カラー
// ストリーム 0
//     reg v0   = 位置 ( 4 x 1 ベクトル)
//     reg v5   = ディフューズ色
//     reg v7   = テクスチャ座標 (2 x 1 ベクトル)
const char SimpleVertexShader2[] =
"vs.1.0                          // シェーダ バージョン 1.0 \n"\
"dp4     oPos.x , v0   , c4      // 射影後の x 位置を出力    \n"\
"dp4     oPos.y , v0   , c5      // 射影後の y 位置を出力    \n"\
"dp4     oPos.z , v0   , c6      // 射影後の z 位置を出力    \n"\
"dp4     oPos.w , v0   , c7      // 射影後の w 位置を出力    \n"\
"mov     oT0.xy , v7             // テクスチャ座標をコピー    \n";
// 単純な頂点シェーダ 3
// 定数
//      reg c0   = (0,0.5,1.0,2.0)
//      reg c4-7   = WorldViewProj 行列
//      reg c8    = コンスタント カラー
//      reg c12   = ライトの方向
// ストリーム 0
//     reg v0   = 位置 (4 x 1 ベクトル)
//     reg v3   = 法線 (4 x 1 ベクトル)
//     reg v5   = ディフューズ色
//     reg v7   = テクスチャ座標 (2 x 1 ベクトル)
const char SimpleVertexShader3[] =
"vs.1.0                          // シェーダ バージョン 1.0        \n"\
"dp4     oPos.x , v0   , c4      // 射影後の x 位置を出力        \n"\
"dp4     oPos.y , v0   , c5      // 射影後の y 位置を出力        \n"\
"dp4     oPos.z , v0   , c6      // 射影後の z 位置を出力        \n"\
"dp4     oPos.w , v0   , c7      // 射影後の w 位置を出力        \n"\
"dp3    r0.x   , v3   , c12      // ワールド空間の N と L の内積        \n"\
"mul     oD0    , r0.x , v5      // カラーを計算            \n"\
"mov     oT0.xy , v7             // テクスチャ座標をコピー        \n";

各シェーダ定義はテキスト文字列として定義するため、D3DXAssembleShader で使いやすくなっています。また、バインドの状況を明確にするため、各シェーダの定数レジスタとストリーム レジスタの定義をコメントにしました。独自にシェーダを作成する場合は、同じようなコメント記述方式を使用することをお勧めします。

シェーダのデバッグ

シェーダの実装については詳しく説明したので、デバッグに移ります。ツールを使用すると、シェーダの実行結果が適切でない原因を簡単に突き止めることができます。Graphics チップ IHV nVidia は、自社の Web サイト (http://www.nvidia.com Leave-ms の開発者向けエリア) でシェーダのデバッグ用ツールを提供しています。図 6 では、nVidia シェーダ デバッガに用意されているビューにマークを付けておきましたが、シェーダの細部の状況はわかりやすくなっています。

重要なのは "Current Program" ビューです。このビューにはシェーダ プログラムが用意されているため、命令をステップ実行することができます。現在の命令は、青色で表示され、左端にダッシュ記号 (-) が付けられます。ブレークポイントを設定したプログラム行は、横にアスタリスク (*) が付けられます。プログラムの表示部分では、現在設定されている命令が逆アセンブルして表示されるため、シェーダのコンパイルに使用した元のソース ファイルとはまったく異なって表示される場合があります (特にレジスタのポインタ変換やマスクが表示される場合)。シェーダが完了したことを示すために、すべてのプログラムは、"-end-" インジケータで終わります。このプログラムの列見出しには、アクティブなシェーダの現在のハンドルが表示されます。これは、API が IDirect3DDevice8::CreateVertexShader 呼び出しの結果として返すハンドルと一致します。

Dd148671.directx02192001-f05(ja-jp,MSDN.10).gif

5. nVidia シェーダ   デバッガ

ほかの重要なビューは、次のとおりです。

  • "Temp Registers" ビュー - プログラムのステップ実行中に、値を即座に表示できます。

  • "Input Streams" ビュー - ロードした定数の "Constant" ビューの場合と同じように、元のソース データを表示できます。

  • "Shader Output" ビュー - シェーダ出力の数値表現を表示できます。

また、シェーダの下部には、デバッガおよびシーンのステータスと、メッセージ出力ビューが表示されます。

directx02192001-f06.gif

6. コマンドが表示されている nVidia シェーダ   デバッガのツールバー

このデバッガは、メニューからでもツールバーからでも操作できます。メニューで使用できるすべてのオプションは、ツールバーのボタンから使用できます。ツールバーを使用する方が、ほとんどの場合便利です。ツールバーには使用可能なデバッガ コマンドがあり、図 6 では頂点シェーダに関連したコマンドを示しています。

このデバッガ ツールおよびそのボタンの使い方の詳細については、nVidia のマニュアルを参照してください。このツールは非常に便利なため、本格的にシェーダを作成する場合は、このツールをダウンロードして使い方を学ぶことをお勧めします。このツールでもピクセル シェーダをデバッグできますが、これについては改めてこのコラムで取り上げます。

単純なシェーダの例

これまでに、シェーダ、頂点定義、シェーダ宣言、シェーダ定義を使用するための API と、nVidia シェーダ デバッガについて説明しました。ここでは、作業中の頂点シェーダについて取り上げます。BaseVS サンプルを起動するには、固定機能パイプラインを使用します。V キーを押すと、頂点シェーダが使用できます。Vertex Shader メニューからシェーダを選択します。

最初の頂点シェーダ SimpleVertexShader0 は、基本的なトランスフォームを実行し、コンスタント カラーを使用します。次のコードをもう一度見てください。

vs.1.0                      // シェーダ バージョン 1.0         
m4x4    oPos , v0   , c4   // 射影後の位置を出力   
mov     oD0  , c8          // ディフューズ色 = c8

この頂点シェーダは、 4 x 4 行列をトランスフォームする m4x4 命令を使用して、ワールド *ビュー* 射影行列の組み合わせを受け取り、この行列によって頂点位置をトランスフォームし、トランスフォームされたこの位置 (ベクトル) を oPos 出力レジスタに格納します。次に、コンスタント ディフューズは oD0 カラー出力レジスタにロードされます。このシェーダを使用できるのは、ワールドビュー射影行列の組み合わせを定数としてロードした場合です。ここではユーティリティ ルーチンを次のように使用しました。

LoadMatrix4( m_pd3dDevice , 
                                  4 , 
                                  m_matWorld * m_matView * m_matProj ); 
LoadMatrix4 の本体は次のようになります。
void   LoadMatrix4( IDirect3DDevice8* pdevice , 
                    DWORD creg , 
                    const D3DXMATRIX& matrix )
{
   D3DXMATRIX   trans;
   D3DXMatrixTranspose( &trans , &matrix );
   pdevice->SetVertexShaderConstant( creg , &trans , 4 );
}

このルーチンでは、元の行列の転置行列をロードし、IDirect3DDevice8::SetVertexShaderConstant  を使用して定数レジスタにロードします。また、呼び出しによってコンスタント カラーは c8 にロードされます。

float   color[4] = {0,1,0,0};
m_pd3dDevice->SetVertexShaderConstant( 8 , color , 1 );

これ以上簡単にはできません。図 7 は、この単純な頂点シェーダの結果を示しています。

directx02192001-f07.gif

7. コンスタント   カラー頂点シェーダ

はっきりしない点がある場合は、nVidia シェーダ デバッガを使用してこのシェーダをステップ実行することをお勧めします。図 5 は、このシェーダでシェーダ デバッガを使用している様子です。

2 番目のシェーダ SimpleVertexShader1 でも、ほぼ同じ結果が得られますが、やり方は少し違います。次の SimpleVertexShader1 のコードについて見ていきます。

vs.1.0                          // シェーダ バージョン 1.0         
dp4     oPos.x , v0   , c4      // 射影後の x 位置を出力
dp4     oPos.y , v0   , c5      // 射影後の y 位置を出力
dp4     oPos.z , v0   , c6      // 射影後の z 位置を出力
dp4     oPos.w , v0   , c7      // 射影後の w 位置を出力
mov     oD0    , v5             // ディフューズ色 = 頂点色;

4 x 4 行列をトランスフォームする m4x4 命令を使用して各頂点成分に対する操作を 1 つの操作にまとめる代わりに、4 x 1 ベクトルをトランスフォームする dp4 命令を使用して、このシェーダを各頂点成分に実行します。これは、先月のコラムで説明した 4 つのスロット/クロックを拡張する m4x4 命令に相当します。上記の 4 つの dp4 命令は、m4x4 で拡張する場合と同じです。これで理解できたと思います。シェーダ宣言の記述どおり、頂点位置は入力レジスタ v0 に、ディフューズ色は入力レジスタ v5 にあります。また、標準的な定数の一部は、シェーダ定数宣言行によって定数レジスタ c0-c3 にあらかじめロードされます。

D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
                  *(DWORD*)&c[2],*(DWORD*)&c[3],

また、呼び出しによってトランスフォームは、もう一度定数レジスタ c4 c7 にロードされます。

LoadMatrix4( m_pd3dDevice , 4 , 
                                  m_matWorld * m_matView * m_matProj );

directx02192001-f08.gif

8. ディフューズ色の頂点シェーダ

入力ストリーム レジスタに格納されている色は、前のシェーダで使用されて定数レジスタにロードされている色より優先されます。単純なディフューズ頂点シェーダの例については、図 8 を参照してください。

3 番目のシェーダ SimpleVertexShader2 は、テクスチャリングを頂点シェーダのトリック バッグに追加します。次の SimpleVertexShader2  のコードを見ていきます。

vs.1.0                           // シェーダ バージョン 1.0  
dp4     oPos.x , v0   , c4       // 射影後の x 位置を出力
dp4     oPos.y , v0   , c5       // 射影後の y 位置を出力
dp4     oPos.z , v0   , c6       // 射影後の z 位置を出力
dp4     oPos.w , v0   , c7       // 射影後の w 位置を出力
mov     oT0.xy , v7              // テクスチャ座標のコピー;

SimpleVertexShader1  の場合と同じ定数レジスタがロードされます。入力ストリーム バインドは、バインドされているテクスチャ座標を入力レジスタ v7 に追加する場合と同じです。このシェーダも、4 x 1 ベクトルをトランスフォームする dp4 命令を使用して各頂点コンポーネントに対して実行されます。次に、このシェーダは、テクスチャ座標を v7 から oT0 に移動します。これにより、図 9 のようなテクスチャ出力が作成されます。

directx02192001-f09.gif

9. 単純なテクスチャリング頂点シェーダ

4 番目のシェーダ SimpleVertexShader3 は、ライティングを頂点シェーダのトリック バッグに追加します。次の SimpleVertexShader3  のコードについて見ていきます。

vs.1.0                          // シェーダ バージョン 1.0 
dp4     oPos.x , v0   , c4      // 射影後の x 位置を出力   
dp4     oPos.y , v0   , c5      // 射影後の y 位置を出力   
dp4     oPos.z , v0   , c6      // 射影後の z 位置を出力   
dp4     oPos.w , v0   , c7      // 射影後の w 位置を出力   
dp3     r0     , v3   , c12     // ワールド空間での N と L の内積
mul     oD0    , r0.x , v5      // 色を計算
mov     oT0.xy , v7             // テクスチャ座標をコピー   

このシェーダも、4 x 1 ベクトルをトランスフォームする dp4 命令を使用して、各頂点コンポーネントに対して実行されます。次に、このシェーダは、ベクトルをトランスフォームする dp3 命令を使用して、従来の N と L の内積ライティング操作をレジスタ v3 の法線成分に実行します。この操作の結果が 0 より小さい場合、ポリゴンはライトの方向を向いていないため見えません。このことは、 mul 演算子で計算して得られた結果を oD0 にロードする時に行われます。ここでは、値はシェーダによって [0,1] という範囲にクランプされます。この実際の計算はライティング フェーズの一環として行われますが、2 回行う理由はわかりますか。最後に、テクスチャ座標は、レジスタ v7 から出力レジスタ oT0 に更新されます。さらに細かいことですが、Modulate テクスチャ ステージ演算子を変更し、この結果として得られる出力色としてディフューズとテクスチャの組み合わせを得られるようにする必要があります。この結果、「四角形」は図 10 のようになります。

directx02192001-f10.gif

10. 単純なライティングとテクスチャリングの頂点シェーダ

ディフューズ色が追加されたテクスチャに加えて、「四角形」はライトから回転して離れるに従って、その背面は黒く表示されます。これは背面にライトがないため黒く表示されるという点で視覚的には正常です。

これで単純な頂点シェーダの説明は終わりです。ただし、これはほんの一部分で、核心的な話にまでは進んでいません。頂点シェーダのほかの使い方として、キャラクタのアニメーション、カスタム フォグ エフェクト、ライティング エフェクトなどがあります。シェーダのコードを記述すれば、固定機能パイプラインに備わっている機能に依存せずに、開発者は思い通りに作業することができます。このコードを参考に、nVidia シェーダ デバッガ ツールを使用して、自分でいろいろと試してみることをお勧めします。

最後に

頂点シェーダの基本的な説明は済みましたので、ツールを用意して頂点パイプラインの可能性の限界を探ってみてください。コンスタント カラーとディフューズ色を使用し、ライティングの基本的な操作を処理することができます。シェーダ、およびプログラマブルなパイプラインによって、3D プログラマは創造性を自由に発揮し、進化し続ける 3D Graphics ハードウェアの最新機能を常に使用することができます。次回も引き続き、シェーダについて説明します。

このコラムの執筆に際して、Mike Burrows、Mike Anderson (Microsoft)、Chris Seitz、Chris Maughan (nVidia) 各氏の御協力に感謝いたします。

Philip Taylor は DirectX のプログラム マネージャの一人です。DirectX の最初のパブリック ベータの段階から手を染め、かつては実際に DirectX 2 でゲームを開発したこともありました。時間に余裕があるときは、さまざまな3-Dグラフィックス プログラミング メーリング リストで彼に出会うことがあるかもしれません。

DirectX Driving コラム アーカイブ