ホーム < ゲームつくろー! < プログラマブルシェーダ編 < プログラマブルシェーダーって何?

その1 プログラマブルシェーダーって何?


 Direct3Dをある程度読み解けるようになったころ、ちらちらと目に付いてくる用語の1つが「プログラマブルシェーダ」ではないでしょうか?これ、気になるんですよね。でも、多くの日本人にとってこの用語から内容はイメージしづらいものです。プログラマブルシェーダーって何なのでしょうか?いや、そもそも「シェーダー」って何?この章では以後の章の前知識としてこれらについて見ていくことにしましょう。



@ プログラマブルシェーダーとは?

 シェード「Shade」というのは「描画法」という意味です。それに「-r」が点いてシェーダー(Shader)となると「描画する人」という意味になります。ちなみにシェーダーを辞書で引くと「映像制御技師」とありました。これらからイメージすると、Direct3Dにおいてシェーダーというのは、描画を担当する部分ということになります。

 描画を担当する部分というのは、具体的にどういうことなのでしょうか?これを理解するには、Direct3Dがどのように描画を行っているか、どうしても知る必要があります。まず、以下のフロー図をご覧下さい。


 これはDirectX9の日本語版マニュアルに載っている図を拝借したものです(マニュアル「Direct3D アーキテクチャの概要」)。ただ、これについての説明は日本語版に乏しいので、英語版の同じ箇所である「Direct3D Architecture」に記載されている内容で説明します。

 このフロー図はプログラマが与えた入力情報をDirectXがどのように処理して画面に出力するか(レンダリングするか)を表しています。非常に細かい過程を経ていますね。この中でオレンジ色で示した2箇所、ここが「プログラマブルシェーダ」で変更することができる箇所です。これまで私たちが意識せずともDirect3Dを使えたのは、デフォルトでの動作が内部に設定されていたからです。こうみると、シェーダプログラミングと言うのは全体の限られた部分であることがわかると思います。

 左から順にその動作を説明していきましょう。

 「頂点データ」とはローカル座標で定義され頂点バッファに格納されている頂点データのことです。頂点バッファの中には位置や頂点の色、テクスチャ座標などが設定されています。Vertex Buffer, IDirect3DVertexBuffer9などが関係します。

 「プリミティブデータ」とは基本的な図形、例えば点、線、三角ポリゴンなどで構成されている幾何学図形で、序列(インデックス)バッファとして定義されます。Index Buffer, IDirect3DIndexBuffer9などが関係します。頂点バッファとインデックスバッファはどちらかがあっても、また両方あってもかまいません。

 プログラマが作成した頂点の情報は、「テセレーション(tessellation)」に渡されます。これは、ポリゴンを細かく分割する部分です。例えば、ディスプレースメントマップという既存のポリゴンを三角ポリゴンに細分化する技術で、テセレーションの一種です。通常、この機能はサポートされないことが多いので、何もせずに通り抜ける時もあります。

 そして、次に来るのが「固定機能パイプライン」もしくは「プログラマブルパイプライン(頂点シェーダ)」です。この両者が行うことは決まっています。それは「3Dのポリゴンの情報を直方体の座標系に変換する」という作業で、私たちはすでに「固定機能パイプライン」、すなわちワールド変換、ビュー変換および射影変換を知っていて、何気なく使っています。プログラマブルパイプラインは、プログラマがこの頂点変換作業を独自の方法で記述するわけです。「固定機能パイプライン以外の記述方法があるのか?」と疑問に思うかもしれませんが、使い道は沢山あります。

 頂点変換パイプラインを通った座標は直方体の座標系にぎゅっと凝縮されています。これを正面から見れば、2Dのスクリーン画像ができあがるわけなんですが、その時に、必要の無い情報が沢山含まれています。「背面カリング」「クリッピング」「属性の評価」「ラスタ化」というのは無駄を省きながら、要らない情報を捨てていく部分に当たります。
 「背面カリング」はカメラから見て後ろを向いているポリゴンを削除してしまいます。これにより大幅に描画数が減ります。
 「クリッピング」はカメラの範囲外にあるポリゴンを描かないようにする処理です。
 「属性の評価」というのはやけに抽象的なのですが、英語版にも「Attribute evaluation」とあるので、そのまんまの訳です。これは多分、ポリゴンの表面材質やライトによる頂点カラーの変更などを評価しているのだと創造します。
 そして最後に「ラスタ化(rasterization)」を行います。ラスタ化というのは、背面カリングやクリッピングなどの処理が終わった状態で、スクリーンに投影されるポリゴンとそのスクリーン座標を決定します。どのスクリーン座標にどのポリゴンが描画されるのかを決めるわけです(実際に描画するわけではありません)。

 ここまでで、頂点の変換およびスクリーンでの位置決めが終了します。

 次からは頂点ではなく、描画される1点1点について各種描画処理と判定を行う作業に移ります。その一番最初にあるのが「ピクセルシェーダ」です。とは言うものの、実はピクセルシェーダはラスタ化の1つです。これはDirectXのマニュアル(ピクセルシェーダ)に「ピクセル シェーダは、三角形をラスタ化するときにピクセル単位で実行される短いプログラムである。」とあります。つまり、ラスタ化の段階で「ポリゴンをスクリーン上にどうやって描画するか?」がここに記述されるわけです。最終的に出力されるのは「ピクセルカラー」となります。プログラマはピクセルシェーダを独自にカスタマイズすることで、すばらしいエフェクトを数々と起こすことができるようになるわけです。

 図のピクセルシェーダには、下から来る流れがあります。これはテクスチャに関する流れです。「テクスチャサーフェイス」は、ご存知の通りテクスチャ情報のことを指します。これもプログラマが与える入力情報で、Direct3D Texture、 IDirect3DTexture9などと関連します。

 「テクスチャサンプラ」は入力されたテクスチャに各種フィルタを適用します。例えば線形補間フィルタは、テクスチャの拡大縮小時に中間的なテクスチャの色を線形補間します。その他の各種フィルタはD3DSAMPLERSTATETYPE 列挙型としてまとめられています。これにより、ピクセルシェーダは指定のテクスチャ座標(テクセル)に対して適切な色を取り出すことができるようになります。

 ピクセルシェーダによりスクリーン上でのピクセルカラーが決まったら、あとは「それを描くのか、描かないのか」という判定を沢山行います。一番右側の枠は、その判定を表しています。
 「アルファテスト」はアルファ情報(透明度情報)を用いて、そのピクセルを描画するかしないかを決定するテストです。多くのピクセルフォーマットにはアルファ情報が格納されていまして、それとテスト値を比べます。「描画する」と判定されたピクセルだけが次の「深度テスト」へ進みます。
 「深度テスト」は、スクリーン座標においてそのピクセルが描かれる予定の2D座標上に、よりカメラに近いピクセルがあるかどうかで描画の有無を判定するテストです。スクリーンは2Dですが、この段階でもピクセルは奥行きの情報を持っています。通常、もし同じ(X,Y)座標において判定しているピクセルが最前方にあるのならば、深度テストに合格し、そのピクセルは描画候補として次の「ステンシルテスト」に進みます。さらに、その最前位置は「Zバッファ」というスクリーンと同じ大きさを持ったメモリに書き込まれます。
 「ステンシルテスト」はそのピクセルを描画するか否かをユーザがある程度自由に決めることができるように設けられたテストです。例えば、画面の左端50ピクセルに描画して欲しくないときなどに、ステンシルバッファ(スクリーンと同じ大きさ)にそれを判定する値を埋め込んでおきます。もし(10,20)などのピクセルがこのテストにやってきたら、このバッファの同一の位置の値を読みに行きます。「そこは描画しない」と決めた値のはずなので、このテストを通ることができず、以後の描画は行われなくなります。つまり、ステンシルテストによって、かなり強制的にピクセルの描画の有無をプログラマが決められるのです。ステンシルバッファを通過したピクセルは、「フォグテスト」を受けます。
 「フォグテスト」はカメラの位置とピクセルの深度を利用して、そのピクセルに霧がかったような効果を追加します。結構オプション的なテストです。これにより、相当遠くにあるピクセルは白(別の色にもできます)に塗りつぶされてしまうので、描画を稼ぐことができます。
 最後の「ブレンディング」は、すでにバックバッファ描かれているある座標にあるピクセルと同じ座標である自身が持つ色及びアルファ情報から新しいピクセルの色とアルファ値を決定します。いわゆる「アルファブレンド」というのはここで行われるわけです。ここでようやく、バックバッファにピクセルが描画されることになります。



A プログラマブルシェーダとGPU

 以上長々と説明してきましたが、Direct3Dの描画ではこんなに凄い過程を高速で処理しているわけです。これもひとえにビデオカード上にある「GPU(グラフィックプロセッシングユニット)」のお陰です。上記の計算をハードウェアとして処理してくれるので、DirectXは高速なわけです。

 プログラマブルシェーダとは、この高速なGPUを自らが扱うことに他なりません。@で示したオレンジの部分について、GPUに与える独自の命令群を差し込むわけです。CPUもそうですが、プロセッシングユニットに与える命令は通常「アセンブラ」で記述します。頂点シェーダおよびピクセルシェーダには、それぞれGPUに命令を与えるためのアセンブラ命令群が揃っています。この命令群にはバージョンがあり、頂点シェーダ及びピクセルシェーダで何ができるかは、そのバージョンによって決まってきます。GPU側にも同様のバージョンがあり、指定のバージョンがなければ、プログラマブルシェーダは失敗してしまいます。

 頂点シェーダ及びプログラマブルシェーダのバージョンは、2006年6月の時点で次のようになっています(Feb2006マニュアル参照)。

シェーダ バージョン
頂点シェーダ 1_1 2_0 2_x 3_0
ピクセルシェーダ 1_1 1_2 1_3 1_4 2_0 2_x 3_0

 もちろん最新バージョンの方ができる事が多いのですが、いかんせんGPUがそれをサポートしていなければ使うことができません。よって、沢山のユーザをターゲットにする場合は、バージョンを落とす必要があります。現行のグラフィックスカードであれば2_0はサポートしていると思われますが、あくまでも目安です。2_xというのはバージョン2_0の拡張版で、カード独自の仕様があったりします。使うのには注意が必要です。そのうち3_0も普通にサポートされるようになると思いますが、誰でもというのはまだちょっと先のようです。

 もし可能であれば、ゲームを開始する時にシェーダのバージョンをチェックして、各バージョンにあわせたプログラマブルシェーダを差し替えると、多くの人にゲームをしてもらえますが、もちろんこれはプログラマの負担を激増させます。



 ということで、この章ではプログラマブルシェーダがどういうもので、どこで使用されるのかを説明してきました。これまでシェーダという言葉は聞いた事があっても何だか良くわからなかった方は、大分イメージがつかめたのではないでしょうか。そして、敷居が高い感じもしたかもしれません。しかし、一般にプログラムは「習うより慣れろ」だと私は思いますので、以後の章で超簡単なシェーダプログラムを始めることにしましょう。