C# HLSL Viewer 2
~ Cutting Edge DX 9 - 第 14 回目 ~
Hiroyuki Kawanishi (川西 裕幸)
マイクロソフト株式会社
テクニカル エバンジェリスト
2004 年 2 月 16 日
今回は、BasicHLSL.fx に使われているエフェクト フレームワークと HLSL について解説します。エフェクトファイルについては DX8 のときに3D エフェクトのスクリプティングで紹介したことがありますが、DX9 では、エフェクト ファイルの中で HLSL (High Level Shader Language) が使えるよう拡張されています。エフェクト フレームワークと HLSL の仕様については、書籍「プログラミング DirectX 9 グラフィックス パイプライン」が詳しいので、参照してください。
目次
テクニックとパス
変数
セマンティクス
テクスチャ サンプラ
注釈
HLSL の仕様
データ型
組み込み関数
BasicHLSL.fx の内容
頂点シェーダ
ピクセル シェーダ
まとめ
エフェクト フレームワーク
元々エフェクト (Effect) フレームワークは、仕様の異なるハードウェアに対応して適用するエフェクトを切り替え可能にするスキームを提供するために開発されました。テクニック (technique) というキーワードを基に、ターゲットとなるハードウェアに合わせてそれぞれテクスチャステートやレンダーステート (そしてシェーダ言語) を記述できます。
DX90SDK\Samples\Media\water.fx を見るとこのことがよく分かります。water.fx では、5 種類のテクニックを記述しており、それぞれ複数ステージや複数パスを使ったり反射を適用したりしています。アプリケーションは動作システムのハードウェアに合わせて、どのテクニックを使うのかを決めるだけで済みます。エフェクト フレームワークを使うことでコードをシンプルにでき、テストも容易になります。
詳細は DirectX 9.0 SDK ドキュメントの「エフェクト」を参照してください。
テクニックとパス
前述のようにエフェクトの本体はテクニック内にあります。BasicHLSL.fx の一番最後にあり、ここでは HLSL (RenderSceneVS 頂点シェーダ関数と RenderScenePS ピクセル シェーダ関数) を呼び出し、コンパイルしています。複数パスを使ったレンダリングを実装するときは、pass キーワードを複数設定します。シェーダを使わないときは、water.fx のようにここでステート変数を設定します。
//-----------------------------------------------------------------------------
// Name: RenderScene
// Type: テクニック
// Desc: レンダーターゲットにシーンをレンダリング
//-----------------------------------------------------------------------------
technique RenderScene
{
pass P0
{
VertexShader = compile vs_1_1 RenderSceneVS();
// 単純なピクセル シェーダ(必要に応じて固定機能にも変更できます)
PixelShader = compile ps_1_1 RenderScenePS();
}
}
変数
エフェクト ファイルと (C++ や C#/VB.NET の) アプリケーションとの間で、変数のやり取りが可能です。BasicHLSL.fx ファイルではいくつかのグローバル変数をアプリケーションから SetValue メソッドなどを使って毎フレーム設定できるようにしています。もちろん g_LightDiffuse のようにエフェクト ファイル内部だけで宣言・定義して使うこともできます。
//-----------------------------------------------------------------------------
// グローバル 変数
//-----------------------------------------------------------------------------
...
float4 g_LightDiffuse = { 1.0f, 1.0f, 1.0f, 1.0f };// ライトのディフューズ色
...
float g_fTime; // 秒単位のアプリケーション時刻
float4x4 g_mWorld; // オブジェクト用のワールド行列
float4x4 g_mWorldViewProjection;// ワールド * ビュー * 射影行列
上のようにエフェクト ファイル内で宣言された変数を、次のように C# 内で設定します。
// エフェクト パラメータの初期化
hTime = basicEffect.GetParameter(null, "g_fTime");
hWorld = basicEffect.GetParameter(null, "g_mWorld");
hWorldViewProj = basicEffect.GetParameter(null,"g_mWorldViewProjection");
...
basicEffect.SetValue(hWorld, matWorld);
// ワールドビュー射影行列の設定
matWorld.Multiply(matViewProj);
basicEffect.SetValue(hWorldViewProj, matWorld);
// 時間変数の設定
basicEffect.SetValue(hTime, appTime);
セマンティック
セマンティックは、変数に追加する識別子であり、アプリケーションはこの識別子から必要な変数を探すことができます。変数宣言の後にある「:」がキーワードです。BasicHLSL.fx では、頂点シェーダとピクセル シェーダの入力引数にセマンティックを使っています。位置変数に vPos、法線に vNormal、テクスチャ座標に vTexCoord0 変数を使っていることが分かります (実際に、このセマンティックを使って頂点宣言と変数とのマッチングを行っています)。もちろん、グローバル変数にセマンティックを使うこともできます。
VS_OUTPUT RenderSceneVS( float4 vPos : POSITION,
float3 vNormal : NORMAL,
float2 vTexCoord0 : TEXCOORD0 )
テクスチャ サンプラ
エフェクト フレームワークでは、テクスチャ サンプラ (sampler) を使ってテクスチャをサンプリングします。テクスチャ サンプラには、テクスチャのサンプリングに必要なテクスチャへの参照とサンプラ ステート(フィルタ ステートやラップ モードなど) が入ります。
texture g_MeshTexture;
...
sampler MeshTextureSampler =
sampler_state
{
Texture = <g_MeshTexture>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
注釈
BasicHLSL.fx では使っていませんが、注釈 (annotation) はユーザーの情報を変数に追加するメカニズムです。次のように変数宣言の後に追加し、< と > に囲まれた、型・変数・等号・値・セミコロンで表現します。アプリケーションは該当する変数に必要なデータを注釈から取得できます。
// テクスチャ
texture Tex0 < string name = "tiger.bmp"; >;
HLSLの仕様
HLSL はシェーダを記述する高級言語です。ここでは、HLSL 特有のデータ型と組み込み関数について紹介します。
データ型
次のように HLSL にはスカラー型とベクトル型と行列型があります。したがって、ベクトルや行列の宣言・定義、およびベクトルや行列を使った式の表現が非常に簡単です。
スカラー型 | 説明 |
---|---|
bool | 真あるいは偽 |
int | 32 ビット符号付き整数 |
half | 16 ビット浮動小数点値 |
float | 32 ビット浮動小数点値 |
ベクトル型 | 説明 |
---|---|
float4, float3, float2, float1 | 32 ビット浮動小数点ベクトル |
int4, int3, int2, int1 | 32 ビット符号付き整数ベクトル |
half4, half3, half2, half1 | 16 ビット浮動小数点ベクトル |
行列型 | 説明 |
---|---|
float4x4, float4x3, float3x4...float1x1 | 32 ビット浮動小数点行列 |
int4x4, int4x3, int3x4...int1x1 | 32 ビット符号付き整数行列 |
half4x4, half4x3, half3x4...half1x1 | 16 ビット浮動小数点行列 |
組み込み関数
次のような、シェーダを書く際に使い勝手のよい組み込み関数がサポートされています。
組み込み関数 | 説明 |
---|---|
abs(a) | 絶対値 (各成分) |
sin(x), cos(x), tan(x) | x の正弦 / 余弦 / 正接を返す |
cross(a, b) | 2 つのベクトル a と b の外積を返す |
dot(a, b) | 2 つのベクトル a と b の内積を返す |
frac(x) | x の小数部分を返す |
lerp(a, b, s) | a + s(b - a) を返す。これは a と b の間で線形補間する |
max(a,b), min(a, b) | a と b の大きいほう / 小さいほうを返す |
mul(a, b) | a と b の行列乗算を実行する |
notmalize(v) | ベクトル v を正規化したベクトルを返す |
reflect(i, n) | 入射光線の方向ベクトル i と、サーフェイス法線 n から、反射ベクトル v を返す |
refract(i, n, eta) | 入射光線の方向ベクトル i と、サーフェイス法線 n と、相対屈折率 eta から、屈折ベクトル v を返す |
round(x) | 最も近い整数に x を丸める |
rsqrt(a) | 平方根の逆数: 1 / 平方根 (各成分) |
sqrt(a) | 平方根 (各成分) |
tex1D(s, t) | 1D テクスチャ参照 |
tex2D(s, t) | 2D テクスチャ参照 |
tex3D(s, t) | 3D テクスチャ参照 |
texCUBE(s, t) | 3D キューブ テクスチャ参照 |
BasicHLSL.fx の内容
頂点シェーダ
最初に説明したように、RenderSceneVS() 関数が頂点シェーダです。頂点シェーダは頂点バッファから入力された単一の頂点を処理します。位置と法線とテクスチャ座標を入力として、位置とディフューズ色とテクスチャ座標を出力します。処理の内容は次の通りです。
- 位置座標の X 成分を時間変数の正弦で変形させ、クリップ空間に座標変換
- 法線をワールド空間に座標変換
- 法線と平行光との内積 (の正成分) を計算し、ディフューズ色と乗算し、アンビエント色を加算
- テクスチャ座標はそのまま出力にコピー
最初の変形以外は、固定機能パイプラインを使ったテクスチャとライティングのモジュレートと同じです。
//-----------------------------------------------------------------------------
// 頂点シェーダ出力構造体
//-----------------------------------------------------------------------------
struct VS_OUTPUT
{
float4 Position : POSITION; // 頂点の位置
float4 Diffuse : COLOR0; // 頂点のディフューズ色
float2 TextureUV : TEXCOORD0; // 頂点のテクスチャ座標
};
//-----------------------------------------------------------------------------
// Name: RenderSceneVS
// Type: 頂点シェーダ
// Desc: このシェーダは標準的な座標変換と照明を計算します。
//-----------------------------------------------------------------------------
VS_OUTPUT RenderSceneVS( float4 vPos : POSITION,
float3 vNormal : NORMAL,
float2 vTexCoord0 : TEXCOORD0 )
{
VS_OUTPUT Output;
float3 vNormalWorldSpace;
// 時刻を基に頂点のオブジェクト空間での位置をアニメーションします
float4 vAnimatedPos = vPos + float4( sin(g_fTime) * vPos.x/2, 0, 0, 0 );
// オブジェクト空間から同次射影空間にオブジェクトを座標変換
Output.Position = mul(vAnimatedPos, g_mWorldViewProjection);
// オブジェクト空間からワールド空間に法線を座標変換
vNormalWorldSpace = normalize(mul(vNormal, (float3x3)g_mWorld));
// 単純な平行光の式を計算
Output.Diffuse.rgb =
g_MaterialDiffuseColor * g_LightDiffuse * max(0,dot(vNormalWorldSpace, g_LightDir)) +
g_MaterialAmbientColor * g_LightAmbient;
Output.Diffuse.a = 1.0f;
// テクスチャ座標はコピーするだけ
Output.TextureUV = vTexCoord0;
return Output;
}
ピクセル シェーダ
RenderScenePS() 関数がピクセルシェーダです。頂点シェーダの出力データがピクセル シェーダに渡されますが、ピクセル シェーダに渡されるのは、ラスタ化されピクセル単位に補間されたデータなので注意してください。ピクセル シェーダは渡された単一のピクセルを処理します。ここで行っているのは、固定機能パイプラインで通常行っていることと同じテクスチャ マッピングとディフューズ色のモジュレートです。したがって、ピクセルシェーダを使わずにテクスチャステートとレンダーステートでも同じことができます。
//-----------------------------------------------------------------------------
// ピクセル シェーダ出力構造体
//-----------------------------------------------------------------------------
struct PS_OUTPUT
{
float4 RGBColor : COLOR0; // ピクセル色
};
//-----------------------------------------------------------------------------
// Name: RenderScenePS
// Type: ピクセル シェーダ
// Desc: このシェーダは、テクスチャ色とディフューズ マテリアル色と
// モジュレートしてピクセルの色を出力します。
//-----------------------------------------------------------------------------
PS_OUTPUT RenderScenePS( VS_OUTPUT In )
{
PS_OUTPUT Output;
// メッシュ テクスチャを参照し、ディフューズとモジュレート
Output.RGBColor = tex2D(MeshTextureSampler, In.TextureUV) * In.Diffuse;
return Output;
}
まとめ
BasicHLSL.fx をベースにエフェクト フレームワークと HLSL について解説しましたが、分かりにくい部分があったと思います。シェーダ プログラミングを理解するには、3D グラフィックスの基礎である行列・座標変換やグラフィックス パイプラインあるいは光と色の計算などの知識が必要です。シェーダ言語あるいはコンピュータ グラフィックスについていくつか書籍が出版されていますので、参考にしてください。
次回は、EffectEdit (C++) とある程度互換性のある、HLSL Viewer を C# で実装してみようと思います。