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

  • 頂点シェーダーの基本機能
  • ピクセル シェーダーの基本機能
  • シェーダー入力とシェーダー変数
  • 関数の作成
  • フロー制御

頂点シェーダーの基本機能

操作時には、プログラム可能な頂点シェーダーは、Microsoft Direct3D グラフィック パイプラインで処理される頂点処理の代わりとなります。頂点シェーダーの使用中には、トランスフォームとライティング操作に関するステート情報は固定機能パイプラインによって無視されます。頂点シェーダーが無効化され、固定機能処理が返されると、現在のすべてのステート設定が適用されます。

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

最低限、頂点シェーダーは同次クリック空間に頂点の位置を出力する必要があります。オプションとして、頂点シェーダーはテクスチャー座標、頂点カラー、頂点ライティング、フォグ係数などを出力できます。

ピクセル シェーダーの基本機能

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

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

ピクセル シェーダーは、テクスチャー ステージ ステートによって以前に定義された処理を含むマルチテクスチャー ブレンダーによって指定される、ピクセルブレンディング機能の代わりとなります。縮小、拡大、ミップ フィルターリング、およびラップ アドレッシング モードに対する標準テクスチャー ステージ ステートによって制御されたテクスチャー サンプリングとフィルターリング処理は、シェーダーで初期化できます。現在バインドされているシェーダーを再生成せずに、アプリケーションはこれらのステートを自由に変更できます。シェーダーがエフェクト内に設計されている場合は、ステートの設定がさらに簡単です。

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

ピクセル シェーダーのバージョン ps_1_1 から ps_2_0 については、シェーダーによって使用される前に、ディフューズ色と スペキュラ色は範囲 0 ~ 1 で飽和 (クランプ) されます。

ピクセル シェーダーへのカラー値入力は、パースペクティブ補正と想定されますが、すべてのハードウェアで保証されるわけではありません。テクスチャー座標からサンプリングされたカラーはパースペクティブ補正方法で繰り返され、反復中に 0 ~ 1 の範囲でクランプされます。

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

ピクセル シェーダーのバージョン ps_1_1 から ps_1_4 については、ピクセル シェーダーが放出する結果は、レジスタ r0 の内容です。シェーダーが処理完了時に何を含んでいても、処理はフォグ ステートとレンダー ターゲット ブレンダーに送信されます。

ピクセル シェーダーのバージョン ps_2_0 以上に関しては、出力カラーは C0 ~ 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 種類の入力データを扱うことができます。不均一入力とは、シェーダーの実行ごとに固有のデータです。頂点シェーダーでは、位置や法線などの不均一データを頂点ストリームから取得します。マテリアル カラーやワールド トランスフォームなどの均一データは、1 つのシェーダーを何回実行しても一定です。アセンブリ シェーダー モデルに馴染みのある開発者は、均一データを定数レジスタで指定し、不均一データを v レジスタと t レジスタで指定します。

均一データは 2 とおりの方法で指定できます。最も一般的な方法は、グローバル変数を宣言し、これらをシェーダー内で使用するというものです。シェーダー内でグローバル変数を使うと、そのシェーダーが必要とする均一変数のリストにその変数が追加されます。2 つ目の方法は、最上位のシェーダー関数の入力パラメーターを均一にすることです。このマーキングは、所定の変数が均一変数のリストに追加されるように指定します。

シェーダーが使用する均一変数は、定数テーブルを介してアプリケーションに戻されます。定数テーブルとは、シェーダーが使用する均一変数を定数レジスタに収める方法を定義するシンボル テーブルの名称です。定数テーブルでは均一関数のパラメーターは、グローバル変数とは異なり先頭にドル記号 ($) が付きます。ドル記号はローカル均一入力と、同じ名前のグローバル変数との名前の衝突を避けるために必要です。

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

不均一なシェーダー入力とセマンティック

不均一な入力パラメーター (最上位のシェーダー関数) は、シェーダーの実行に対してその値が定数であることを示す、セマンティックと均一キーワードのいずれかでマーキングする必要があります。最上位のシェーダーをセマンティックまたは不均なキーワードでマーキングしないと、シェーダーは異常終了します。

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

ピクセル シェーダーと頂点シェーダーはさまざまな入力セマンティックを持ちます。これは、各シェーダー ユニットに送り出されるグラフィック パイプライン部分がさまざまであるためです。頂点シェーダーの入力セマンティクスは、頂点シェーダーで使用可能な形式で頂点バッファーから読み込む情報を頂点ごとに記述します。この情報には、位置、法線、テクスチャー座標、カラー、接線、従法線などがあります。入力セマンティックは頂点宣言の使用法と使用法インデックスに直接マッピングします。

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

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

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

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

シェーダーをピクセル シェーダーとラスタライザー ステージの両方にリンクするには、焦点シェーダーの出力セマンティックを使用します。ラスタライザーに消費され、ピクセル シェーダーに公開されない頂点シェーダーは、少なくとも位置データを生成する必要があります。テクスチャー座標とカラー データを生成する頂点シェーダーは、線形補間が実行された後でピクセル シェーダーにそのデータを提供します。

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

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

セマンティックは、データがどこで生成されたかを識別します。セマンティックは、シェーダーの入力と出力を識別するオプションの識別子です。セマンティクスは、次のいずれかの位置に記述します。

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

この例では、1 つの構造体を使って 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; } 

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

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

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

出力構造体は、頂点シェーダー出力パラメーターの position と color に似ています。これらの出力は、プリミティブな処理におけるトライアングル ラスタライゼーションのためにパイプラインによって使用されます。位置データとマークされている出力は、同次空間の頂点の位置を表します。頂点シェーダーは、少なくとも位置データを出力する必要があります。スクリーン空間位置は、頂点シェーダーの完了後に (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 戻り型で指定され、赤および緑成分のデータを表します。

tex1D(s, t) (DirectX HLSL)tex2D(s, t) (DirectX HLSL)tex3D(s, t) (DirectX HLSL)texCUBE(s, t) (DirectX HLSL) という 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 成分で分割されます。"bias" サフィックスを指定した場合は、ミップ レベルが 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; } 

    

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 には、uniform キーワードでラベル付けされます。これらは、複数の描画関数呼び出しにわたって変化しない均一定数です。これらのパラメーターの値は、シェーダー グローバル変数から渡されます。

引数には、in キーワードを使って入力として、出力引数には out キーワードを使って出力としてラベル付けできます。この場合、引数を参照渡しすることはできません。ただし、inout キーワードを使って宣言されている場合、引数は入力と出力の両方になります。inout キーワードでマークされた関数に渡された引数は、関数が戻り、書き戻されるまで元のユニットのコピーと見なされます。次に、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 ); }; 

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

シェーダーの本体は、行列の乗算を行う、float4 の結果を返す、という 2 つの操作を行います。行列の乗算では、4x4 行列の乗算を実行する mul (DirectX HLSL) 関数を使用します。mul (DirectX HLSL) は、HLSL 関数ライブラリにあらかじめ組み込まれているので、組み込み関数と呼ばれます。組み込み関数については、次のセクションで詳しく説明します。

行列の乗算では、入力ベクトル Pos と合成行列 WorldViewProj の組み合わせが使用されます。その結果は、スクリーン空間にトランスフォームされた位置データとなります。これは、実行できる最小の頂点シェーダ処理です。頂点シェーダの代わりに固定機能パイプラインを使用した場合は、このトランスフォームを行った後に頂点データを描画できます。

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

関数の戻り型には、HLSL に定義されている単純データ型のいずれか (bool、int half、float、double を含む) を指定できます。戻り型には、ベクトルや行列のような複雑なデータ型を使用できます。オブジェクトを参照する HLSL 型を戻り型として使用することはできません。これにはピクセルシェーダー、頂点シェーダー、テクスチャー、サンプラーが含まれます。

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

 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 が、float4 メンバーを 1 つ含む構造体 VS_OUTPUT に置き換えられています。

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

 void main() {     return ; } 

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

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

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

 return  light.enabled = true ; 

次の例は、1 つのローカル変数と 1 つのリテラルから構成される 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 度実行するように設計されています。HLSL は、フロー制御をサポートし、これには、静的な分岐、命令の予測、静的ループ、動的分岐、および動的ルーピングが含まれます。

以前は、if ステートメントを使用すると、コード フローの if 側と他の側の両方を実装するアセンブリ言語シェーダーが発生しました。次に、vs_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   

静的ループまたは動的ループを許可するハードウェアは一部ですが、ほとんどのハードウェアが線形実行を必要とします。ルーピングをサポートしないモデルでは、すべてのループを展開しなければなりません。たとえば、「DepthOfField サンプル」にある、ps_1_1 シェーダーに対しても展開ループを使用するサンプルがこれに該当します。

これで HLSL はこれらのフロー制御のタイプ別にサポートを含むことになります。

  • 静的分岐
  • 命令の予測
  • 静的ループ
  • 動的分岐
  • 動的ループ

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

最もわかりやすい分岐サポートは動的分岐です。動的分岐では、比較条件が変数に常駐します。これは、比較が実行時に頂点ごと、またはピクセルごとに実行されることを意味します (コンパイル時または 2 つの描画呼び出し間で発生するのではなく)。パフォーマンス ヒットは、分岐のコストと、分岐が行われる側の命令のコストの合計です。動的分岐は、スライダー モデル 3 以降で実装されます。これらのモデルで動作する最適化シェーダーは、CPU で動作する最適化 コードに似ています。