ピクセル シェーダ、第1部: イントロダクション
Philip Taylor
Microsoft Corporation
Septmber 18, 2001
Driving DirectX へようこそ。今月は、ピクセル シェーダを扱うコラムの一回目として、基本的なピクセル シェーダをいくつか紹介し、頂点シェーダとストリームとの関係を示します。ピクセル シェーダのプログラミングは、上流のコンポーネントに依存している部分があるため、それ単独で切り離して行うことはできません。
ピクセル シェーダの詳細を説明してから数か月が経っているので、まずピクセル シェーダの簡単な復習から始めます。ピクセル シェーダがパイプラインの中のどこに位置しているのかを説明し、ピクセル シェーダの使い方の仕組みを示します。
今回のサンプルは、基本的には単純な頂点シェーダを紹介した記事で使った、回転する四角形を使用する Base サンプルと同じもので、これにピクセル シェーダを追加しただけです。この単純なレンダリングを使用しているのは、第一にシェーダの効果を見えやすくするため、第二にサンプルがリファレンス デバイス上でも十分な速度で動作するようにするためです。geForce3 が発売されてから 6 か月が経ち、Radeon8500 も発売されたいま、ピクセル シェーダ ボードは普及しつつありますから、大部分のグラフィックス プログラマが近いうちにシェーダ カードを利用できるようになるでしょう。しかし現時点では、読者全員がシェーダ カードを持っているということを前提にする気はありません。
その後で、このサンプルで使用されている頂点シェーダが、下流のピクセル シェーダにどのように情報を流すかを解説します。最後に、ピクセル シェーダを使用するためのインフラストラクチャ、ピクセル シェーダの関数、およびこれらのシェーダが使用するピクセル シェーダ命令に対応するブレンド式の概念について説明します。
ピクセル シェーダの復習
ピクセル シェーダが行うピクセル処理は、単一のピクセルに適用される操作のみをカバーしています。ピクセル パイプラインの中での操作のシーケンスは次のようになっています。
- 三角形セットアップ
- ピクセル シェーダ(固定機能のマルチテクスチャを置き換え)
- カラー、テクスチャ座標などの繰り返し処理
- テクスチャのサンプリング
- テクスチャ/カラーのブレンド
- フォグのブレンド
- アルファ、ステンシル、デプスのテスト
- フレームバッファのブレンド
図 1 はこれらのフェーズを示すブロック ダイアグラムです。ただし三角形セットアップのフェーズは、固定機能のマルチテクスチャ フェーズまたはピクセル シェーダ フェーズよりも前に発生しています。ピクセル シェーダへの入力は頂点シェーダの出力から来ています。レジスタ v0 と v1 は、頂点シェーダの出力レジスタ oD0 と oD1 の頂点カラーを含んでいます。tex t0 などのピクセル シェーダ命令によって参照されるカラー ステージのテクスチャは、oT0 などの、そのステージに対応する頂点シェーダ出力レジスタの中のテクスチャ座標を使ってサンプリングされます。ピクセル シェーダはカラーおよびアルファ ブレンディング命令とテクスチャ アドレシング命令を使用してこれらの入力を操作し、結果を計算します。ピクセル シェーダが出力する結果は、レジスタ r0 の内容または出力ピクセル カラーです。この結果は、シェーダが処理を完了した時点で、その後の処理のためにフォグ ステージとレンダ ターゲット ブレンダに送られます。
図1. ピクセル処理パイプライン
これで、先月のコラムで固定機能のマルチテクスチャ操作とピクセル シェーダ操作の両方に対応するのが簡単だった理由がわかるでしょう。この 2 つは、ピクセル処理パイプラインの中で、同じフェーズに置かれているのです。
ピクセル シェーダの命令セット、ピクセル シェーダで利用できるリソース、およびピクセル シェーダ算術論理ユニット (ALU) の仮想アーキテクチャの詳細については、1 月のコラム「DirectX 8.0 のプログラマブル シェーダ」を参照してください。このコラムでは、ピクセル シェーダを詳しく解説し、実際のコードを作成して、命令セットの使用方法を示すことにします。まず最初に、API を使ってシェーダを作成し、使用する方法を説明します。
ピクセル シェーダの使い方は、頂点シェーダのそれとよく似ています。まず、次に示すように D3DXAssembleShader
を使ってシェーダを作成します。変数 m_pfShader はシェーダ関数定義を含んでいます。結果は ID3DXBuffer
ポインタの pshader に格納されます。
// シェーダの作成
D3DXAssembleShader(m_pfShader ,
strlen(m_pfShader) -1 ,
0 , // フラグ
NULL , // 定数
&pshader ,
&perrors );
次に、 **IDirect3DDevice8::CreatePixelShader
**を使って、pshader バッファ ポインタに含まれている作成済みのピクセル シェーダのデバイス ハンドルを作成し、そのハンドルを m_dhShader ハンドル変数に格納します。
// シェーダ デバイス ハンドルの作成
m_pd3dDevice->CreatePixelShader( (DWORD*)pshader->GetBufferPointer(),
& m_dhShader );
その後、必要に応じて IDirect3DDevice8::SetPixelShader
を使って、デバイスに対してこのピクセル シェーダ デバイス ハンドルを設定します。
// デバイスに対してシェーダを設定
m_pd3dDevice->SetPixelShader(m_dhShader);
次に、Base シェーダ サンプルとその内部の実装を説明しましょう。
BasePS ピクセル シェーダ サンプル
BasePS は、図 4 に示す 4 つの頂点シェーダを、図 3 に示す 10 個のピクセル シェーダへの「フロントエンド」として使用します。頂点シェーダはピクセル シェーダのフロントエンドまたはセットアップ エンジンの役割を果たします。最初のうちは理解しにくいかもしれませんが、頂点シェーダの出力はピクセル シェーダから見た入力に他ならないので、両者の間には直接のつながりがあります。たとえば、ディフューズ カラーおよびスペキュラ カラーと 4 つのテクスチャを使用するピクセル シェーダがある場合、頂点シェーダではこの 2 つのカラーと 4 つのテクスチャ座標のセットを出力として設定する必要があります。心配する必要はありません。この点については後でもう一度触れます。
図2. 使用可能な頂点シェーダ
図3. 使用可能なピクセル シェーダ
これでこのサンプルの機能はわかりましたが、ではこの機能はどのようにして実現されているのでしょうか? 構造上、ピクセル シェーダ サンプルは、オーバーライド可能な関数と内部のミニ API の 2 つの主要機能領域に分割できます。SDK サンプル フレームワークでは、 CD3DAppplication クラスから派生を行い、オーバーライド可能なメソッドのセットを実装する必要があることを思い出してください。内部的にミニ API を使用しているのは、プログラマブル シェーダの使用を制御し、ツリーの構造が見えやすいように複雑な処理を減らすためです。
オーバーライド可能なメソッド
BasePS は CD3DAppplication クラスから派生しており、コンストラクタと、以下に示すオーバーライド可能なメソッドを実装しています。
// コンストラクタ
CMyD3DApplication();
// アプリケーションが作成する 3D シーンのためのオーバーライド可能な関数
HRESULT ConfirmDevice( D3DCAPS8*, DWORD, D3DFORMAT );
HRESULT InitDeviceObjects();
HRESULT DeleteDeviceObjects();
HRESULT RestoreDeviceObjects();
HRESULT InvalidateDeviceObjects();
HRESULT Render();
HRESULT FrameMove();
HRESULT FinalCleanup();
InitDeviceObjects、RestoreDeviceObjects、InvalidateDeviceObjects、 および DeleteDeviceObjects の違いと対応関係を明確にするために、この記事では InitDeviceObjects と DeleteDeviceObjects、RestoreDeviceObjects と InvalidateDeviceObjects の順番で取り上げることにします。Init/Delete はデバイスを完全に作成/破棄するときに呼び出されます。Restore/Invalidate は、デバイスの何らかの側面を変更したか、失われたデバイスを処理する必要があるときにデバイスに対して呼び出されるResetの前後に呼び出されます。
コンストラクタ
次に示す CMyD3DApplication クラスのコンストラクタ メソッドは、いくつかの状態と統計情報フォントを初期化し、シェーダブロック初期化の InitVertexShaders と InitPixelShaders を呼び出して、シェーダ コントロール ミニ API で使用する頂点シェーダおよびピクセル シェーダ ブロックを初期化しています。シェーダ ブロックについては、シェーダ コントロール ミニ API の項で詳しく説明します。
CMyD3DApplication::CMyD3DApplication()
{
m_strWindowTitle = _T("Base-PS: D3D Basic PixelShader Example");
m_bUseDepthBuffer = TRUE;
m_pFont = new CD3DFont(_T("Arial"), 12, D3DFONT_BOLD );
// ストリーム
m_pVBVertexShader0 = NULL;
m_pVBPosColor = NULL;
m_pVBTexC0 = NULL;
m_pVBTexC1 = NULL;
// テクスチャ
m_pTex0 = NULL;
m_pTex1 = NULL;
// シェーダ ブロックの初期化
InitVertexShaders();
InitPixelShaders();
}
ConfirmDevice
次に示す ConfirmDevice ConfirmDevice メソッドは、デバイスが 1.1 の頂点シェーダと 1.1 のピクセル シェーダに対応していることを確認します。つまり、Microsoft(R) DirectX(R) 8.0 グラフィックスを完全にサポートしていないグラフィックス カード上でこのサンプルを実行した場合には、REF デバイスが必要となります。ミックス頂点処理とハードウェア頂点処理の両方をチェックしていることに注意してください。この例ではどちらであってもかまいませんが、現実のプログラマブル シェーダ専用に書かれたアプリケーションでは、ハードウェア頂点処理のみを検出したい場合もあるでしょう。
HRESULT CMyD3DApplication::ConfirmDevice( D3DCAPS8* pCaps,
DWORD dwBehavior,
D3DFORMAT Format )
{
if( (dwBehavior & D3DCREATE_HARDWARE_VERTEXPROCESSING ) ||
(dwBehavior & D3DCREATE_MIXED_VERTEXPROCESSING ) )
{
if( pCaps->VertexShaderVersion < D3DVS_VERSION(1,1) )
return E_FAIL;
}
if( pCaps->PixelShaderVersion < D3DPS_VERSION(1,1) )
return E_FAIL;
return S_OK;
}
InitDeviceObjects
InitDeviceObjects メソッドは、統計情報フォントのためのデバイス オブジェクトと、dx8_logo.bmp (本サンプルに付属) および dx5_logo.bmp (SDKに付属) の 2 つのテクスチャを作成します。
HRESULT CMyD3DApplication::InitDeviceObjects()
{
// フォントの内部テクスチャの初期化
m_pFont->InitDeviceObjects( m_pd3dDevice );
if( FAILED( D3DUtil_CreateTexture( m_pd3dDevice,
_T("dx8_logo.bmp"),&m_pTex0 ) ) )
return D3DAPPERR_MEDIANOTFOUND;
if( FAILED( D3DUtil_CreateTexture( m_pd3dDevice,
_T("dx5_logo.bmp"),&m_pTex1 ) ) )
return D3DAPPERR_MEDIANOTFOUND;
return S_OK;
}
DeleteDeviceObjects
DeleteDeviceObjects メソッドは、 InitDeviceObjects が作成したものを分割します。この例では、上で作成した統計情報フォントのデバイス オブジェクトと、2 つのテクスチャです。実に簡単です。
HRESULT CMyD3DApplication::DeleteDeviceObjects()
{
m_pFont->DeleteDeviceObjects();
SAFE_RELEASE( m_pTex0 );
SAFE_RELEASE( m_pTex1 );
return S_OK;
}
RestoreDeviceObjects
RestoreDeviceObjects メソッドは、いくつかの有用なサービスを実行します。第一 に、統計情報フォントのためのデバイス オブジェクトのリストアを行います。その後、BuildVertexBuffers を使ってデバイス頂点バッファを作成します。次に、シェーダ コントロール ミニ API メソッドの BuildVertexShaders および BuildPixelShaders により、シェーダ ブロックを使ってシェーダの組み立てと作成を行います。最後に、射影行列のようなグローバル トランスフォーム ステートと、z バッファリング ステートのようなグローバル レンダ ステートが、SetViewStates と SetRenderStates を使って設定されます。この 2 つのメソッドは、この記事の内容にはほとんど関係していません。これらの機能に関心がある方は、ダウンロードを参照してください。
HRESULT CMyD3DApplication::RestoreDeviceObjects()
{
// フォントのデバイス オブジェクトのリストア
m_pFont->RestoreDeviceObjects();
// ジオメトリ
BuildVertexBuffers();
// シェーダ
BuildVertexShaders();
BuildPixelShaders();
// 状態
SetViewStates();
SetRenderStates();
return S_OK;
}
InvalidateDeviceObjects
InvalidateDeviceObjects メソッドは、RestoreDeviceObjects によって作成されたオブジェクトを破棄します。つまり、統計情報フォントのデバイス オブジェクトを無効にし、シェーダ コントロール ミニ API メソッドの DestroyVertexBuffers、DestroyVertexShaders、および DestroyPixelShaders を使って、頂点バッファ、頂点シェーダのためのデバイス ハンドル、およびピクセル シェーダのためのデバイス ハンドルを破棄します。
HRESULT CMyD3DApplication::InvalidateDeviceObjects()
{
m_pFont->InvalidateDeviceObjects();
DestroyVertexBuffers();
DestroyVertexShaders();
DestroyPixelShaders();
return S_OK;
}
FrameMove
FrameMove メソッドはきわめて単純です。頂点シェーダを使用するときには、他のコラムで解説している LoadMatrix4 関数により、頂点シェーダに現在のトランスフォーム状態が与えられます。その後、texcoord1 ストリーム データを含んでいる頂点バッファのアニメーションが実行されます。つまり、マルチストリームのマルチテクスチャ シェーダを使用するときには、テクスチャ座標アニメーションが表示されるということです。ただし、この記事の目的(ピクセル シェーダの解説)からするとこの点はそれほど重要ではないので、実装の紹介は省略することにします。詳細については、ダウンロードに含まれているコードを参照してください。
HRESULT CMyD3DApplication::FrameMove()
{
// ワールド行列では、y軸のみを中心としてオブジェクトを回転させる
D3DXMatrixRotationY( &m_matWorld, m_fTime * 2.0f );
//
if ( m_bVertexShader )
{
LoadMatrix4( m_pd3dDevice , 4 ,
m_matWorld * m_matView * m_matProj );
AnimateTexCoord1Stream();
}
return S_OK;
}
Render
Render メソッドはきわめて明解です。ミニ API メソッドの SetVertexProcessing と SetPixelProcessing を呼び出して、それぞれ頂点処理とピクセル処理を制御します。その後、DrawPrimitive と三角形のファンを使ってポストカードが描画されます。最後に、統計情報がレンダリングされます。
vHRESULT CMyD3DApplication::Render()
{
// ビューポートのクリア
m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0,0,128), 1.0f, 0L );
// シーンの開始
if( SUCCEEDED( m_pd3dDevice->BeginScene() ) )
{
// 頂点処理のセットアップ
SetVertexProcessing();
// ピクセル処理のセットアップ( MT, pixel shader )
SetPixelProcessing();
// 実際のレンダリングを行うDrawPrimitive()の呼び出し
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, NUM_TRIS );
// 統計情報の出力
m_pd3dDevice->SetPixelShader( NULL );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,
D3DTOP_SELECTARG1);
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_pFont->DrawText( 2, 40, D3DCOLOR_ARGB(255,255,255,0),
m_strVertexShaderStats );
m_pFont->DrawText( 2, 60, D3DCOLOR_ARGB(255,255,255,0),
m_strPixelShaderStats );
// シーンの終了
m_pd3dDevice->EndScene();
}
return S_OK;
}
FinalCleanup
FinalCleanup メソッドは、単に統計情報フォント オブジェクトを削除します。内部のデバイス オブジェクトの管理は、すべて Init/Delete と Restore/Invalidate のペアの中で行われるためです。
HRESULT CMyD3DApplication::FinalCleanup()
{
SAFE_DELETE( m_pFont );
return S_OK;
}
内部ミニ API
シェーダ コントロール ミニ API は、次に示す SHADERBLOCK
構造体を使用して、シェーダ名、宣言、関数、および結果として得られたデバイス ハンドルを格納します。宣言ポインタが含まれているということは、この構造体を頂点シェーダとピクセル シェーダの両方に使用できるということを意味します。どちらのシェーダも関数を持っていますが、明示的な宣言を持つのは頂点シェーダだけです。
// シェーダ作成情報を格納するためのシェーダ ブロック
typedef struct t_SHADERBLOCK
{
TCHAR m_szShader[80]; // 名前
DWORD* m_pdDecl; // 宣言ポインタ
const char* m_pfShader; // 関数ポインタ
DWORD m_dhShader; // デバイス ハンドル
} SHADERBLOCK;
シェーダ コントロール ミニ API は、頂点シェーダとピクセル シェーダの両方に使用できる 4 つの部分を含んでいます。
- 初期化フェーズ。
SHADERBLOCK
構造体に宣言と関数へのポインタをロード。 - 作成フェーズ。シェーダ デバイス ハンドルを組み立て、作成。
- 設定フェーズ。シェーダで使用するためにデバイスの設定を行う。
- 破棄フェーズ。シェーダ ハンドルをデバイスから削除。
各フェーズについて、両方のメソッドを順番に説明していきます。頂点バッファの作成と破棄のメソッドはあまり関係がないので、ここでは扱いません。これらの操作に関して疑問がある方は、ダウンロードに含まれているコードを参照してください。
// シェーダ コントロール ミニ API ヘルパー メソッド
HRESULT InitVertexShaders();
HRESULT BuildVertexShaders();
HRESULT SetVertexProcessing();
HRESULT DestroyVertexShaders();
HRESULT InitPixelShaders();
HRESULT BuildPixelShaders();
HRESULT SetPixelProcessing();
HRESULT DestroyPixelShaders();
InitVertexShader
ヘルパーの InitVertexShader は、まずグローバル頂点シェーダ状態を、シェーダが使用されていないことを示す値に設定します。その後、4 つの頂点シェーダすべてについて、SHADERBLOCK
要素を初期化し、ハードコードされた名前をコピーし、宣言ポインタでこの頂点シェーダの宣言ブロックを初期化し、さらにシェーダ関数ポインタが正しい頂点シェーダ関数をポイントするように初期化します。ここではスペースを節約するために、頂点シェーダ SHADERBLOCK
配列の最初のメンバのみを示しています。これらの要素を初期化したら、すべての頂点シェーダ デバイス ハンドルをループ処理によって未初期化の値に設定します。最後に、頂点シェーダ統計情報の文字列を初期化します。
HRESULT CMyD3DApplication::InitVertexShaders()
{
// 頂点シェーダの状態
m_bVertexShader = FALSE;
m_curVertexShader = 0;
// 頂点シェーダの名前と関数ポインタ
lstrcpy(m_sbVertexShaders[0].m_szShader,"DiffuseSSVertexShader0", );
m_sbVertexShaders[0].m_pdDecl = dwDecl0;
m_sbVertexShaders[0].m_pfShader = DiffuseSSVertexShader0;
// 頂点シェーダ1~3は、スペースの節約のために省略……
// 頂点シェーダ ハンドル
for ( int i = 0; i < CUR_VSHADERS; i++ )
{
m_sbVertexShaders[i].m_dhShader = 0xffffffff;
}
// 頂点シェーダ統計情報
_stprintf( m_strVertexShaderStats, m_bVertexShader? _
T("%s %d"):_T("%s"),
m_bVertexShader?_T("using vertex shader "):_
T("using FF vertex pipeline"),
m_curVertexShader);
return S_OK;
}
InitPixelShader
ヘルパーの InitPixelShader では、まずグローバル ピクセル シェーダ状態を、シェーダが使用されていないことを示す値に設定します。その後、10 個のピクセル シェーダすべてについて、SHADERBLOCK
要素が初期化され、ハードコードされた名前がコピーされ、、シェーダ関数ポインタが正しいピクセル シェーダ関数をポイントするように初期化されます。ここではスペースを節約するために、ピクセル シェーダ SHADERBLOCK
配列の最初のメンバのみを示しています。これらの要素が初期化されたら、使用されないシェーダ宣言ポインタが NULL に設定され、すべてのピクセル シェーダ デバイス ハンドルがループ処理によって未初期化の値に設定されます。最後に、ピクセル シェーダ統計情報の文字列が初期化されます。
HRESULT CMyD3DApplication::InitPixelShaders()
{
// ピクセル シェーダの状態
m_bPixelShader = FALSE;
m_curPixelShader = 0;
// ピクセル シェーダの名前と関数ポインタ
lstrcpy(m_sbPixelShaders[0].m_szShader,
"STDiffuseColorPixelShader0", );
m_sbPixelShaders[0].m_pfShader = STDiffuseColorPixelShader0;
// ピクセル シェーダ1~10は、スペースの節約のために省略……
// ピクセル シェーダ ハンドル
for ( int i = 0; i < CUR_PSHADERS; i++ )
{
m_sbPixelShaders[i].m_pdDecl = 0;
m_sbPixelShaders[i].m_dhShader = 0xffffffff;
}
// ピクセル シェーダ統計情報
_stprintf( m_strPixelShaderStats,
m_bPixelShader? _T("%s %d"):_T("%s"),
m_bPixelShader?_T("using pixel shader "): _T("using MT pixel pipeline"),
m_curPixelShader);
return S_OK;
}
BuildVertexShaders
シェーダ ヘルパー メソッドの BuildVertexShaders を見ると、SHADERBLOCK
構造体の有効性を理解することができます。ここでは、DirectX シェーダ API が大きなループの中で使用されていますが、このループでは SHADERBLOCK
の要素が取り出され、まず組み立てのために D3DXAssembleShader
に渡され、次に頂点シェーダ デバイス ハンドルを作成するために CreateVertexShader
に渡されます。以前は、個々のシェーダを D3DXAssembleShader
と CreateVertexShader
の呼び出しの中で個別に作成しなくてはならず、保守が大変でした。また、頂点シェーダの数が増えたときに対応するのが困難でした。
HRESULT CMyD3DApplication::BuildVertexShaders()
{
HRESULT rc;
TCHAR szBuffer[128];
TCHAR szBuffer2[256];
ID3DXBuffer* perrors;
ID3DXBuffer* pshader;
// シェーダ作成ループ
for ( int i = 0; i<CUR_VSHADERS; i++)
{
// シェーダの組み立て
rc = D3DXAssembleShader( m_sbVertexShaders[i].m_pfShader ,
strlen(m_sbVertexShaders[i].m_pfShader) -1 ,
0 , // フラグ
NULL , // 定数
&pshader ,
&perrors );
if ( FAILED(rc) )
{
sprintf(szBuffer,"Failed to assemble %s\n",
m_sbVertexShaders[i].m_szShader);
OutputDebugString( szBuffer);
sprintf(szBuffer,"\terrors: %s\n",
(char*)perrors->GetBufferPointer() );
OutputDebugString( szBuffer);
goto next;
}
// シェーダ デバイス ハンドルの作成
rc = m_pd3dDevice->CreateVertexShader(
m_sbVertexShaders[i].m_pdDecl,
(DWORD*)pshader->GetBufferPointer(),
&m_sbVertexShaders[i].m_dhShader,0 );
if ( FAILED(rc) )
{
sprintf(szBuffer,"Failed to create %s\n",
m_sbVertexShaders[i].m_szShader );
OutputDebugString( szBuffer);
D3DXGetErrorStringA(rc,szBuffer2,sizeof(szBuffer2) );
sprintf(szBuffer,"\terrors: %s\n", szBuffer2 );
OutputDebugString( szBuffer );
}
SAFE_RELEASE(pshader);
next:
SAFE_RELEASE(perrors);
}
//
if ( FAILED(rc) )
return rc;
else
return S_OK;
}
BuildPixelShaders
シェーダ ヘルパー メソッド BuildPixelShaders では、BuildVertexShaders と同じ面での進歩が見られ、SHADERBLOCK
構造体が同じように有効に使われています。ここでは、DirectX シェーダ API が大きなループの中で使用されていますが、このループでは SHADERBLOCK
の要素を取り出し、まず組み立てのために D3DXAssembleShader
に渡し、次にピクセル シェーダ デバイス ハンドルを作成するために CreatePixelShader
に渡します。この処理はきわめてスムーズであり、ピクセル シェーダの作成方法が単純化されています。
HRESULT CMyD3DApplication::BuildPixelShaders()
{
HRESULT rc;
TCHAR szBuffer[128];
TCHAR szBuffer2[256];
ID3DXBuffer* pshader;
ID3DXBuffer* perrors;
// シェーダ作成ループ
for ( int i = 0; i< CUR_PSHADERS; i++)
{
// シェーダの組み立て
rc = D3DXAssembleShader( m_sbPixelShaders[i].m_pfShader ,
strlen(m_sbPixelShaders[i].m_pfShader) -1 ,
0 , // フラグ
NULL , // 定数
&pshader ,
&perrors );
if ( FAILED(rc) )
{
sprintf(szBuffer,"Failed to assemble %s\n",
m_sbPixelShaders[i].m_szShader);
OutputDebugString( szBuffer);
sprintf(szBuffer,"\terrors: %s\n",
(char*)perrors->GetBufferPointer() );
OutputDebugString( szBuffer);
goto next;
}
// シェーダ デバイス ハンドルの作成
rc = m_pd3dDevice->CreatePixelShader(
(DWORD*)pshader->GetBufferPointer(),
&m_sbPixelShaders[i].m_dhShader );
if ( FAILED(rc) )
{
sprintf(szBuffer,"Failed to create %s\n",
m_sbPixelShaders[i].m_szShader );
OutputDebugString( szBuffer);
D3DXGetErrorStringA(rc,szBuffer2,sizeof(szBuffer2) );
sprintf(szBuffer,"\terrors: %s\n", szBuffer2 );
OutputDebugString( szBuffer );
}
SAFE_RELEASE(pshader);
next:
SAFE_RELEASE(perrors);
}
//
if ( FAILED(rc) )
return rc;
else
return S_OK;
}
SetVertexProcessing
SetVertexProcessing は、頂点処理状態の設定をサポートします。頂点シェーダ ハンドルを設定し、頂点バッファをストリーム ソースとして設定し、いくつかの頂点シェーダ定数を設定します。これを個々の頂点シェーダについて行います。
HRESULT CMyD3DApplication::SetVertexProcessing()
{
if ( !m_bVertexShader )
{
m_pd3dDevice->SetStreamSource( 0, m_pVBVertexShader0, sizeof(CUSTOMVERTEX) );
m_pd3dDevice->SetVertexShader( D3DFVF_CUSTOMVERTEX );
}
else
{
// シェーダの設定
float color[4] = {0,1,0,0};
m_pd3dDevice->SetVertexShaderConstant( 8 , color , 1 );
// シェーダの設定
m_pd3dDevice->SetVertexShader( m_sbVertexShaders[m_curVertexShader].m_dhShader );
// ストリームの設定
switch(m_curVertexShader)
{
case(0):
m_pd3dDevice->SetStreamSource( 0, m_pVBVertexShader0, sizeof(CUSTOMVERTEX) );
m_pd3dDevice->SetStreamSource( 1, NULL,0);
m_pd3dDevice->SetStreamSource( 2, NULL,0);
break;
case(1):
m_pd3dDevice->SetStreamSource( 0, m_pVBPosColor, sizeof(POSCOLORVERTEX) );
m_pd3dDevice->SetStreamSource( 1, m_pVBTexC0, sizeof(TEXC0VERTEX) );
m_pd3dDevice->SetStreamSource( 2, NULL,0);
break;
case(2):
m_pd3dDevice->SetStreamSource( 0, m_pVBVertexShader0, sizeof(CUSTOMVERTEX) );
m_pd3dDevice->SetStreamSource( 1, NULL, 0);
m_pd3dDevice->SetStreamSource( 2, NULL, 0);
break;
case(3):
m_pd3dDevice->SetStreamSource( 0, m_pVBPosColor, sizeof(POSCOLORVERTEX) );
m_pd3dDevice->SetStreamSource( 1, m_pVBTexC0, sizeof(TEXC0VERTEX) );
m_pd3dDevice->SetStreamSource( 2, m_pVBTexC1, sizeof(TEXC1VERTEX) );
break;
}
}
return S_OK;
}
SetPixelProcessing
SetPixelProcessing の実装は単純です。まず、ピクセル シェーダが使用可能である場合には、SetPixelShader
を使って現在のピクセル シェーダを設定します。その後、現在のピクセル シェーダに応じてテクスチャ状態が設定されます。つまり、テクスチャ ステージにどれだけの有効なテクスチャがバインドされているかが設定されます。これは SetTexture を使って行われます。テクスチャ ステージは、tex tn 命令を使って texel をピクセル シェーダ テクスチャ レジスタにロードするテクスチャ サンプラをフィードすることに注意してください。ここでは、下流のピクセル シェーダが要求する頂点シェーダの要件、少なくともテクスチャの点での要件が反映されています。ピクセル シェーダが要求するカラー入力の要件はいくぶんわかりにくいのですが、この例からどうやって探せばいいのかがわかるはずです。また、ピクセル シェーダが使用可能でない場合には固定機能のマルチテクスチャ パイプラインをセットアップしていますが、この記事にはあまり関係がありません。
HRESULT CMyD3DApplication::SetPixelProcessing()
{
if ( m_bPixelShader )
{
// シェーダ
m_pd3dDevice->SetPixelShader( m_sbPixelShaders[m_curPixelShader].m_dhShader );
// テクスチャ状態
switch(m_curPixelShader)
{
case 0:
case 1:
m_pd3dDevice->SetTexture(0,NULL);
m_pd3dDevice->SetTexture(1,NULL);
break;
case 2:
case 4:
case 5:
m_pd3dDevice->SetTexture(0,m_pTex0);
m_pd3dDevice->SetTexture(1,NULL);
break;
case 3:
case 6:
case 7:
case 8:
case 9:
case 10:
m_pd3dDevice->SetTexture(0,m_pTex0);
m_pd3dDevice->SetTexture(1,m_pTex1);
break;
}
}
else //!m_bPixelShader
{
if ( !m_bVertexShader )
{
// ffではディフューズのみ
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 );
m_pd3dDevice->SetTexture(0,NULL);
m_pd3dDevice->SetTexture(1,NULL);
}
else
{
// ステージ状態
switch(m_curVertexShader)
{
case(0):
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 );
m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE);
m_pd3dDevice->SetTexture(0,NULL);
m_pd3dDevice->SetTexture(1,NULL);
break;
case(1):
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );
m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE);
m_pd3dDevice->SetTexture(0,m_pTex0);
m_pd3dDevice->SetTexture(1,NULL);
break;
case(2):
case(3):
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );
m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_ADDSIGNED );
m_pd3dDevice->SetTexture(0,m_pTex0);
m_pd3dDevice->SetTexture(1,m_pTex1);
break;
}
}
}
return S_OK;
}
DestroyVertexShader
DestroyVertexShader は、頂点シェーダ SHADERBLOCK
配列の中の頂点シェーダ デバイス ハンドルをループ処理して DeleteVertexShader を呼び出し、デバイスから頂点シェーダ ハンドルを削除します。その後、ハンドルは未初期化の状態に設定されます。
HRESULT CMyD3DApplication::DestroyVertexShaders()
{
for ( int i = 0; i < CUR_VSHADERS; i++ )
{
if ( m_sbVertexShaders[i].m_dhShader != 0xffffffff )
{
m_pd3dDevice->DeleteVertexShader(
m_sbVertexShaders[i].m_dhShader );
m_sbVertexShaders[i].m_dhShader = 0xffffffff;
}
}
return S_OK;
}
DestroyPixelShader
DestroyVertexShader のミラーとして機能する DestroyPixelShader は、ピクセル シェーダ SHADERBLOCK
配列の中のピクセル シェーダ デバイス ハンドルをループ処理して DeletePixelShader 呼び出し、デバイスからピクセル シェーダ ハンドルを削除します。その後、ハンドルは未初期化の状態に設定されます。
HRESULT CMyD3DApplication::DestroyPixelShaders()
{
for ( int i = 0; i < CUR_PSHADERS; i++ )
{
if ( m_sbPixelShaders[i].m_dhShader != 0xffffffff )
{
m_pd3dDevice->DeletePixelShader(
m_sbPixelShaders[i].m_dhShader );
m_sbPixelShaders[i].m_dhShader = 0xffffffff;
}
}
return S_OK;
}
これでサンプルの基本構造と、その動作の仕組みについての解説が終わりました。次はシェーダそのものを取り上げます。まず、頂点シェーダについて説明します。次に、頂点シェーダとピクセル シェーダの関係を理解する必要があります。最後に、ピクセル シェーダそのものを詳しく説明し、それぞれが実装しているブレンディング式について解説します。
頂点シェーダと頂点パイプライン
DirectX シェーダ API の使い方については、シェーダ コントロール ミニ API の項で説明しました。後は以下の事柄を理解すれば、このサンプルで使用されている頂点シェーダを理解することができます。
- 頂点定義
- シェーダ宣言
- シェーダ定義
頂点定義
このサンプルは、2 つの頂点定義のセットを使用します。
- すべてのコンポーネントを含んでいる単一ストリーム頂点。
- 3 ストリーム頂点。第一のストリームは位置、ディフューズ カラー、およびスペキュラ カラーを含んでおり、第二のストリームはテクスチャ座標セット 1 を含んでおり、第三のストリームはテクスチャ座標セット 2 を含んでいます。
// 単一ストリーム頂点
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD color1, color2;
FLOAT tu1, tv1;
FLOAT tu2, tv2;
};
// 頂点シェーダ 0 の頂点構造体を記述するカスタム FVF
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_SPECULAR|D3DFVF_TEX2)
// マルチストリーム頂点
// ストリーム 0、位置、ディフューズ、スペキュラ
struct POSCOLORVERTEX
{
FLOAT x, y, z;
DWORD color1, color2;
};
#define D3DFVF_POSCOLORVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_SPECULAR)
// ストリーム 1、テクスチャ座標0
struct TEXC0VERTEX
{
FLOAT tu1, tv1;
};
#define D3DFVF_TEXC0VERTEX (D3DFVF_TEX1)
// ストリーム1、テクスチャ座標1
struct TEXC1VERTEX
{
FLOAT tu2, tv2;
};
#define D3DFVF_TEXC1VERTEX (D3DFVF_TEX0)
これで、CUSTOMVERTEX
と POSCOLORVERTEX+TEXC0VERTEX+TEXC1VERTEX
の 2 つの頂点タイプを定義したことになりますが、この両方が 4 頂点シェーダで使用されます。頂点定義には構造体、FVF コード、および初期化済みの頂点が含まれていますが、四角形のための頂点の初期化はこの記事には関係ないので、構造体と FVF のコードのみを示しています。
次は、シェーダ宣言について説明します。
シェーダ宣言
シェーダ宣言は、SetStreamSource を使用してマッピングされた入力頂点コンポーネントと頂点入力レジスタの間のバインディングを提供します。次に 4 つの頂点シェーダ宣言を示します。最初のものは、CUSTOMVERTEX タイプに対応する単一ストリーム バインディングを提供します。
// シェーダ宣言
float c[4] = {0.0f,0.5f,1.0f,2.0f};
// ディフューズ - 単一ストリーム
DWORD dwDecl0[] =
{
D3DVSD_STREAM(0),
D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ),
D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR ),
D3DVSD_REG(D3DVSDE_SPECULAR, D3DVSDT_D3DCOLOR ),
D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
*(DWORD*)&c[2],*(DWORD*)&c[3],
D3DVSD_END()
};
次の 3 つは複数のストリームを必要とし、 POSCOLORVERTEX、TEXC0VERTEX、および TEXC1VERTEX タイプを使用します。第二のものは単一テクスチャのマルチストリーム シェーダを定義し、POSCOLORVERTEX および TEXC0VERTEX タイプを使用します。第3のものはマルチテクスチャの単一ストリーム シェーダを定義し、結合型の CUSTOMVERTEX タイプを使用します。最後の宣言はマルチ テクスチャのマルチストリーム シェーダを定義し、POSCOLORVERTEX、TEXC0VERTEX、および TEXC1VERTEX タイプを使用します。
// 単一テクスチャ - マルチストリーム
DWORD dwDecl1[] =
{
D3DVSD_STREAM(0),
D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ),
D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR ),
D3DVSD_REG(D3DVSDE_SPECULAR, D3DVSDT_D3DCOLOR ),
D3DVSD_STREAM(1),
D3DVSD_REG(D3DVSDE_TEXCOORD0,D3DVSDT_FLOAT2 ),
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 ),
D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR ),
D3DVSD_REG(D3DVSDE_SPECULAR, D3DVSDT_D3DCOLOR ),
D3DVSD_REG(D3DVSDE_TEXCOORD0,D3DVSDT_FLOAT2 ),
D3DVSD_REG(D3DVSDE_TEXCOORD1,D3DVSDT_FLOAT2 ),
D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
*(DWORD*)&c[2],*(DWORD*)&c[3],
D3DVSD_END()
};
// マルチテクスチャ - マルチストリーム
DWORD dwDecl3[] =
{
D3DVSD_STREAM(0),
D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3 ),
D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR ),
D3DVSD_REG(D3DVSDE_SPECULAR, D3DVSDT_D3DCOLOR ),
D3DVSD_STREAM(1),
D3DVSD_REG(D3DVSDE_TEXCOORD0,D3DVSDT_FLOAT2 ),
D3DVSD_STREAM(2),
D3DVSD_REG(D3DVSDE_TEXCOORD1,D3DVSDT_FLOAT2 ),
D3DVSD_CONST(0,1),*(DWORD*)&c[0],*(DWORD*)&c[1],
*(DWORD*)&c[2],*(DWORD*)&c[3],
D3DVSD_END()
};
次は頂点シェーダ関数について説明します。
シェーダ関数定義
このサンプルには 4 つのシェーダがあるので、頂点定義とシェーダ宣言に対応する 4 つのシェーダ定義が含まれていなくてはなりません。これらについて簡単に説明しておきます。
DiffuseSSVertexShader0
は単に m4x4
命令を使って 4x4 行列のトランスフォームを実行し、結果を oPos
出力レジスタにコピーし、その後 mov 命令を使ってディフューズ カラーとスペキュラ カラーをそれぞれ oD0 と oD1 にコピーします。
const char DiffuseSSVertexShader0[] =
"vs.1.1 // シェーダ バージョン1.1\n"\
"m4x4 oPos , v0 , c4 // 射影位置の発行 \n"\
"mov oD0 , v5 // ディフューズ カラー = 頂点カラー0 \n"\
"mov oD1 , v6 // スペキュラ カラー = 頂点カラー1 \n";
単一テクスチャ シェーダの **STMSVertexShader1
**は、まず m4x4
命令を使って 4x4 行列のトランスフォームを実行します。その後、やはり結果を oPos
出力レジスタにコピーし、その後 mov 命令を使ってディフューズ カラーとスペキュラ カラーをそれぞれ oD0 と oD1 にコピーします。最後に、やはり mov 命令を使ってテクスチャ座標を oTO に出力します。
const char STMSVertexShader1[] =
"vs.1.1 // シェーダ バージョン1.1\n"\
"m4x4 oPos , v0 , c4 // 射影位置の発行 \n"\
"mov oD0 , v5 // ディフューズ カラー = 頂点カラー0 \n"\
"mov oD1 , v6 // スペキュラ カラー = 頂点カラー1 \n"\
"mov oT0.xy , v7 // テクスチャ座標のコピー \n";
マルチテクスチャ シェーダの MTSSVertexShader2
は、まず m4x4
命令を使って 4x4 行列のトランスフォームを実行します。その後、やはり結果を oPos
出力レジスタにコピーし、その後 mov 命令を使ってディフューズ カラーとスペキュラ カラーをそれぞれ oD0 と oD1 にコピーします。最後に、やはり mov 命令を使って 1 つのテクスチャ座標を oTO に、もう 1 つのテクスチャ座標を oT1 に出力します。これは単一ストリーム宣言にバインドされています。
const char MTSSVertexShader2[] =
"vs.1.1 // シェーダ バージョン1.1 \n"\
"m4x4 oPos , v0 , c4 // 射影位置の発行 \n"\
"mov oD0 , v5 // ディフューズ カラー = 頂点カラー0 \n"\
"mov oD1 , v6 // スペキュラ カラー = 頂点カラー1 \n"\
"mov oT0.xy , v7 // テクスチャ座標のコピー \n"\
"mov oT1.xy , v8 // テクスチャ座標のコピー \n";
マルチテクスチャ シェーダの MTMSVertexShader3
は、まず m4x4
命令を使って 4x4 行列のトランスフォームを実行します。その後、やはり結果を oPos
出力レジスタにコピーし、その後 mov 命令を使ってディフューズ カラーとスペキュラ カラーをそれぞれ oD0 と oD1 にコピーします。最後に、やはり mov 命令を使って 1 つのテクスチャ座標を oTO に、もう1つのテクスチャ座標を oD1 に出力します。これは単一ストリーム宣言にバインドされています。これはマルチストリーム宣言にバインドされています。
const char MTMSVertexShader3[] =
"vs.1.1 // シェーダ バージョン1.1 \n"\
"m4x4 oPos , v0 , c4 // 射影位置の発行 \n"\
"mov oD0 , v5 // ディフューズ カラー = 頂点カラー0 \n"\
"mov oD1 , v6 // スペキュラ カラー = 頂点カラー1 \n"\
"mov oT0.xy , v7 // テクスチャ座標のコピー\n"\
"mov oT1.xy , v8 // テクスチャ座標のコピー\n";
頂点シェーダ関数の定義を見たことで、サンプル内での頂点シェーダの機能が理解できたと思います。次は、頂点シェーダとピクセル シェーダの関係について説明します。
頂点およびピクセル パイプラインの関係
次の図 4 は,DiffuseSSVertexShader0
シェーダが選択されたときに、[Pixel Shader] メニューで使用可能となるピクセル シェーダを示しています。このシェーダからの出力はカラー値だけなので、当然ながらディフューズ シェーダとスペキュラ シェーダーのみが使用可能になっています(c0 と c1).
図4. 頂点シェーダ 0 で使用可能なピクセル シェーダ。カラー コンポーネントのみがマッピングされる
図5. 頂点シェーダ 1 で使用可能なピクセル シェーダ。カラー コンポーネントとテクスチャ コンポーネントがマッピングされる
図6. 頂点シェーダ 2 および 3 おで使用可能なピクセル シェーダ。すべてのコンポーネントがマッピングされる
次の図5は、STMSVertexShader1
シェーダが選択されたときに [Pixel Shader] メニューで使用可能となるピクセル シェーダを示しています。このシェーダからの出力値はカラー値と単一テクスチャ座標なので、当然ながらカラー シェーダ(ディフューズとスペキュラ、または c0 と c1) および単一テクスチャ シェーダ(テクスチャ 0、t0 を使用)のみが使用可能となっています。
次の図6は、**MTSSVertexShader2
**シェーダまたは **MTSSVertexShader3
**シェーダが選択されたときに [Pixel Shader] メニューで使用可能となるピクセル シェーダを示しています。このシェーダからの出力値はカラー値と単一テクスチャ座標なので、これも当然の結果です。
ピクセル シェーダとピクセル パイプライン
次はピクセル シェーダ関数と、これにエンコードされているブレンディング式について説明します。個々のシェーダについて、シェーダの説明を行い、ブレンディング式を示し、シェーダのコードを示し、画面ショットを紹介します。ブレンディング式では次の表記を使用します。
- c0 == ディフューズ カラー
- c1 == スペキュラ カラー
- t0 == テクスチャ1
- t1 == テクスチャ2
これは、上の頂点シェーダとピクセル シェーダの関係を示す図4~6の[Pixel Shader]メニューで使用されている表記と同じです。
STDiffuseColorPixelShader0
は考えうる最も単純なピクセル シェーダです。これはディフューズ カラーを r0
出力レジスタにコピーします。使用されるブレンディング式は次のとおりです。
r0 = c0
図 7 は STDiffuseColorPixelShader0
シェーダの画面ショットです。ディフューズ カラーはポストカードの表面の単純な RGB カラー ランプです。このシェーダのコードを次に示します。
const char STDiffuseColorPixelShader0[] =
"// ディフューズのみ \n"\
"ps.1.1 \n"\
"mov r0, v0 \n";// c0
STSpecularColorPixelShader1
は、その次に単純なピクセル シェーダです。これはスペキュラ カラーを r0
出力レジスタにコピーします。使用されるブレンディング式は次のとおりです。
r0 = c1
図 8 は **STSpecularColorPixelShader1
**シェーダの画面ショットです。スペキュラ カラーはポストカードの表面のグレイスケールのグラデーションです。このシェーダのコードを次に示します。
const char STSpecularColorPixelShader1[] =
"// スペキュラのみ \n"\
"ps.1.1 \n"\
"mov r0, v1 \n";// c1
図7. ピクセル シェーダ 0 - ディフューズ カラー
図8. ピクセル シェーダ 1 - スペキュラ カラー
STDecalT0PixelShader2
は、テクスチャを使用する最も単純なピクセル シェーダです。これはテクスチャ カラーをテクスチャ t0 から r0
出力レジスタにコピーします。使用されるブレンディング式は次のとおりです。
r0 = t0
図 9 は **STDecalT0PixelShader2
**シェーダの画面ショットです。テクスチャ t0 は DirectX 8.0 のロゴです。次にシェーダ命令を示します。
const char STDecalT0PixelShader2[] =
"// テクスチャのみ \n"\
"ps.1.1 \n"\
"tex t0 \n"\
"mov r0, t0 \n";// t0
STDecalT1PixelShader3
は、テクスチャを使用する次に単純なピクセル シェーダです。これはテクスチャ カラーをテクスチャ t1 から r0
出力レジスタにコピーします。使用されるブレンディング式は次のとおりです。
r0 = t1
図 10 は STDecalT1PixelShader3
シェーダの画面ショットです。テクスチャ t1 は DirectX 5.0 のロゴです。次にこのシェーダ関数の命令を示します。
const char STDecalT1PixelShader3[] =
"// テクスチャのみ \n"\
"ps.1.1 \n"\
"tex t0 \n"\
"tex t1 \n"\
"mov r0, t1 \n";// t1
STDiffuseModPixelShader4
は、4 つの入力 (c0、c1、t0、t1) のうちの 1 つをコピーするという以外の処理を行う最初のシェーダです。このシェーダは、テクスチャ カラーをディフューズ カラーによってモジュレートします。使用されるブレンディング式は次のとおりです。
r0 = c0*t0
図 11 は STDiffuseModPixelShader4
シェーダの画面ショットです。カラー ランプが使用されていることを示す、テクスチャの色の違いに注目してください。次にこのシェーダの命令を示します。
const char STDiffuseModPixelShader4[] =
"// テクスチャとディフューズの乗算 \n"\
"ps.1.1 \n"\
"tex t0 \n"\
"mul r0, v0, t0 \n";// c0*t0
図9. ピクセル シェーダ 2 - 単一テクスチャ t0
図10. ピクセル シェーダ 3 - 単一テクスチャ t1
図11. ピクセル シェーダ 4 - ディフューズ モジュレーション テクスチャ
STSpecularModPixelShader5
は STDiffuseModPixelShader4
に似ていますが、テクスチャ カラーのモジュレートにスペキュラを使用します。使用されるブレンディング式は次のとおりです。
r0 = c1*t0
const char STSpecularModPixelShader5[] =
"// テクスチャとスペキュラの乗算 \n"\
"ps.1.1 \n"\
"tex t0 \n"\
"mul r0, v1, t0 \n";// c1*t0
図 12 は STSpecularModPixelShader5
シェーダの画面ショットです。グレイスケールのグラデーションが使用されていることを示す、テクスチャの濃淡に注目してください。次にこのシェーダの命令を示します。
MTDiffuseBlendPixelShader6
は、マルチテクスチャ ブレンドを実行する最初のシェーダです。ディフューズ値を使用して、2 つの入力テクスチャの間で線形補間を行っています。使用されるブレンディング式は次のとおりです。
r0 = c0*t0+(1-c0)*t1
図 13 は MTDiffuseBlendPixelShader6
シェーダの画面ショットです。2つのテクスチャの外見と、カラー ランプの使用を示す色の違いに注目してください。次にこのシェーダの命令を示します。
const char MTDiffuseBlendPixelShader6[] =
"// ディフューズと t0 と t1 のブレンド\n"\
"ps.1.1 \n"\
"tex t0 \n"\
"tex t1 \n"\
"mov r1, t1 \n"\
"lrp r0, v0, t0, r1 \n"; // c0*t0+(1-c0)*t1
図12. ピクセル シェーダ 5 - スペキュラとテクスチャ 0 の乗算
図13. ピクセル シェーダ 6 - c0 を使用した t0 と t1 の線形補間
図14. ピクセル シェーダ 7 - c1 を使用した t0 と t1 の線形補間
MTSpecularBlendPixelShader7
は MTDiffuseBlendPixelShader6
に似ていますが、補間の制御にスペキュラを使用します。使用されるブレンディング式は次のとおりです。
r0 = c1*t0+(1-c1)*t1
図 14 は **MTSpecularBlendPixelShader7
**シェーダの画面ショットです。2 つのテクスチャで異なるブレンディングが行われていること、またdx5_logo.bmpテクスチャの "DirectX" が色付きではなく灰色に見えることに注目してください。これはブレンディングの制御にグレイスケールのグラデーションが使用されていることを示しています。次にこのシェーダの命令を示します。
const char MTSpecularBlendPixelShader7[] =
"// スペキュラと t0 と t1 のブレンド\n"\
"ps.1.1 \n"\
"tex t0 \n"\
"tex t1 \n"\
"mov r1, t1 \n"\
"lrp r0, v1, t0, r1 \n"; // c1*t0+(1-c1)*t1
MTDiffuseScalePixelShader8
は、ディフューズ カラーを使ってテクスチャ カラーのスケーリングを行い、第二 のテクスチャ カラーを加えます。このシェーダは、_x2 修飾子を使って結果を明るくしています。使用されるブレンディング式は次のとおりです。
r0 = c0*t0+t1
図 15 は **MTDiffuseScalePixelShader8
**シェーダの画面ショットです。2 つのテクスチャで異なるブレンディングが行われていること、また dx5_logo.bmp テクスチャの "DirectX" に色が付いていることに注目してください。これはブレンディングの制御にカラー ランプが使用されていることを示しています。次にこのシェーダの命令を示します。
const char MTDiffuseScalePixelShader8[] =
"// t0 + ( t1*c0) \n"\
"ps.1.1 \n"\
"tex t0 \n"\
"tex t1 \n"\
"mov r1, t0 \n"\
"mad_x2 r0, t1, v0, r1 \n";// t1 + ( t0*c0)
MTSpecularScalePixelShader9
は、スペキュラ カラーを使ってテクスチャ カラーのスケーリングを行い、第二 のテクスチャ カラーを加えます。このシェーダも、_x2 修飾子を使って結果を明るくしています。使用されるブレンディング式は次のとおりです。
r0 = c1*t0+t1
図 16 は **MTSpecularScalePixelShader9
**シェーダの画面ショットです。2 つのテクスチャで異なるブレンディングが行われていること、また dx5_logo.bmp テクスチャの "DirectX" が白色になっていることに注目してください。これはブレンディングの制御にグレイスケール グラディエントが使用されていることを示しています。次にこのシェーダの命令を示します。
const char MTSpecularScalePixelShader9[] =
"// t0 + ( t1*c1) \n"\
"ps.1.1 \n"\
"tex t0 \n"\
"tex t1 \n"\
"mov r1, t0 \n"\
"mad_x2 r0, t1, v1, r1 \n";// t1 + ( t0*c1)
図15. ピクセル シェーダ 8 - ディフューズで t0 をスケーリングし、t1 を加える
図16. ピクセル シェーダ 9 - スペキュラで t0 をスケーリングし、t1 を加える
MTBothScalePixelShader10
は、ディフューズ カラーを使ってテクスチャ t0 を、スペキュラ カラーを使ってテクスチャt1をスケーリングし、2 つのスケーリングされた項を足しあわせます。複数のカラーとテクスチャを使用すると、結果は非常に暗くなることが多いため、このシェーダは _x4 修飾子を使って結果を明るくしています。使用されるブレンディング式は次のとおりです。
r0 = c1*t0+c1*t1
図 17 は **MTBothScalePixelShader10
**シェーダの画面ショットです。2 つのテクスチャで異なるブレンディングが行われていること、また dx5_logo.bmp テクスチャの "DirectX" が白色で、DX8_logo.bmp の "DirectX" に色が付いていることに注目してください。これは、ブレンディングの制御に両方のカラー入力が使用されていることを示しています。次にこのシェーダの命令を示します。
const char MTBothScalePixelShader10[] =
"// (c0+t0)+(c1+t1) \n"\
"ps.1.1 \n"\
"tex t0 \n"\
"tex t1 \n"\
"mul_x4 r0, v0, t0 \n"\
"mul_x4 r1, v1, t1 \n"\
"add r0, r0, r1 \n"; // (c0*t0)+(c1*t1)
図17. ピクセル シェーダ 10 - t0 をディフューズ、t1をスペキュラでスケーリングし、足し合わせる
図18. ピクセル シェーダ 10 - アニメーション付きのテクスチャ座標を可能にするマルチストリーム
追加のボーナスとして、図 18 は同じシェーダで、マルチストリーム、マルチテクスチャの頂点シェーダを使用した例です。この例では、ストリーム2のテクスチャ座標(テクスチャ 2、t1 のテクスチャ座標に対応)がアニメーションされます。これは複数ストリームの使い方の例であり、頂点シェーダとピクセル シェーダの強い結び付きの効果を示すもう 1 つの例でもあります。
この記事では、10 個のピクセル シェーダに入力を提供する 4 つの頂点シェーダ、頂点シェーダとピクセル シェーダを組み合わせる仕組み、頂点シェーダとピクセル シェーダの組み合わせ、そして個々のピクセル シェーダが使用するブレンディング式を記述するための簡単な表記法について説明してきました。このサンプルと記事は広い範囲のトピックを扱っていますが、これでピクセル シェーダを深く検討するための準備ができたと言えます。
**注 **この記事のサンプルについて。DirectX 8.0 SDK ではなく DirectX 8.1 SDK をインストールしている場合、レジストリ キー名の変更のために、BasePS.exe ファイルはサンプル メディアを見つけることができません。これを回避するには、単に BasePS.exe ファイルを DirectX 8.1 SDK のフレームワーク ソース ファイルを使ってビルドしてください。
最後に
このコラムの執筆に際して、Mike Anderson と Jason Sandlin (Microsoft) 各氏のご協力に感謝します。
ご意見をお待ちしています。コメント、質問、トピックに関するアイデア、コラムのトピックに関しての皆さんのやり方を示すリンクなどを directxj@microsoft.com まで送ってください。ただし、個別に返答したり、サポートに関する質問に回答したりすることはできませんので、あらかじめご了承ください。Microsoft では、オーディオやビデオ関係の DirectXAV、Graphics、ネットワーク、および入力関係の DirectXDev など、同じ目的の開発者が情報を共有できるように、活発なメーリング リストを運営しています。
Philip Taylor は DirectX のプログラム マネージャの一人です。DirectX の最初のパブリック ベータの段階から手を染め、かつては実際に DirectX 2 でゲームを開発したこともありました。時間に余裕があるときは、さまざまな 3-D グラフィックス プログラミング メーリング リストで彼に出会うことがあるかもしれません。