ホーム < ゲームつくろー! < プログラマブルシェーダ編 < ピクセルシェーダプログラムの基礎


その5 ピクセルシェーダプログラムの基礎


 その4でピクセルシェーダの流れについて説明しました。この章ではピクセルシェーダプログラムについて見ていくことにしましょう。幸いなことに、その殆どは頂点シェーダと変わりないのですが、ピクセルシェーダ独自の部分も多少あります。



@ GPUがピクセルを処理する流れ

 ピクセルシェーダもやはりGPUが内部でどのような処理を行っているかを理解しなければ使いこなすことができません。GPUの内部処理を以下の図に示します。


 頂点シェーダの後にラスタ化された「補間ディフューズカラー」「補間スペキュラカラー」がv0およびv1入ってきます。一方SetTexture関数を用いた場合、そのテクスチャカラーが「補間テクスチャ0(t0)」…「補間テクスチャ7(t7)」に入ってきます。テクスチャカラー部分は読み込みだけでなく書き込みもできます。これによってテクスチャを動的に変化させることが可能になります。

 ピクセルシェーダで目新しいのは「サンプリングステージレジスタ(s0-s15)」でしょう。これは、取得するピクセルに対するフィルタが設定されていて、これを使うことでテクスチャを特殊な方法で読み込むことができるようになります。ただこのs#の値は直接読むことができない「擬似値」でして、texld及びtexldという「テクスチャアドレッシング命令」で使われます。今は、あまり気にしない方が良いかもしれません(^-^;

 ピクセルシェーダプログラミングに書かれた数々の演算は「ピクセル算術演算ユニット」で行われます。計算の結果は最終出力レジスタとして出力されます。これはoC0〜oC3の4つのカラー出力とoDepthという1つの深度出力です。「色を4色も出力するの?」と思われるかもしれません。これは「マルチエレメントテクスチャ」というテクスチャを作成した時に必要となります。マルチエレメントテクスチャは、1つのピクセルに複数の色情報を含んでいる特殊なテクスチャです。もしビデオカードがこのテクスチャをサポートしているなら、oC1〜oC3の部分が有効になります。しかし、多くの環境に対応させるのであれば、使用しない方が賢明です。

 ピクセルシェーダ2_0の場合、少なくともoC0に色情報(RGBA)を出力する必要があります。またバージョン1.1〜1.4の場合はr0(一次レジスタ)に値を入れるとそれが色出力とみなされます。バージョンによって出力先が全然違うので気を付けて下さい。バージョン2.0以降で深度oDepthを使用しなかった場合は、ラスタ化された時の深度が使用されます。1.1〜1.4ではoDepthがありません。



A ピクセルシェーダプログラム

 では、早速簡単なピクセルシェーダプログラムで感じをつかんでみることにしましょう。例として挙げるプログラムは次のようなものです。

ピクセルシェーダプログラムの一例
const char PxShader[] =
   "ps_1_1 \n"
   "def   c0,   0.2989f,   0.5866f,   0.1145f,   0.0f \n"  // 彩度算出係数
   "tex   t0                                          \n"  // テクスチャ
   "dp3   r0,   t0     ,   c0                         \n"  // 彩度Y算出(r.aに格納される)
   "mov   r1,   r0.a                                  \n"  // r0の各成分をYで埋める
   "lrp   r0,   c1     ,   t0     ,   r1              \n"; // 線形補間( v0 + c1*(Y-v0) )


 これはポリゴンに貼り付けられたテクスチャの彩度を変化させるピクセルシェーダです。
 まず、ピクセルシェーダプログラムの先頭にはバージョン情報を付記します。それは「ps_2_0」のようにpsの後にアンダーバーでバージョンを記します。
 次の行のdefというのはピクセルシェーダ内で定数を指定する時につける命令です。ピクセルシェーダの定数は4次元ベクトルで定義されます。上の例の場合、c0という定数レジスタには次のように数値が入ります。

c0 = (c0.x, c0.y, c0z, c0.w) = ( 0.2989f, 0.5866f, 0.1145f, 0.00f )

 この数値は何か?これはRGBから彩度を計算するための係数です。彩度には色々な考え方があるのですが、ポピュラーに使われているのが次の式です。

彩度Y = 0.2989*R + 0.5866*G + 0.1145*B

 ここでRGBはそれぞれ赤・緑・青成分で、0〜255までの整数です。左辺のYが彩度で、これも0.0〜255.0の間の実数になります。この彩度Yの値でRGBをそれぞれ置き換えると、そのピクセルは彩度Yの灰色になります。今回はこれをにさらに一工夫します。

 さて、次のtexというのは「テクスチャを使いますよ」という命令です。テクスチャカラーはt0〜t7までありますが、ここではt0を用います。この宣言以降、IDirect3DDevice9::SetTexture関数で指定したテクスチャステージ0番のテクスチャの色が使えるようになります。

 次の行「dp3」というのは「dot prduct」すなわち「内積」をする命令です。内積というのは2つのベクトルの同じ成分同士を掛けて、結果を全部足し算する計算です。つまり、上の命令は次のような計算をしているのと同じ意味になります。

r0 = t0c0 = t0.r*0.2989 + t0.g*0.5866 + t0.b*0.1145 + y0.a*0

 この式は先程紹介したRGBから彩度を計算する式です。つまり、ここではr0に彩度を格納したことになります。ちなみに、内積の結果は1つの数値(スカラー)でして、その結果はr.aとアルファ情報のところに入ると決められています。そこで、次のmov、つまり「コピーしなさい」という命令でr0.aの値をr1に格納しています。r0.aという1つの数字をr1というベクトルにコピーすると、ベクトルの全ての数値が同じr0.aになります。

 この段階で、r1=(Y, Y, Y, Y)となっています。もちろんこれをr0に入れればグレー画像になるのですが、そのままじゃ面白くないじゃないですか。そこで、最後に一工夫しています。それが「lrp」という線形補間命令です。これは次のように定義されます。

lrp  dest,  src0,  src1,  src2

そして、次のような計算をしてくれます。

dest = src0 * ( src1 - src2 ) + src2

上の命令にこれを置き換えると、

r0 = c1 * ( r1 - t0 ) + t0

この式をグラフにすると次のようになります。

 もしc1=0だったら、式よりr0 = t0となり、最終出力色はテクスチャ自体となります。これがc1が1に近くなるにつれてグラフにありますように少しずつYの値に近づき、c1=1.0になるとRGBとも全部Y、すなわち灰色になってしまいます。つまり、最終的にこのピクセルシェーダプログラムは、c1の値によってテクスチャの彩度を上げ下げすることができるというわけです。


 どうでしょうか。しっかり読み解くとそれほど難しいことはしていない事に気付かれると思います。肝心なのはどういう命令があるかを知ることかもしれません。もし線形補間命令lrpを知らなければ、プログラムはもう少し面倒なことになります。頂点シェーダもピクセルシェーダも、その命令群についてはマニュアルに詳しく記載されていますので、最初の頃はそれをじっくり見て、バージョンごとの命令の特徴をつかんでおいた方が良いでしょう。

 頂点シェーダの時も同じことを書いたのですが、特にピクセルシェーダの場合は参考図書やネット上に素晴らしいエフェクトの実装方法がゴロゴロと転がっています。そのようなエフェクトをたくみに利用すれば、ゲームの見た目の質はとてつもなく向上するはずです。○×でも今後ピクセルシェーダの応用例を掲載していく予定です。

 尚、今回のピクセルシェーダプログラムの実装例を早速サンプルプログラムにしましたのでご活用下さい。