![]() |
||||||||||||||||||||||||||||||||||||||||||||||
![]() |
8/20 VertexShader事始め 昨日に引き続いて、今日はVertexShader(以下、VS)についての特集というよりむしろ僕の学習メモです。 VSはレンダリングパイプラインの中ではPSの前段にあたり、VSに入力されるデータとは、DrawPrimitiveメソッドで渡されるVertexBufferに格納された頂点データそのものです。この頂点データを、画面に出力できるようワールド→ビュー→スクリーン変換を行い、スクリーン座標系に変換し、頂点の法線と光源ベクトルなどからライティングを行って頂点の色を決定するのが基本になるわけです。 入出力をおさえてみるまずは前回同様、VSにどんなデータが流れてきて、どんなデータを出せばいいのか、それからおさえてみます。 入出力の方法自体はPSと同じで、それぞれレジスタにマップされています。ただし、出力情報がPSの場合は色データ1つだけだったのに対し、VSでは頂点の位置や色など少々多めになっています。このため、入力用/汎用レジスタ群と出力用レジスタ群が分かれています。 まずは、入力用/汎用レジスタについて
a0レジスタ以外は4次元のSingle型によるベクトルです。vレジスタに具体的に何がどうやって格納されるのかについては、後で使い方と一緒に述べます。 次に、出力レジスタについて
レジスタの機能が分かった所で、やってみましょう 頂点フォーマットをおさえるレジスタの紹介はしたものの、引っかかるのが入力レジスタ群のうち vレジスタです。このレジスタに頂点フォーマットをどうやって認識させるのかという問題があるわけですね。この作業は多少面倒かもしれませんが、ガマンしましょう。 ここでは、例として、ROKLoaderに使われている、六角大王の頂点データを扱うための、TROKVertexを取り上げて説明します。 ROKVertexは、以下に示すように TROKVertex = packed record
Case Integer of
0: (
x,y,z:Single; //頂点
nx,ny,nz:Single; //法線
diffuse:D3DCOLOR; //ディフューズ
tu,tv:Single; //テクスチャ座標
);
1:(
pos:D3DVector;
normal:D3DVector;
);
end;
これをVertexShaderに認識させるために、以下のような配列定数を宣言します。 const
ROKVertex_decl:Array[0..4] of D3DVERTEXELEMENT9 = (
( Stream:0 ; Offset:0 ; _Type:D3DDECLTYPE_FLOAT3 ;
Method:D3DDECLMETHOD_DEFAULT ; Usage:D3DDECLUSAGE_POSITION ; UsageIndex:0 ),
( Stream:0 ; Offset:12 ; _Type:D3DDECLTYPE_FLOAT3 ;
Method:D3DDECLMETHOD_DEFAULT ; Usage:D3DDECLUSAGE_NORMAL ; UsageIndex:0 ),
( Stream:0 ; Offset:24 ; _Type:D3DDECLTYPE_D3DCOLOR ;
Method:D3DDECLMETHOD_DEFAULT ; Usage:D3DDECLUSAGE_COLOR ; UsageIndex:0 ),
( Stream:0 ; Offset:28 ; _Type:D3DDECLTYPE_FLOAT2 ;
Method:D3DDECLMETHOD_DEFAULT ; Usage:D3DDECLUSAGE_TEXCOORD ; UsageIndex:0 ),
( Stream:$FF ; Offset:0 ; _Type:D3DDECLTYPE_UNUSED ;
Method:D3DDECLMETHOD_DEFAULT ; Usage:D3DDECLUSAGE_POSITION ; UsageIndex:0 )
);
上から順に、TROKVertexの各メンバに対応しているというのが分かると思います。各メンバの意味ですが
一番下の要素は終端マーカーであり、TROKVertexの内容とは無関係です。終端マーカーは元々D3DDECL_ENDという定数としても定義されているのですが、配列定数の宣言時には、他の構造型定数は使用できないみたいで、展開した形で書いちゃってます(^^;) こうして、VertexShaderに頂点フォーマットを認識させるための荷札が出来たわけです。では、これをDirect3DDeviceに渡して行きましょう。 var VDecl:IDirect3DVertexDeclaration9; begin DG.D3DDevice.CreateVertexDeclaration(@ROKVertex_decl, VDecl); DG.D3DDevice.SetVertexDeclaration(VDecl); IDirect3DVertexDeclaration9インターフェイスを生成し、D3DDevice.SetVertexDeclarationメソッドによってD3DDeviceに渡します。 使用が終わったら DG.D3DDevice.SetVertexDeclaration(Nil); VDecl.Release; このようにSetVertexDeclarationにNilを指定してから、IDirect3DVertexDeclaration9オブジェクトを解放します。 実を言いますと、オブジェクトモーフ用など、複数のストリームから頂点データを読み込むような必要が無ければ、これに関しては何もやらなくてもVertexShaderはちゃんと動いてくれるみたいです。とはいえ、面倒なのは頂点フォーマットをVS用に宣言しなおす部分くらいなので、これは新しく頂点フォーマットを作る都度行えば問題ないでしょう。 VSアセンブラでゴリゴリといよいよ、VertexShaderアセンブラでの作業に移ります。 とりあえず、ディフューズだけ考慮したライティング(平行光源1つ)と、特に工夫の無いテクスチャマッピングを施すようなシェーダを作ります。尚、入力には先ほどのTROKVertexが来ると想定します。 vs_1_1 //バージョンの宣言
// レジスタへ頂点データを割り当てる
dcl_position v0 // v0に位置ベクトル
dcl_normal v4 // v4に法線ベクトル
dcl_color0 v7 // v7にディフューズ色
dcl_texcoord0 v8 // v8にテクスチャ座標
//c4〜c7に行列が入っていると仮定
//c12にローカル座標系での光線
m4x4 oPos, v0, c4 // ビュー、投影行列で位置ベクトルを変換
dp3 r0, v4, c12 // 光線ベクトルと法線の内積を計算
mul oD0, -r0.x , v7 // ディフューズ色と↑で計算した内積の負の数を乗算して
// 頂点のディフューズ色を決定
mov oT0.xy , v8 // テクスチャ座標はそのままで
とりあえず、vshader.vshとして保存しておきますか。 中身についての解説ですが、dcl_** 命令によって、入力情報である頂点データをvレジスタ群に割り当て、それをもとに計算します。尚、前回はコメントをセミコロンで区切ってましたけど、実は//でもコメントになります。 その他の注意事項については、コメントを参照してください(^^;) 計算といっても、あんまりする事ないんですね、実は。1頂点ごとに行わなければならない計算というのは当然最小限に絞らないとパフォーマンスはガタガタ低下していくので、VSアセンブラはなるだけシンプルに、と。1ピクセルごとに実行されるPSほど神経質にならくても良いという考え方もありますけどね。 で、PSアセンブラ同様、c:\dxsdk\bin\dxutils\vsa.exe でアセンブルします vsa vshader.vsh 生成されたvshader.vsoをD3DDeviceに渡してやれば、カスタムVSが使用できます。この方法もPSとほとんど同じ。 var
ms:TMemoryStream;
VS:IDirect3DVertexShader9;
begin
ms:=TMemoryStream.Create;
ms.LoadFromFile('vshader.vso'); //vsoファイルを読み込み
DG.D3DDevice.CreateVertexShader(ms.Memory, VS); //デバイス
こうして、PixelShaderオブジェクトを生成します。利用するときは DG.D3DDevice.SetVertexShader(VS); まったく簡単です。 呼び出し側もきっちりお次は呼び出し側で行う処理についてです。今までの話の流れでは、DrawPrimitiveメソッドなどで描画させる前に、
が必要になってくるわけです。 //ワールド→ビュー→投影変換行列
var
transMat,tmpTranMat:
begin
//DG-Carad&SXLib用、変換行列の計算
transMat:=Sender.MatrixOnRender;
transMat:=NowCompositeMatrix(transMat,Scene.CameraFrame.ViewMatrix);
transMat:=NowCompositeMatrix(transMat,Scene.ProjectionMatrix);
//こっちは、RenderStateから直接読み込む方法、わりと実践向き
//上のルーチンとやってる事は同じなので、どっちか好きなほうで
DG.D3DDevice.GetTransform(D3DTS_WORLD, transMat);
DG.D3DDevice.GetTransForm(D3DTS_VIEW, tmpTransMat);
transMat:=NowCompositeMatrix(transMat, tmpTransMat);
DG.D3DDevice.GetTransForm(D3DTS_PROJECTION, tmpTransMat);
transMat:=NowCompositeMatrix(transMat, tmpTransMat);
//転置する…VSの4x4行列はOpenGLと同じならびになってて、Direct3Dとは行・列が逆
transMat:=NowTMatrix(transMat);
//頂点変換用の行列を定数レジスタに突っ込む
//Single型16個分をc4レジスタからc5,c6,c7レジスタに渡って突っ込む
DG.D3DDevice.SetVertexShaderConstantF(4, @transMat, 4);
//光線ベクトルの計算…ローカル座標系での光線ベクトルに変換
lv:=NowTransform(Lights[0].Params.Direction,
NowTMatrix(NowExtractRotation(Sender.MatrixOnRender)));
//c12レジスタに突っ込む
DG.D3DDevice.SetVertexShaderConstantF(12, @lv, 1);
行列の略号がMtrxでなくMatになっているのがむずがゆい人も居るかもしれませんが、ガマンして(^^;) 注意すべきは、4x4行列の行と列がVSとDirect3Dでは逆になっている点でしょうか。…うーむ。 ともかく、こうしてVSを呼び出す前のお膳立てが整うわけです。 以上以上でVSの使用についての説明は終わりです。次回からはこれを利用して実際にVS/PSならではの表現技法などについて学習していきたいと思います。 前回は、PixelShader(以下、PS)について取り上げたのですが、PSはVSの出力を受け取って、ポリゴンの色を決定する部分でして、つまりVSとの連携は必要不可欠なわけです。レンダリングパイプラインの中ではVSの後に実行されるPSの方を先に取り上げた理由は、なんかラクそうだからということに他ならないわけですが… ともかく、なんといいますか、VSはPSに比べると幾分面倒です。とはいえ、一度やってしまえば使いまわしや応用が効くものですし、言うまでも無く、非常に高いポテンシャルを持った代物です。
|
|||||||||||||||||||||||||||||||||||||||||||||
![]() |