Direct3D 9 での HLSL シェーダーの記述

Vertex-Shaderの基本

操作中は、プログラム可能な頂点シェーダーによって、Microsoft Direct3D グラフィックス パイプラインによって実行される頂点処理が置き換えられます。 頂点シェーダーを使用している間、変換と照明の操作に関する状態情報は、固定関数パイプラインでは無視されます。 頂点シェーダーが無効になっており、固定関数処理が返されると、現在の状態設定がすべて適用されます。

頂点シェーダーを実行する前に、高次プリミティブのテセレーションを行う必要があります。 シェーダー処理後にサーフェス テセレーションを実行する実装では、アプリケーションとシェーダー コードに明らかではない方法で行う必要があります。

頂点シェーダーは、少なくとも、同種のクリップ空間内の頂点位置を出力する必要があります。 必要に応じて、頂点シェーダーはテクスチャ座標、頂点の色、頂点の照明、霧の要因などを出力できます。

Pixel-Shaderの基本

ピクセル処理は、個々のピクセルのピクセル シェーダーによって実行されます。 ピクセル シェーダーは頂点シェーダーと連携して動作します。頂点シェーダーの出力は、ピクセル シェーダーの入力を提供します。 シェーダーの実行後に、その他のピクセル操作 (フォグ ブレンド、ステンシル操作、レンダー ターゲット ブレンド) が発生します。

テクスチャ ステージとサンプラーの状態

ピクセル シェーダーは、テクスチャ ステージの状態で以前に定義された操作を含め、マルチテクスチャ ブレンダーで指定されたピクセル ブレンド機能を完全に置き換えます。 縮小、倍率、mip フィルタリング、ラップ アドレッシング モードの標準テクスチャ ステージの状態によって制御されたテクスチャ サンプリングおよびフィルタリング操作は、シェーダーで初期化できます。 アプリケーションは、現在バインドされているシェーダーの再生成を必要とせずに、これらの状態を自由に変更できます。 シェーダーがエフェクト内で設計されている場合は、状態の設定がさらに簡単になります。

ピクセル シェーダー入力

ps_1_1ピクセル シェーダー バージョンの場合- ps_2_0、シェーダーで使用する前に、拡散色と反射色が 0 ~ 1 の範囲で飽和 (クランプ) されます。

ピクセル シェーダーに入力される色の値はパースペクティブが正しいと見なされますが、これは保証されていません (すべてのハードウェアについて)。 テクスチャ座標からサンプリングされた色は、パースペクティブの正しい方法で反復処理され、反復中に 0 ~ 1 の範囲にクランプされます。

ピクセル シェーダーの出力

ps_1_1ピクセル シェーダーのバージョンの場合 - ps_1_4、ピクセル シェーダーによって出力される結果はレジスタ r0 の内容です。 シェーダーの処理が完了したときに含まれるものは何であれ、霧ステージとレンダー ターゲット ブレンダーに送信されます。

ps_2_0以上のピクセル シェーダー バージョンの場合、出力色は oC0 - oC4 から出力されます。

シェーダー入力とシェーダー変数

シェーダー変数の宣言

最も単純な変数宣言には、型と変数名 (この浮動小数点宣言など) が含まれます。

float fVar;

同じステートメントで変数を初期化できます。

float fVar = 3.1f;

変数の配列を宣言できます。

int iVar[3];

または が宣言され、同じステートメントで初期化されます。

int iVar[3] = {1,2,3};

高レベルシェーダー言語 (HLSL) 変数の特性の多くを示すいくつかの宣言を次に示します。

float4 color;
uniform float4 position : POSITION; 
const float4 lightDirection = {0,0,1};

データ宣言では、次を含む任意の有効な型を使用できます。

シェーダーには、最上位レベルの変数、引数、関数を含めることができます。

// top-level variable
float globalShaderVariable; 

// top-level function
void function(
in float4 position: POSITION0 // top-level argument
              )
{
  float localShaderVariable; // local variable
  function2(...)
}

void function2()
{
  ...
}

最上位の変数は、すべての関数の外部で宣言されます。 最上位レベルの引数は、最上位レベルの関数のパラメーターです。 最上位レベルの関数は、アプリケーションによって呼び出される関数です (別の関数によって呼び出される関数とは対照的です)。

均一シェーダー入力

頂点シェーダーとピクセル シェーダーは、2 種類の入力データを受け入れます。これは、変化と均一です。 さまざまな入力は、シェーダーの各実行に固有のデータです。 頂点シェーダーの場合、さまざまなデータ (位置、法線など) は頂点ストリームから取得されます。 均一なデータ (マテリアルの色、ワールド変換など) は、シェーダーの複数の実行に対して一定です。 アセンブリ シェーダー モデルに精通しているユーザーにとって、均一なデータは定数レジスタで指定され、v レジスタと t レジスタによって異なるデータが指定されます。

均一なデータは、2 つのメソッドで指定できます。 最も一般的な方法は、グローバル変数を宣言し、シェーダー内で使用することです。 シェーダー内でグローバル変数を使用すると、そのシェーダーで必要な均一な変数の一覧にその変数が追加されます。 2 つ目の方法は、最上位レベルのシェーダー関数の入力パラメーターを均一としてマークすることです。 このマーキングは、指定された変数を均一変数の一覧に追加することを指定します。

シェーダーで使用される均一変数は、定数テーブルを介してアプリケーションに伝達されます。 定数テーブルは、シェーダーによって使用される uniform 変数が定数レジスタにどのように適合するかを定義するシンボル テーブルの名前です。 グローバル変数とは異なり、均一関数パラメーターは、ドル記号 ($) で始まる定数テーブルに表示されます。 ローカルの均一入力と同じ名前のグローバル変数との間の名前の競合を回避するには、ドル記号が必要です。

定数テーブルには、シェーダーで使用されるすべての均一変数の定数レジスタの場所が含まれています。 テーブルには、型情報と既定値 (指定されている場合) も含まれます。

シェーダーの入力とセマンティクスの変化

(最上位シェーダー関数の) さまざまな入力パラメーターは、シェーダーの実行で値が定数であることを示すセマンティックまたは均一のキーワード (keyword)でマークする必要があります。 最上位レベルのシェーダー入力がセマンティックまたは均一キーワード (keyword)でマークされていない場合、シェーダーはコンパイルに失敗します。

入力セマンティックは、指定された入力をグラフィックス パイプラインの前の部分の出力にリンクするために使用される名前です。 たとえば、入力セマンティック POSITION0 は、頂点バッファーからの位置データをリンクする場所を指定するために頂点シェーダーによって使用されます。

ピクセル シェーダーと頂点シェーダーには、各シェーダー ユニットにフィードするグラフィックス パイプラインのさまざまな部分があるため、入力セマンティクスのセットが異なります。 頂点シェーダー入力セマンティクスは、頂点バッファーから頂点シェーダーで使用できるフォームに読み込まれる頂点ごとの情報 (位置、法線、テクスチャ座標、色、タンジェント、二項など) を記述します。 入力セマンティクスは、頂点宣言の使用法と使用状況インデックスに直接マップされます。

ピクセル シェーダー入力セマンティクスは、ラスター化ユニットによってピクセルごとに提供される情報を記述します。 データは、現在のプリミティブの各頂点の頂点シェーダーの出力間を補間することによって生成されます。 基本的なピクセル シェーダー入力セマンティクスは、出力色とテクスチャ座標情報を入力パラメーターにリンクします。

入力セマンティクスは、次の 2 つの方法でシェーダー入力に割り当てることができます。

  • パラメーター宣言にコロンとセマンティック名を追加します。
  • 各構造体メンバーに割り当てられた入力セマンティクスを使用した入力構造体の定義。

頂点シェーダーとピクセル シェーダーは、後続のグラフィックス パイプライン ステージに出力データを提供します。 出力セマンティクスは、シェーダーによって生成されたデータを次のステージの入力にリンクする方法を指定するために使用されます。 たとえば、頂点シェーダーの出力セマンティクスを使用して、ラスタライザー内の補間器の出力をリンクし、ピクセル シェーダーの入力データを生成します。 ピクセル シェーダーの出力は、各レンダー ターゲットのアルファ ブレンド ユニットに提供される値、または深度バッファーに書き込まれた深度値です。

頂点シェーダー出力セマンティクスは、シェーダーをピクセル シェーダーとラスタライザー ステージの両方にリンクするために使用されます。 ラスタライザーによって使用され、ピクセル シェーダーに公開されていない頂点シェーダーは、最小として位置データを生成する必要があります。 テクスチャ座標と色データを生成する頂点シェーダーは、補間が行われた後、そのデータをピクセル シェーダーに提供します。

ピクセル シェーダーの出力セマンティクスは、ピクセル シェーダーの出力色を正しいレンダー ターゲットにバインドします。 ピクセル シェーダーの出力色はアルファ ブレンド ステージにリンクされ、ターゲット レンダー ターゲットの変更方法が決まります。 ピクセル シェーダーの深度出力を使用して、現在のラスター位置の宛先深度の値を変更できます。 深度出力と複数のレンダー ターゲットは、一部のシェーダー モデルでのみサポートされています。

出力セマンティクスの構文は、入力セマンティクスを指定するための構文と同じです。 セマンティクスは、"out" パラメーターとして宣言されたパラメーターに直接指定することも、"out" パラメーターまたは関数の戻り値として返される構造体の定義中に割り当てることができます。

セマンティクスは、データの取得元を識別します。 セマンティクスは、シェーダーの入力と出力を識別する省略可能な識別子です。 セマンティクスは、次の 3 つの場所のいずれかに表示されます。

  • 構造体メンバーの後。
  • 関数の入力引数リストの引数の後。
  • 関数の入力引数リストの後。

この例では、 構造体を使用して 1 つ以上の頂点シェーダー入力を提供し、もう 1 つの構造体を使用して 1 つ以上の頂点シェーダー出力を提供します。 各構造体メンバーはセマンティックを使用します。

vector vClr;

struct VS_INPUT
{
    float4 vPosition : POSITION;
    float3 vNormal : NORMAL;
    float4 vBlendWeights : BLENDWEIGHT;
};

struct VS_OUTPUT
{
    float4  vPosition : POSITION;
    float4  vDiffuse : COLOR;

};

float4x4 mWld1;
float4x4 mWld2;
float4x4 mWld3;
float4x4 mWld4;

float Len;
float4 vLight;

float4x4 mTot;

VS_OUTPUT VS_Skinning_Example(const VS_INPUT v, uniform float len=100)
{
    VS_OUTPUT out;

    // Skin position (to world space)
    float3 vPosition = 
        mul(v.vPosition, (float4x3) mWld1) * v.vBlendWeights.x +
        mul(v.vPosition, (float4x3) mWld2) * v.vBlendWeights.y +
        mul(v.vPosition, (float4x3) mWld3) * v.vBlendWeights.z +
        mul(v.vPosition, (float4x3) mWld4) * v.vBlendWeights.w;
    // Skin normal (to world space)
    float3 vNormal =
        mul(v.vNormal, (float3x3) mWld1) * v.vBlendWeights.x + 
        mul(v.vNormal, (float3x3) mWld2) * v.vBlendWeights.y + 
        mul(v.vNormal, (float3x3) mWld3) * v.vBlendWeights.z + 
        mul(v.vNormal, (float3x3) mWld4) * v.vBlendWeights.w;
    
    // Output stuff
    out.vPosition    = mul(float4(vPosition + vNormal * Len, 1), mTot);
    out.vDiffuse  = dot(vLight,vNormal);

    return out;
}

入力構造体は、シェーダー入力を提供する頂点バッファーからのデータを識別します。 このシェーダーは、頂点バッファーの位置、法線、およびブレンドウェイト要素のデータを頂点シェーダー レジスタにマップします。 入力データ型は、頂点宣言データ型と完全に一致する必要はありません。 完全に一致しない場合、シェーダー レジスタに書き込まれると、頂点データは HLSL のデータ型に自動的に変換されます。 たとえば、通常のデータがアプリケーションによって UINT 型として定義されている場合、シェーダーによって読み取られると float3 に変換されます。

頂点ストリーム内のデータに含まれるコンポーネントの数が、対応するシェーダー データ型よりも少ない場合、欠落しているコンポーネントは 0 に初期化されます (w を除き、1 に初期化されます)。

入力セマンティクスは、 D3DDECLUSAGE の値に似ています。

出力構造体は、位置と色の頂点シェーダー出力パラメーターを識別します。 これらの出力は、(プリミティブ処理で) 三角形ラスター化のためにパイプラインによって使用されます。 位置データとしてマークされた出力は、同種空間内の頂点の位置を示します。 頂点シェーダーは、少なくとも位置データを生成する必要があります。 画面空間の位置は、頂点シェーダーが完了した後、(x、y、z) 座標を w で除算して計算されます。 画面空間では、-1 と 1 はビューポートの境界の x と y の最小値と最大値であり、z は z バッファー テストに使用されます。

出力セマンティクスは、 D3DDECLUSAGE の値にも似ています。 一般に、ピクセル シェーダーが位置、ポイント サイズ、または霧セマンティクスでマークされた変数から読み取らない場合、頂点シェーダーの出力構造をピクセル シェーダーの入力構造として使用することもできます。 これらのセマンティクスは、ピクセル シェーダーで使用されない頂点ごとのスカラー値に関連付けられます。 これらの値がピクセル シェーダーに必要な場合は、ピクセル シェーダー セマンティックを使用する別の出力変数にコピーできます。

グローバル変数は、コンパイラによって自動的にレジスタに割り当てられます。 グローバル変数は、シェーダーが呼び出されるたびに処理されるすべてのピクセルで同じであるため、均一パラメーターとも呼ばれます。 レジスタは定数テーブルに含まれており、 ID3DXConstantTable インターフェイスを使用して読み取ることができます。

ピクセル シェーダーの入力セマンティクスは、頂点シェーダーとピクセル シェーダー間のトランスポート用の特定のハードウェア レジスタに値をマップします。 各レジスタの種類には、特定のプロパティがあります。 現在、色座標とテクスチャ座標のセマンティクスは 2 つしかないため、そうでない場合でも、ほとんどのデータがテクスチャ座標としてマークされるのが一般的です。

頂点シェーダーの出力構造では、ピクセル シェーダーでは使用されない位置データを含む入力が使用されていることに注意してください。 HLSL では、ピクセル シェーダーで参照されていない場合、ピクセル シェーダーの有効な入力データではない頂点シェーダーの有効な出力データが許可されます。

入力引数は配列にすることもできます。 セマンティクスは、配列の各要素に対してコンパイラによって自動的にインクリメントされます。 たとえば、次の明示的な宣言を検討してください。

struct VS_OUTPUT
{
    float4 Position   : POSITION;
    float3 Diffuse    : COLOR0;
    float3 Specular   : COLOR1;               
    float3 HalfVector : TEXCOORD3;
    float3 Fresnel    : TEXCOORD2;               
    float3 Reflection : TEXCOORD0;               
    float3 NoiseCoord : TEXCOORD1;               
};

float4 Sparkle(VS_OUTPUT In) : COLOR

上記の明示的な宣言は、コンパイラによって自動的にインクリメントされるセマンティクスを持つ次の宣言と同じです。

float4 Sparkle(float4 Position : POSITION,
                 float3 Col[2] : COLOR0,
                 float3 Tex[4] : TEXCOORD0) : COLOR0
{
   // shader statements
   ...

入力セマンティクスと同様に、出力セマンティクスはピクセル シェーダー出力データのデータ使用量を識別します。 多くのピクセル シェーダーは、1 つの出力色にのみ書き込みます。 ピクセル シェーダーでは、1 つ以上のレンダー ターゲットに深度値を同時に書き込むこともできます (最大 4 つ)。 頂点シェーダーと同様に、ピクセル シェーダーは構造体を使用して複数の出力を返します。 このシェーダーは、カラー コンポーネントと深度コンポーネントに 0 を書き込みます。

struct PS_OUTPUT
{
    float4 Color[4] : COLOR0;
    float  Depth  : DEPTH;
};

PS_OUTPUT main(void)
{
    PS_OUTPUT out;

   // Shader statements
   ...

  // Write up to four pixel shader output colors
  out.Color[0] =  ...
  out.Color[1] =  ...
  out.Color[2] =  ...
  out.Color[3] =  ...

  // Write pixel depth 
  out.Depth =  ...

    return out;
}

ピクセル シェーダーの出力色は float4 型である必要があります。 複数の色を書き込む場合は、すべての出力色を連続して使用する必要があります。 つまり、 COLOR0 が既に書き込まれていない限り、 COLOR1 を出力することはできません。 ピクセル シェーダーの深度出力は float1 型である必要があります。

サンプラーとテクスチャ オブジェクト

サンプラーにはサンプラーの状態が含まれています。 サンプラーの状態は、サンプリングするテクスチャを指定し、サンプリング中に実行されるフィルター処理を制御します。 テクスチャをサンプリングするには、次の 3 つが必要です。

  • テクスチャ
  • サンプラー (サンプラーの状態)
  • サンプリング命令

次に示すように、サンプラーはテクスチャとサンプラーの状態で初期化できます。

sampler s = sampler_state 
{ 
  texture = NULL; 
  mipfilter = LINEAR; 
};

2D テクスチャをサンプリングするコードの例を次に示します。

texture tex0;
sampler2D s_2D;

float2 sample_2D(float2 tex : TEXCOORD0) : COLOR
{
  return tex2D(s_2D, tex);
}

テクスチャはテクスチャ変数 tex0 で宣言されます。

この例では、s_2D という名前のサンプラー変数が宣言されています。 サンプラーには、中かっこ内のサンプラーの状態が含まれています。 これには、サンプリングされるテクスチャと、必要に応じてフィルター状態 (ラップ モード、フィルター モードなど) が含まれます。 サンプラーの状態を省略すると、線形フィルター処理とテクスチャ座標のラップ モードを指定する既定のサンプラー状態が適用されます。 サンプラー関数は、2 成分の浮動小数点テクスチャ座標を受け取り、2 成分の色を返します。 これは float2 戻り値の型で表され、赤と緑のコンポーネントのデータを表します。

4 種類のサンプラーが定義され (キーワードを参照)、組み込み関数によってテクスチャ参照が実行されます。tex1D(s, t) (DirectX HLSL)tex2D(s,t) (DirectX HLSL)、tex3D(s,t) (DirectX HLSL)texCUBE(s,t) (DirectX HLSL))。 3D サンプリングの例を次に示します。

texture tex0;
sampler3D s_3D;

float3 sample_3D(float3 tex : TEXCOORD0) : COLOR
{
  return tex3D(s_3D, tex);
}

このサンプラー宣言では、フィルター設定とアドレス モードに既定のサンプラー状態が使用されます。

対応するキューブ サンプリングの例を次に示します。

texture tex0;
samplerCUBE s_CUBE;

float3 sample_CUBE(float3 tex : TEXCOORD0) : COLOR
{
  return texCUBE(s_CUBE, tex);
}

最後に、1D サンプリングの例を次に示します。

texture tex0;
sampler1D s_1D;

float sample_1D(float tex : TEXCOORD0) : COLOR
{
  return tex1D(s_1D, tex);
}

ランタイムは 1D テクスチャをサポートしていないため、コンパイラは y 座標が重要ではないという知識を持つ 2D テクスチャを使用します。 tex1D(s, t) (DirectX HLSL) は 2D テクスチャ参照として実装されているため、コンパイラは y コンポーネントを効率的に自由に選択できます。 まれなシナリオでは、コンパイラは効率的な y コンポーネントを選択できません。その場合、警告が発行されます。

texture tex0;
sampler s_1D_float;

float4 main(float texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float, texCoords);
}

コンパイラが入力座標を別のレジスタに移動する必要があるため、この特定の例は非効率的です (1D 参照は 2D 参照として実装され、テクスチャ座標は float1 として宣言されているため)。 コードが float1 ではなく float2 入力を使用して書き換えられた場合、コンパイラは y が何かに初期化されていることを認識しているため、入力テクスチャ座標を使用できます。

texture tex0;
sampler s_1D_float2;

float4 main(float2 texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float2, texCoords);
}

すべてのテクスチャ参照には、"bias" または "proj" (つまり、 tex2Dbias (DirectX HLSL)texCUBEproj (DirectX HLSL)) を追加できます。 "proj" サフィックスを使用すると、テクスチャ座標は w コンポーネントで除算されます。 "バイアス" を使用すると、mip レベルは w 成分によってシフトされます。 したがって、サフィックスを持つすべてのテクスチャ参照は、常に float4 入力を受け取ります。 tex1D(s, t) (DirectX HLSL)tex2D(s, t) (DirectX HLSL) は、yz- コンポーネントと z 成分をそれぞれ無視します。

サンプラーは配列でも使用できますが、現在、サンプラーの動的配列アクセスをサポートしているバックエンドはありません。 したがって、コンパイル時に解決できるため、次の値が有効です。

tex2D(s[0],tex)

ただし、この例は無効です。

tex2D(s[a],tex)

サンプラーの動的アクセスは、リテラル ループを含むプログラムを記述する場合に主に役立ちます。 次のコードは、サンプラー配列へのアクセスを示しています。

sampler sm[4];

float4 main(float4 tex[4] : TEXCOORD) : COLOR
{
    float4 retColor = 1;

    for(int i = 0; i < 4;i++)
    {
        retColor *= tex2D(sm[i],tex[i]);
    }

    return retColor;
}

Note

Microsoft Direct3D デバッグ ランタイムを使用すると、テクスチャ内のコンポーネントの数とサンプラーの間の不一致をキャッチするのに役立ちます。

 

関数の記述

関数は、大きなタスクを小さなタスクに分割します。 小さなタスクはデバッグが簡単で、実証後に再利用できます。 関数を使用すると、他の関数の詳細を非表示にできるため、関数で構成されるプログラムのフォローが容易になります。

HLSL 関数は、いくつかの点で C 関数に似ています。どちらも定義と関数本体を含み、両方とも戻り値の型と引数リストを宣言します。 C 関数と同様に、HLSL 検証では、シェーダーのコンパイル中に引数、引数の型、戻り値の型チェックが行われます。

C 関数とは異なり、HLSL エントリ ポイント関数はセマンティクスを使用して、関数の引数をシェーダーの入力と出力にバインドします (内部的に呼び出される HLSL 関数はセマンティクスを無視します)。 これにより、バッファー データをシェーダーにバインドし、シェーダーの出力をシェーダー入力にバインドしやすくなります。

関数には宣言と本文が含まれており、宣言は本体の前に置く必要があります。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

関数宣言には、中かっこの前にあるすべてのものが含まれます。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

関数宣言には、次のものが含まれます。

  • 戻り値の型
  • 関数名
  • 引数リスト (省略可能)
  • 出力セマンティック (省略可能)
  • 注釈 (省略可能)

戻り値の型には、float4 などの HLSL 基本データ型のいずれかを指定できます。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
   ...
}

戻り値の型には、既に定義されている構造体を指定できます。

struct VS_OUTPUT
{
    float4  vPosition        : POSITION;
    float4  vDiffuse         : COLOR;
}; 

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

関数が値を返さない場合は、void を戻り値の型として使用できます。

void VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

戻り値の型は、常に関数宣言の先頭に表示されます。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

引数リストは、関数への入力引数を宣言します。 また、返される値を宣言することもできます。 一部の引数は、入力引数と出力引数の両方です。 4 つの入力引数を受け取るシェーダーの例を次に示します。

float4 Light(float3 LightDir : TEXCOORD1, 
             uniform float4 LightColor,  
             float2 texcrd : TEXCOORD0, 
             uniform sampler samp) : COLOR 
{
    float3 Normal = tex2D(samp,texcrd);

    return dot((Normal*2 - 1), LightDir)*LightColor;
}

この関数は、テクスチャ サンプルと明るい色のブレンドである最終的な色を返します。 関数は 4 つの入力を受け取ります。 2 つの入力にセマンティクスがあります。LightDir には TEXCOORD1 セマンティックがあり、texcrd には TEXCOORD0 セマンティックがあります。 セマンティクスは、これらの変数のデータが頂点バッファーから取得されることを意味します。 LightDir 変数には TEXCOORD1 セマンティックがありますが、パラメーターはおそらくテクスチャ座標ではありません。 TEXCOORDn セマンティック型は、多くの場合、定義済みではない型のセマンティックを提供するために使用されます (ライト方向には頂点シェーダー入力セマンティックはありません)。

他の 2 つの入力 LightColor と samp には、均一なキーワード (keyword)でラベルが付けます。 これらは、描画呼び出し間で変更されない均一な定数です。 これらのパラメーターの値は、シェーダー グローバル変数から取得されます。

引数には、キーワード (keyword) 内の を含む入力としてラベルを付け、出力キーワード (keyword)を含む出力引数としてラベル付けできます。 引数は参照渡しできません。ただし、入力と出力の両方が inout キーワード (keyword)で宣言されている場合は、引数を指定できます。 inout キーワード (keyword)でマークされた関数に渡される引数は、関数が戻るまで元のコピーと見なされ、コピーバックされます。 inout を使用する例を次に示します。

void Increment_ByVal(inout float A, inout float B) 
{ 
    A++; B++;
}

この関数は、A と B の値をインクリメントし、それらを返します。

関数本体は、関数宣言の後のすべてのコードです。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

本文は、中かっこで囲まれたステートメントで構成されます。 関数本体は、変数、リテラル、式、およびステートメントを使用して、すべての機能を実装します。

シェーダー本体は 2 つのことを行います。マトリックス乗算を実行し、float4 の結果を返します。 行列乗算は、4x4 行列乗算を実行する mul (DirectX HLSL) 関数で実現されます。 mul (DirectX HLSL) は、関数の HLSL ライブラリに既に組み込まれているため、組み込み関数と呼ばれます。 組み込み関数については、次のセクションで詳しく説明します。

行列乗算は、入力ベクトル Pos と複合行列 WorldViewProj を結合します。 結果は、画面空間に変換された位置データです。 これは、可能な最小限の頂点シェーダー処理です。 頂点シェーダーの代わりに固定関数パイプラインを使用していた場合は、この変換を実行した後に頂点データを描画できます。

関数本体の最後のステートメントは return ステートメントです。 C と同様に、このステートメントは関数から関数を呼び出したステートメントに制御を返します。

関数の戻り値の型には、bool、int half、float、double など、HLSL で定義されている任意の単純なデータ型を指定できます。 戻り値の型には、ベクトルやマトリックスなどの複合データ型のいずれかを指定できます。 オブジェクトを参照する HLSL 型は、戻り値の型として使用できません。 これには、pixelshader、vertexshader、texture、sampler が含まれます。

戻り値の型に構造体を使用する関数の例を次に示します。

float4x4 WorldViewProj : WORLDVIEWPROJ;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VS_HLL_Example(float4 inPos : POSITION )
{
    VS_OUTPUT Out;

    Out.Pos = mul(inPos,  WorldViewProj );

    return Out;
};

float4 の戻り値の型は、1 つの float4 メンバーを含む構造体VS_OUTPUTに置き換えられました。

return ステートメントは、関数の終了を通知します。 これは最も単純な return ステートメントです。 関数から呼び出し元のプログラムに制御を返します。 値は返されません。

void main()
{
    return ;
}

return ステートメントは、1 つ以上の値を返すことができます。 次の例では、リテラル値を返します。

float main( float input : COLOR0) : COLOR0
{
    return 0;
}

次の例では、式のスカラー結果を返します。

return  light.enabled;

次の例では、ローカル変数とリテラルから構築された float4 を返します。

return  float4(color.rgb, 1) ;

次の使用例は、組み込み関数から返された結果から構築された float4 と、いくつかのリテラル値を返します。

float4 func(float2 a: POSITION): COLOR
{
    return float4(sin(length(a) * 100.0) * 0.5 + 0.5, sin(a.y * 50.0), 0, 1);
}

次の使用例は、1 つ以上のメンバーを含む構造体を返します。

float4x4 WorldViewProj;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
    VS_OUTPUT out;
    out.Pos = mul(inPos, WorldViewProj );
    return out;
};

フロー制御

最新の頂点シェーダーとピクセル シェーダー ハードウェアは、シェーダーを 1 行ずつ実行し、各命令を 1 回実行するように設計されています。 HLSL では、静的分岐、述語命令、静的ループ、動的分岐、動的ループなどのフロー制御がサポートされています。

以前は、 if ステートメントを使用すると、コード フローの if 側と else 側の両方を実装するアセンブリ言語シェーダー コードが生成されました。 vs_1_1用にコンパイルされた HLSL コードの の例を次に示します。

if (Value > 0)
    oPos = Value1; 
else
    oPos = Value2; 

結果のアセンブリ コードを次に示します。

// Calculate linear interpolation value in r0.w
mov r1.w, c2.x               
slt r0.w, c3.x, r1.w         
// Linear interpolation between value1 and value2
mov r7, -c1                      
add r2, r7, c0                   
mad oPos, r0.w, r2, c1  

一部のハードウェアでは静的または動的ループが可能ですが、ほとんどの場合、線形実行が必要です。 ループをサポートしていないモデルでは、すべてのループをアンロールする必要があります。 たとえば、ps_1_1 シェーダーに対しても、ロールされていないループを使用する DepthOfField サンプル サンプル です。

HLSL には、次の種類のフロー制御のサポートが含まれるようになりました。

  • 静的分岐
  • 述語付き命令
  • 静的ループ
  • 動的分岐
  • 動的ループ

静的分岐を使用すると、ブールシェーダー定数に基づいてシェーダー コードのブロックをオンまたはオフに切り替えることができます。 これは、現在レンダリングされているオブジェクトの種類に基づいてコード パスを有効または無効にするための便利な方法です。 描画呼び出しの間に、現在のシェーダーでサポートする機能を決定し、その動作を取得するために必要なブール型フラグを設定できます。 ブール定数によって無効になっているステートメントは、シェーダーの実行中にスキップされます。

最も使い慣れた分岐のサポートは、動的分岐です。 動的分岐では、比較条件は変数内に存在します。つまり、比較は、(コンパイル時または 2 回の描画呼び出しの間で発生する比較ではなく) 実行時に各頂点または各ピクセルに対して行われます。 パフォーマンスの低下は、ブランチのコストに加えて、実行されたブランチ側の命令のコストです。 動的分岐は、シェーダー モデル 3 以降で実装されます。 これらのモデルで動作するシェーダーの最適化は、CPU で実行されるコードの最適化に似ています。

HLSL 用プログラミング ガイド