HLSLの基本
■環境
この記事の内容、サンプルは以下の環境のもと書いています。
VisualStudio
VisualStudio 2015
DirectX
DirectX9(June 2010)
シェーダ言語
HLSL
■サンプル
このページでは以下のリンクのプロジェクトを元にして解説を行っていますので
ダウンロードをお願いします。
サンプル
■シェーダプログラム
シェーダプログラムはCPUではなく、GPUに命令を伝えて特定の動作を実行させるプログラムです。
シェーダを使用することでレンダリングパイプラインを固定ではなく、
プログラマブルな形で使用することができます。
●プログラマブル
プログラマブルとはプログラマーがCやC++、Javaなどで
プログラムを作成し、自動化や特定の動作を行わせることです。
通常の言語を使用した場合はCPUがプログラムの内容を実行し、
PCやハードに対して動作を行わせます。
●プログラマブルパイプライン
プログラマブルパイプラインとはGPU内のレンダリングパイプラインを
一定の機能を使って動作を決める固定器のパイプラインではなく、
シェーダプログラムを使って独自の動作を行うことができるパイプラインの名称です。
DirectX10以降やOpenGLES2以降など近年のシェーダは
プログラマブルシェーダを使用することが前提となっています。
■HLSL
HLSLはHighLevelShaderLanguageと呼ばれるシェーダプログラム用の言語です。
HLSLを使用することでGPUに命令を送信し、様々なグラフィカルな挙動や
計算を実行することが可能ととなります。
●シェーダ言語の種類
シェーダ言語はHLSLだけではなく他にもいくつか存在します。
HLSL:
DirectXで使用されているシェーダ言語
マイクロソフトがサポートをしている
GLSL:
OpenGLで使用されているシェーダ言語
クロノスグループがサポートしている
AGAL:
AdobeFlash用のシェーダ言語
アドビシステムズがサポートしている
PSSL:
PlayStation4用のシェーダ言語
ソニーがサポートしている
Metal:
iOS(iOS8以降)で使用されているシェーダ言語
アップルがサポートしている
●HLSLのバージョン
HLSLにはバージョンが存在します。
各バージョン毎に使用できるシェーダの種類や対応しているビデオカードが異なります。
バージョン情報:
Version 1.0~3.0:
対応シェーダ:
頂点シェーダ
ピクセルシェーダ
DirectXのバージョン:
9.0
Version 4.*
対応シェーダ
頂点シェーダ
ピクセルシェーダ
ジオメトリシェーダ
コンピュートシェーダ
DirectXのバージョン
10.0
Version 5.*
対応シェーダ
頂点シェーダ
ピクセルシェーダ
ジオメトリシェーダ
ハルシェーダ
ドメインシェーダ
コンピュートシェーダ
DirectXのバージョン
11.0
Version 6.*
対応シェーダ
頂点シェーダ
ピクセルシェーダ
ジオメトリシェーダ
ハルシェーダ
ドメインシェーダ
コンピュートシェーダ
DirectXのバージョン
12.0
●言語特性
HLSLはCやC++言語と似た文法でプログラムを書くことができる高級言語です。
高級言語なので人間には理解できる内容で書かれますがコンピュータには理解できないので
GPUで実行するにはコンパイルを行い、機械語に変換する必要があります。
●HLSLの構成
HLSLは「.hlsl」または「.fx」という拡張子のファイルを作成し、
そこにプログラムを記述します。
そのプログラムの構成はいくつかのブロックに分けることができます。
■グローバル変数の宣言ブロック
HLSLのグローバル変数はCPU側、GPU側を行き来する橋渡し的な変数です。
ここで宣言した変数をCPU側のプログラムでアクセスして値を設定します。
設定後に再度レンダリングパイプラインへ渡されてシェーダプログラムで使用します。
●書式
書式例:
データ型 変数名;
具体例:
// カラー情報
float4 g_Color;
●データ型
・スカラー型
bool:
true or false
half:
16ビット浮動小数点数
float:
32ビット浮動小数点数
double:
64ビット浮動小数点数
int:
整数
・ベクター型
float4:
float * 4分
・マトリクス型
float4x4:
float * 16分
■テクスチャサンプラブロック
テクスチャサンプラではピクセルシェーダでテクスチャに対してどうのようにアクセスするかを決めます。
具体的にはLINEARなどの補間方法であったり、WRAPなどのテクスチャのUV指定等です。
●書式例
書式:
sampler 変数名;
具体例:
// テクスチャを繰り返して設定する
sampler TextureSampler =
sampler_state
{
Texture = ;
AddressU = WRAP;
AddressV = WRAP;
};
■シェーダ関数基礎
シェーダプログラムもCやC++と同様に関数を作成してそこに必要な処理を書きます。
●書式
戻り値の型 関数名(引数)
{
}
引数の書き方:
HLSLの関数の引数には入力用引数と出力用引数があります。
入力用引数:
書式:
データ型 変数名 : 入力セマンティクス
具体例:
float3 pos : POSITION
出力用引数:
書式:
out データ型 変数名 : 出力セマンティクス
具体例:
out float4 out_pos : POSITION
具体例:
// 頂点シェーダ用関数
void VertexShader(float4 pos : POSITION,
out float4 out_pos : POSITION,
out float4 out_color : COLOR)
{
}
●セマンティクス:
GPUの引数には変数に対してどんなデータが入っているかを設定する必要があり、
それを設定するのがセマンティクスです。
セマンティクスは頂点シェーダ、ピクセルシェーダなどのシェーダ毎に
入力、出力の情報が決められており、それらの情報を引数として設定します。
■頂点シェーダプログラムブロック
頂点シェーダプログラムブロックでは頂点シェーダを制御する関数を書きます。
●頂点シェーダ
頂点シェーダの仕事はローカル座標空間の座標をスクリーン座標空間に変換することや
頂点に設定するテクスチャ座標の設定、陰影処理などです。
これらの処理は各ポリゴンの頂点1つ1つに対して行われます。
上のイメージ図で注意してほしい点は頂点シェーダの変換後は
ラスタライズやピクセルシェーダがあるので頂点シェーダ終了後、
すぐにスクリーンに投影されません。
あくまでイメージです。
●セマンティクス:
・入力セマンティクス
入力セマンティクスの内容はDirectXのFVFデータの内容がほぼそのまま使用されています。
・出力セマンティクス
出力セマンティクスで必ず引数に設定しなければいけない情報は
POSITIONだけで、あとは必要に応じて設定を行います。
・例
void VertexShader(float4 pos : POSITION,
out float4 out_pos : POSITION,
out float4 out_color : COLOR)
第1引数:
float4 pos : POSITION
float * 4 のローカル座標
第2引数:
out float4 out_pos : POSITION
float * 4 のスクリーン変換後の座標
第3引数:
out float4 out_color : COLOR
float * 4 の頂点カラー
■ラスタライズ
ラスタライズは頂点シェーダとピクセルシェーダの間に存在する処理で、
頂点シェーダで決まった座標とポリゴン単位の色を元にして
フレームバッファにピクセル単位で描画情報を設定する処理です。
●ラスタライズの処理内容
ラスタライズが行う処理は主に以下の内容となります。
・線形の補間
フレームバッファに対し、頂点シェーダで変換された
頂点の座標を配置し、頂点同士を線で結びます。
・頂点カラーの補間
線で結ばれポリゴンが作成されたら、
そのポリゴンの内部を走査し、頂点カラーの色で補間を行います。
この補間が行われたポリゴン1つ1つがピクセルシェーダの対象となります。
■ピクセルシェーダプログラムブロック
ピクセルシェーダプログラムブロックではピクセルシェーダを制御するプログラムを書きます。
OpenGLではフラグメントシェーダとも呼ばれています。
●ピクセルシェーダ
ピクセルシェーダはラスタライズで設定された色情報を取得して
そこに何らかの対応を行い、その結果の色情報をGPUへ戻します。
・ピクセルシェーダの注意点
上でも書いていますが、ピクセルシェーダはラスタライズで処理された
ピクセル1つ1つに対して処理を行います。
そのため頂点シェーダと比較した場合、作業コストは大きいです。
画面全体を表示するようなデータがラスタライズに存在した場合、
その画面領域のピクセルに対してピクセルシェーダの処理を
行わなければいけません。
例:
画面サイズ 800 * 600 の場合:
800 * 600 => 480,000
画面サイズ 1920 * 1080 の場合:
1920 * 1080 => 2,073,600
ゲームなど近年の画面解像度は大きくなっていく傾向にあります。
画面の解像度が高くなるとその分だけピクセルシェーダにかかる負担が増えます。
なので、ピクセルシェーダでは複雑な処理は行わないようにして、
簡単な処理を行うように心がけてください。
●セマンティクス
・入力セマンティクス
入力セマンティクスは色情報とテクスチャ座標が設定されます。
・出力セマンティクス
出力セマンティクスは最低限で色情報を返す必要があります。
テクスチャ座標は必要ならば設定します。
・例
void PixelShader(float4 color : COLOR0,
out float4 out_color : COLOR0)
第1引数:
float4 color:COLOR0
ピクセルのディフューズカラー情報
第2引数:
out float4 out_color : COLOR0
処理結果の出力カラー情報(ディフューズ)
■テクニック宣言ブロック
テクニックとはシェーダファイルで作成した関数に対して頂点シェーダや、
ピクセルシェーダなどのシェーダの割り当てを行うための宣言領域のことです。
「頂点シェーダプログラムブロック」と「ピクセルシェーダプログラムブロック」で
構成のブロックとして分けていますが、実際の関数定義の順番はどちらでも構いません。
また、関数名も両ブロックともに指定もないので実際にシェーダを起動した際に
どの関数が頂点、またはピクセルシェーダの関数なのかコンパイル側では判断ができません。
そこで、使用するシェーダとその関数の設定をするためにテクニックが用意されています。
●書式
書式例:
technique テクニック名
{
pass パス名
{
vertexShader = compile バージョン 頂点シェーダ関数();
pixelShader = compile バージョン ピクセルシェーダ関数();
}
}
具体例:
technique StandardDraw
{
pass normal
{
vertexShader = compile vs_3_0 VertexShader();
pixelShader = compile vs_3_0 PixelShader();
}
}
■DirectXでのシェーダ使用(初期化)
DirectXでHLSL言語を使用して描画を行うにはシェーダの初期化を行う必要があります。
初期化の手順は以下の流れです。
①.頂点フォーマットの定義
頂点フォーマットは通常の2Dや3Dと同じようにシェーダで使用する
頂点情報を詰め込んだ構造体を作成します。
例:
struct VERTEX
{
D3DXVECTOR3 m_Pos; // 頂点情報
DWORD m_Color; // カラー情報
};
※この頂点フォーマットの設定ではRHWの要素は入れてはいけません。
設定してしまうと頂点シェーダの処理が飛ばされてしまいます。
②.頂点シェーダで使用する頂点データの宣言
①で定義した頂点フォーマットの内容はシェーダ側に伝える必要があります。
伝達用に用意された構造体がD3DVERTEXELEMENT9です。
頂点情報は項目1つ1つに対して設定する必要があります。
例えば①で設定したVERTEX構造体では「頂点情報」「カラー情報」の二つを
設定しているのでD3DVERTEXELEMENT9は最低でも3つ必要です。
2つは「頂点情報」「カラー情報」最後の1つは「終了情報」です。
D3DVERTEXELEMENT9ではデータがここで終了するという内容の設定を行い
必ず配列の最後に追加する必要があります。
D3DVERTEXELEMENT9
WORD Stream:
ストリーム番号
WORD Offset:
頂点情報のオフセット値
①のVERTEX構造体では最初の座標はオフセット0、
次のカラーはfloatのx、y、zの次にあるので、
4 * 3でオフセット値は12となります。
BYTE Type:
変数の型
座標 => D3DDECLTYPE_FLOAT3
カラー => D3DDECLTYPE_D3DCOLOR
BYTE Method:
テッセレーションの方法
基本的にD3DDECLMETHOD_DEFAULTで問題なし
BYTE Usage:
このデータの使用目的の設定
座標 => D3DDECLUSAGE_POSITION
カラー => D3DDECLUSAGE_COLOR
BYTE UsageIndex:
Usageの詳細設定
カラーでは頂点カラーとスペキュラカラーがあるが
D3DDECLUSAGEはD3DDECLUSAGE_COLORしかないので、
ここの数値でどちらのデータかを判別する
D3DDECLUSAGE_COLORのインデックス
0 => 頂点カラー
1 => スペキュラカラー
終了設定:
終了設定は以下の内容で設定するか、D3DDECL_END()を指定します。
D3DVERTEXELEMENT9の終了設定:
Stream
0xFF
Offset
0
Type
D3DDECLTYPE_UNUSED
Method
0
Usage
0
UsageIndex
0
具体例:
以下は①の頂点設定でD3DVERTEXELEMENT9を宣言しています。
struct VERTEX
{
D3DXVECTOR3 m_Pos; // 頂点情報
DWORD m_Color; // カラー情報
};
D3DVERTEXELEMENT9 element[3]; // 頂点、カラー、終了
// 頂点情報
element[0].Stream = 0;
element[0].Offset = 0;
element[0].Type = D3DDECLTYPE_FLOAT3;
element[0].Method = D3DDECLMETHOD_DEFAULT;
element[0].Usage = D3DDECLUSAGE_POSITION;
element[0].UsageIndex = 0;
// カラー情報
element[1].Stream = 0;
element[1].Offset = 12;
element[1].Type = D3DDECLTYPE_D3DCOLOR;
element[1].Method = D3DDECLMETHOD_DEFAULT;
element[1].Usage = D3DDECLUSAGE_COLOR;
element[1].UsageIndex = 0;
element[2] = D3DDECL_END();
③.頂点データのシェーダ側での宣言
②で作成した頂点データを使用してシェーダ用のデータに変換し、
シェーダ側で同じ内容の頂点データの宣言を行います。
変換には「DirectXDevice」のCreateVertexDeclarationを使用します。
CreateVertexDeclaration
関数名:
CreateVertexDeclaration
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
D3DVERTEXELEMENT9 *:
頂点データのポインタ
(頂点データの配列の先頭アドレス)
LPDIRECT3DVERTEXDECLARATION9:
第一引数で渡した頂点データをもとに宣言した
頂点シェーダ用頂点データ格納用
内容:
第一引数の頂点データの配列を元にして
頂点シェーダ側で頂点データの宣言を行う
④.シェーダの作成
シェーダの作成(読み込み)はD3DXCreateEffectFromFile関数で行います。
D3DXCreateEffectFromFile
関数名:
D3DXCreateEffectFromFile
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
LPDIRECT3DDEVICE9 pDevice:
DirectDeviceのポインタ
LPCSTR pSrcFile:
ファイル名
CONST D3DXMACRO* pDefines:
現状はNULL
LPD3DXINCLUDE pInclude:
現状はNULL
DWORD Flags:
シェーダコンパイル時のオプション
LPD3DXEFFECTPOOL pPool:
現状はNULL
LPD3DXEFFECT* ppEffect:
作成されたシェーダデータを
格納するLPD3DXEFFECTのアドレス
LPD3DXBUFFER *ppCompilationErrors:
シェーダコンパイルエラーのデータを格納する
LPD3DXBUFFERのアドレス
⑤.テクニックの取得
④で無事コンパイルがシェーダの作成が完了したら
そのシェーダからテクニックの取得を行います。
テクニックの取得はID3DXEffectの「GetTechniqueByName」関数を使用します。
GetTechniqueByName
関数名:
GetTechniqueByName
戻り値:
成功:D3DXHANDLE
失敗:NULL
引数:
LPCSTR pName:
テクニック名
内容:
指定した名前のテクニックが存在するか調査し、
存在すればそのテクニックのハンドルを、
存在しなければNULLを返す
D3DXHANDLE:
今回のテクニックや次に解説するグローバル変数など
シェーダでアクセスすることが可能な情報をDirectX側でアクセスする場合
D3DXHANDLEというハンドルを使用します。
このハンドルはWinAPIのハンドルと同じ存在で、
シェーダはこのD3DXHANDLEでテクニックやグローバル変数を管理しています。
⑥.パラメータ(グローバル変数)取得
グローバル変数もよく使用するので初期化のうちに取得を行っておきます。
取得にはID3DXEffectの「GetParameterByName」関数を使用します。
GetParameterByName
関数名:
GetParameterByName
戻り値:
成功:D3DXHANDLE
失敗:NULL
引数:
D3DXHANDLE hParent:
親のハンドル
LPCSTR pName:
パラメータ名
内容:
指定した名前のテクニックが存在するか調査し、
存在すればそのテクニックのハンドルを、
存在しなければNULLを返す
■DirectXでのシェーダ使用(描画)
シェーダを使用した描画は、DirectDeviceのBeginSceneとEndSceneの間で行います。
バッファのクリア(Clear)やバッファの転送(Present)も必要です。
シェーダ描画の流れは以下の内容となります。
①.頂点フォーマットの指定
描画で使用する頂点フォーマットの指定を行います。
SetFVFと同じだと考え問題ありません。
指定にはID3DXEffectの「SetVertexDeclaration」関数を使用します。
・関数仕様
関数名:
SetVertexDeclaration
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
LPDIRECT3DVERTEXDECLARATION9:
使用する頂点フォーマット
内容:
描画で使用する頂点フォーマットの指定する
②.テクニックの設定
描画で使用するテクニックの指定を行います。
指定にはID3DXEffectの「SetTechnique」関数を使用します。
・関数仕様
関数名:
SetTechnique
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
D3DXHANDLE handle:
テクニックのハンドル
内容:
描画で使用するテクニックを引数に指定する
③.パラメータの変更
グローバル変数の値を変更する場合はSet***を使用します。
「***」の部分はVectorやMatrixなどです。
・関数仕様
関数名:
Set***
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
D3DXHANDLE handle:
変更するグローバル変数のハンドル
*** data:
変更するグローバルの値
内容:
グローバル変数の値を変更する
SetVectorやSetMatrixなどがある
④.パラメータの更新
BeginScene~EndSceneの間でグローバル変数を更新した場合は
GPU側のデータを更新する必要があります。
更新はID3DXEffectの「CommitChanges」関数を使用します。
・関数仕様
関数名:
CommitChanges
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
なし
内容:
グローバル変数の値を更新する
⑤.シェーダ(テクニック)起動
シェーダは描画と同様に関数を通して開始と終了を宣言し、その中でパス指定や描画を行います。
開始はID3DXEffectの「Begin」関数を使用します。
・関数仕様
関数名:
Begin
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
UINT* pPasses:
起動したシェーダのパスの数
DWORD Flags:
デバイスステートの保存設定フラグ
内容:
シェーダを起動する
⑥.パス指定
シェーダを起動したら次は描画で使用するパスの指定を行います。
パスの指定はID3DXEffectの「BeginPass」関数を使用します。
・関数仕様
関数名:
BeginPass
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
UINT Pass:
起動したシェーダのパスの数
内容:
引数で指定した番号のパスを使用する
⑦.描画
描画はDirectXの「DrawPrimitive」などを使用します。
CPU側で専用処理をすることは特にありません。
⑧.パス終了
描画処理が終了したパスは終了を宣言する必要があります。
パスの終了はID3DXEffectの「EndPass」関数を使用します。
・関数仕様
関数名:
EndPass
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
なし
内容:
パスを終了する
⑨.シェーダ(テクニック)終了
シェーダによる描画が完了したら最後に終了を宣言します。
パスの終了はID3DXEffectの「End」関数を使用します。
・関数仕様
関数名:
End
戻り値:
HRESULT
S_OK:成功
E_FAIL:失敗
引数:
なし
内容:
シェーダを終了する