過去の独りごち/独りごとはこちら
過去のJavaアプレットは
こちら

 

8/18 PixelShader事始め

 いつのまにやらこのサイトも6周年記念日が過ぎ去り、夏のイベントにも恙無く物見遊山…と

 爆裂プログラマンさん、Ko-Taさんに感謝します。

 いろいろと。ホントに。

 ともかく、ネット社会復帰を宣言した以上は何かかまさないといけなくなってしまいました。困ったなぁ…いやいや、従来のテンションに戻れば良いだけ、何のためらいが、何の障碍があらんや。

 さて、復活第一回はPixelShader(以下PS)特集です。というより、僕の学習用メモ

 そんな唐突に…と思われるやもしれませんが、実際のところ僕ぁPixelShaderの事を忘れちゃったというよりむしろあんまり理解せずにここをこんな風にいじればなんとかなるっぽという程度にやっていたもので全く応用の効かない状態でした。

 実のところサンプル作って満足満足うむうむ、という状態だったもので。

 しかし、そんなんじゃいけない。もっと高いところを目指さねばならない。ひょっとしたらシェーダ書いてメシの食えるステキ人物になれるかもしれないご時世、頑張らなきゃねぇ。

 なに?うちじゃPSなんて積んでないからなんとかしろ?

 邪魔する奴は指先一つでダウンです(゚∀゚)

 繰り返しになりますが、以下は僕の学習用メモですので、間違いなどありましたらご指摘お願いします。インタラクティブ学習メモ、いいね。

 …今回はまず初めという事で、PS1.1に則って話をすすめます。1.4や2.0での拡張については追々。また、HLSLには言及せず、PSアセンブラに論点を絞ります。

PixelShaderって、なんだ

 まずは、PSの入力と出力が何なのかを明らかにする事で、PSについて捉えてみましょう。

 一言で言うと、PSとはレンダリングパイプラインのうち、テクスチャ座標、頂点色などの入力情報からポリゴン(のうち1ピクセル)の色を決定する部分です。画面に表示されるポリゴンの1ピクセルごとにPSのルーチンが呼び出されるのです。

 かつてはポリゴンの色というのはRenderStateで決定されていたわけですね。ポリゴンの頂点の色と、その線形補間、それに数枚のテクスチャの色をどうブレンドするか、と。
 しかし、近年、フェイクバンプマップやらフェイク反射マップやら、色々なテクニックが開発されるに当たり、テクスチャのルックアップの方法一つ取っても、従来のようにポリゴンの頂点に割り当てられたテクスチャ座標から線形補間するだけといった方法ではダメで、テクスチャ座標に更に一次変換をかましたりと色々な工夫が必要になってきました。
 当然、そういった種々のオプションについていちいちRenderStateで賄おうとすると、まっさらなポリゴンでも条件分岐の嵐をかいくぐる事になり、パフォーマンスが余計に低下することになるというのは想像に難くないですし、従来までのノリでRenderStateの拡充によって表現技法を増やそうとしていると、将来的にDirectGraphicsはRenderStateのオバケになってしまうのは言うまでもないわけです。
 PixelShaderとか訳分からんものが増えてイヤだなぁ…とぼやくのではなく、RenderStateでがんじがらめになるのが回避されたことを我々は喜ぶべきなのですね。

 さて、PS1.1への入力・出力ともレジスタに割り当てられていて、特別に出力するための手続きとか、入力をゲットするための手続きとかは無く、簡単になっています。で、そんなレジスタの種類と役割について列挙します。

 

名前 アクセス制限 役割
c (constant register) 読み出しのみ 8  定数格納用、IDirect3DDevice9.SetPixelShaderConstantI/B/Fメソッドによって設定できます。
 また、def命令によってPSアセンブラで設定する事も出来ます。
r (temporary register) 読み書き可 2  汎用レジスタ。
 r0レジスタはPSの出力としても用いられます。
 EAXが関数の戻り値になっているDelphiみたいでなんかいいですねぇ。
t (texture register) 読み書き可 4  テクセルを参照するためのレジスタです。
v (color register) 読み出し 2  ポリゴンの頂点色を格納してあるレジスタです。
 一般にv0レジスタにはディフューズ成分が格納され、v1レジスタにはスペキュラ成分が格納されるようです。

 全てのレジスタは4つのSingle型で構成される、ベクトルを格納しています。

 色を格納するレジスタはARGBそれぞれを-1.0〜+1.0のレンジで格納しています。但し、vレジスタには0.0〜1.0の範囲で色を格納されています(負数が無い)

とりあえずやってみよう

 以上を踏まえてPSアセンブラでプログラミングしてみましょう。アセンブルの仕方とかはその都度説明するとして、PSアセンブラのソースを書いていきます。

 わざわざPSでやるまでも無いんですが、ポリゴンの地の色と、テクスチャの色を乗算する、ごく普通のテクスチャ付きポリゴンを描画するためのソースを書いてみます。

ps_1_1   			;バージョンの宣言
tex		t0		;1ステージ目のテクスチャをt0レジスタに割り当てます
mul		r0,v0,t0	;r0にディフューズ色とテクスチャの色を乗算したものを格納
add_sat	r0,r0,v1	;さらにスペキュラ色を飽和加算

 ファイル名はshader.pshとでもして、保存しておきましょう。

 アセンブルには、psa.exeを使います。DirectX9SDKをインストールしたディレクトリがc:\dxsdkと仮定すると、c:\dxsdk\bin\dxutils\psa.exe として入っているはずです。パスを通しておくと便利かもしれませんね。

 して、コマンドプロンプトから

psa shader.psa

 と打てば、shader.psoが生成されます。

 あとは、これをDirect3DDeviceに読んで貰えばOK、以下、DGをTDGCaradオブジェクトとして説明します。

var
  ms:TMemoryStream;
  PS:IDirect3DPixelShader9;
begin
  ms:=TMemoryStream.Create;
  ms.LoadFromFile('shader.pso'); //psoファイルを読み込み
  DG.D3DDevice.CreatePixelShader(ms.Memory, PS); //デバイス

  こうして、PixelShaderオブジェクトを生成します。利用するときは

DG.D3DDevice.SetPixelShader(PS);

 たったこれだけ。自前のピクセルシェーダの使用をやめて、固定機能ピクセルシェーダに戻したい時は、SetPixelShader(Nil) とすればOKです。

 なお、現時点ではDG-CaradはD3DDeviceのロストに伴うPixelShaderの自動復元などは行わないので、自前で復元する手続きを用意しなければならないのが難点ですが、近いうちに対応したいですね。 

InstractionFlowについて

 PS1.1を扱う上で考慮しなければならないのが、この概念です。

 困った事に、PSではx86アセンブラと違って、いつでもどこでも好きな命令を使う、というわけには行きません。具体的には、

  1. バージョンの宣言
  2. 定数の定義
  3. テクスチャアドレスの変更(texture instruction)
  4. 算術命令(arithmetic instruction)

 という順序でしか命令を実行できないようになっています。つまり、最初に定数から値を読み込んで、定数同士を加算や乗算などした結果からテクスチャアドレスを変更して…といった事は出来ないのです。テクスチャアドレスを変更するのはtexture instructionにカテゴリ分けされた命令群だけなのですが、これのリファレンスを見ると…まぁ、結構不便です(^^;)

 PS2.0からはこの制限はなくなっているので、結構ゴリゴリとプログラミングできてしまいます。

 狙うなら2.0以降かッ…Ge4ユーザ全滅ですが

次回

 以上でPixelShaderが使えるようになりました。

 ただ、見てお分かりの通り、固定機能VertexShaderをそのまま使っているとPixelShaderへの入力がかなり限られるので、折角のポテンシャルが活かしきれないと言えます。折角のアイディアもInstructionFlowの餌食になってしまうPS1.xではなおさらです。

 そういうわけで、VertexShaderの習得はやはり不可欠。次回はVertexShaderに焦点をあててみたいと思います。

 

Taku Hayase(SANDMAN)

戻る