ホーム < ゲームつくろー! < DirectX技術編 < ステンシルバッファって何?


その18 ステンシルバッファって何?


 ステンシル(stencil)というのは「型板(かたいた)」「型抜き染め」という意味です。blow a pigment over the stencil(型紙を置いた上から染料を吹き付ける)などの例文にあるように、下にある絵を覆う紙や板をステンシルと言います。Direct3Dでのステンシルバッファというのも正にそれでして、描画の際に点を塗るか塗らないかを決めてくれます。ステンシルバッファをどうやって設定するのか?この章ではそういうステンシルバッファの基本を見ていくことにします。



@ ステンシルバッファで出来る事

 ステンシルバッファは描画の際に色を画面に表示するかしないかを決める「マスク」の役目を成します。これはポリゴンの前後関係を保持するZバッファよりももっと直接的な作用です。マスクですから「描画したくない部分」を明確に定義できます。しかも、オンタイムでマスクを変更できます。これがステンシルバッファの魅力です。通常静的に用意するマスクは変更が難しいのですが、ステンシルバッファはそれを自由に行えるのです。

 Direct3Dのマニュアルに載っているステンシルバッファのテクニックには次のようなものがあります。

・ 合成
・ デカール
・ ディゾルブ・フェード、およびワイプ
・ アウトラインとシルエット
・ 両面ステンシル

なんだかカタカナばっかりですか、簡単に説明します。

 合成はステンシルバッファの基本的な使い方です。背景の画像に対してステンシルマスクを適用し、マスクされた部分を別の画像に置き換えます。別の画像はすでに用意した画像でも良いですし、3Dレンダリングしたものでも可能です。

 デカール(decal: 転写シール、ステッカー)は、ポリゴン表面に何らかの色を別に貼り付けます。これは合成を応用したテクニックです。ポリゴンの表面に物を貼り付けるためにビルボードなどを適用する事はよくあるのですが、この時問題になるのが「zファイティング」と呼ばれるちらつきです。背景となるポリゴン表面と貼り付けるポリゴンのZバッファ(深度バッファ)はほぼ同じになってしまいます。すると、深度バッファではどちらのポリゴンを前面に持ってくるかを判断できず、貼り付けるポリゴンが出たり消えたりしてしまいます。これがちらつきの原因となります。デカールを用いると、貼り付けるポリゴンの背景を「描画しない」ために、このちらつきは起こらなくなります。

 ディゾルブ・フェード・ワイプは動きのある画面合成の典型的な方法です。ディゾルブとは2つの画像の切り替えの事です。ステンシルバッファはマスクレベルを設定できます。例えばレベルを挙げるとマスク領域が大きくなって、貼り付ける面積が増えていきます。逆にレベルを下げていくと背景が生かされるようになります。ステンシルバッファを用いるとこのように動的に画像を切り替えることができます。フェードはディゾルブの特殊なもので、白や黒の画像を用意して画面を切り替えていきます。ワイプもディゾルブの特殊な使い方で、ステンシルバッファを動的に変更する事によって貼り付ける画像を左から右へ、または上から下へのように動的に差し替える事が出来ます。

 アウトラインとシルエットは、ステンシルバッファの面白い使い方の1つです。描画するオブジェクトよりもちょっとだけ小さいステンシルバッファを用意してオブジェクトを描画すると、外側だけがうっすらと描画されて、アウトラインを引いたかのようになります。またステンシルバッファ部分だけを別の色で塗りつぶせばシルエットとなります。

 両面ステンシルというのはステンシルバッファを用いた影を再現する時のバグ回避と最適化の技術の事で、Direct3D9から採用されているようです。

 ステンシルバッファ自体は大分前からあった古い技術ではあるのですが、工夫次第で多種多様な効果を挙げる事ができます。そのテクニックは現在進行形で、今もなお新しい効果が生み出されています。



A ステンシルバッファの作成

 ステンシルバッファについては、習うよりも慣れろ感が強いように思えます。さっそくその設定方法を見てみることにしましょう。Direct3DではステンシルバッファはZバッファに埋め込まれています。つまり、Zバッファを作成する時にステンシルバッファも作成するように仕込みます。

ステンシルバッファの初期化
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;    // 24ビットZバッファ8ビットステンシルバッファ作成


HRESULT hRes = g_pD3D->CreateDevice(
               D3DADAPTER_DEFAULT,
               D3DDEVTYPE_HAL,
               hWnd,
               D3DCREATE_SOFTWARE_VERTEXPROCESSING,
               &d3dpp,
               &g_pd3dDevice
               );

if( FAILED(hRes) )
   return false;

太文字の部分がZバッファと一緒にステンシルバッファを定義している部分です。実際はハードウェアがサポートしている深度バッファをちゃんとチェックした方が良いです。作成に成功すると、バックバッファと一緒の大きさのZバッファ・ステンシルバッファが作成されます。 



B ステンシルバッファのプロセス

 ステンシルバッファはレンダリングステートを巧みに利用して描いていきます。実際にはバックバッファに描きこむのと同様の方法で作っていきます。まず最初にやる事はステンシルバッファをクリアする事です。

ステンシルバッファのクリア
g_pd3dDevice->Clear(0, 0,
          D3DCLEAR_STENCIL | D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
          D3DCOLOR_XRGB(0,0,0),
          1.0f,
          0          // ステンシルバッファを0にクリア
          );

 これはバックバッファやZバッファのクリアの部分ですが、D3DCLEAR_STENCILフラグを加え、また第6引数にクリアしたい値を入れます。ステンシルバッファを8ビットにした場合、0〜255の値を設定できます。

 ステンシルバッファをクリアした後は、ステンシルバッファへの書き込みを有効にするためレンダリングステートを変更します。

ステンシルバッファへ書き込み宣言
g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, TRUE );

この状態でレンダリングするとステンシルバッファにも書き込まれるのですが、バックバッファやZバッファにも書き込まれてしまいます。そこで、通常ステンシルバッファに書き込みを行うときにはZバッファへの書き込みを中止します。

Zバッファへ書き込みを禁止
g_pd3dDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_NEVER );

D3DRS_ZFUNCはZバッファの判定について決めるフラグで、D3DCMP_NEVERにすると判定がすべて不合格になります。つまり、Zバッファがすべて通らないので、バックバッファに一切書き込みがされなず、ステンシルバッファのみ書き込みになります。

 ステンシルバッファに書き込みを行うときには、「ステンシルマスク値」「ステンシル参照値」そして「ステンシルバッファ値」という3つの値を比較していきます。ステンシルマスク値というのはステンシルバッファの有効ビットを指定するマスクです。ステンシルバッファを8ビットで定義しているのであれば0x000000ffと設定します。ステンシル参照値はステンシルバッファ値を分ける値であり、この値を変えることでマスクが変化していきます。Direct3Dでは、まずステンシル参照値とステンシルマスク値の論理積(AND)を取ります。その答えとステンシルバッファ値が比較されて、合否が決定されます。

 ステンシルマスク値、ステンシル参照値の設定は以下の通りです。

ステンシルマスク、ステンシル参照値
// ステンシルマスク
g_pd3dDevice->SetRenderState( D3DRS_STENCILMASK, 0x000000ff );
// ステンシル参照値
g_pd3dDevice->SetRenderState( D3DRS_STENCILREF, 0x01 );

 もう少し説明があります。ステンシル参照値とステンシルバッファを比較するといっても、その比較の仕方が色々と考えられます。例えばステンシル参照値と同じものだけを合格とするとか、ステンシル参照値以上のものを合格とするなどということです。これを定めるためには、レンダリングステートに比較関数の設定を表すD3DRS_STENCILEFUNCを渡し、例えば次のようなフラグを設定します:

ステンシルバッファの比較関数
g_pd3dDevice->SetRenderState( D3DRS_STENCILEFUNC, D3DCMP_LESS );

D3DCMP_LESSにすると、ステンシル参照値よりも小さなステンシルバッファ値が合格(ピクセルデータが通る)となります。第2引数の比較フラグは他にも次のようなものがあります。

フラグ 意味
D3DCMP_NEVER 完全不合格
D3DCMP_LESS < 小さければ合格
D3DCMP_EQUAL == 等しければ
D3DCMP_LESSEQUAL <= 以下なら
D3DCMP_GREATER > 大きければ
D3DCMP_NOTEQUAL != 等しくなければ
D3DCMP_GREATEREQUAL >= 以上なら
D3DCMP_ALWAYS 必ず合格

ステンシルバッファの初期値はD3DCMP_ALWAYSで、描かれたものは何でも合格にします。

 さて、これまで「合格」「不合格」と表現してきましたが、合格・不合格になるとどうなるのか?それを定義するレンダリングステートがちゃんとあります。ステンシルバッファとZバッファではステンシルバッファ→Zバッファの順に判定が行われまして、ステンシルバッファにパスしなかったピクセルについてもその振る舞いを細かく設定できます。設定できる筋道は3通りです。

フラグ ステンシル Zバッファ
D3DRS_STENCILPASS 両方に通った時の振る舞い
D3DRS_STENCILZFAIL × Zバッファで引っかかった時の振る舞い
D3DRS_STENCILFAIL × × 両方ダメだった時の振る舞い

これらフラグに対する振る舞いにも沢山の種類があります。

フラグ
D3DSTENCILCAPS_KEEP 何もしない
D3DSTENCILCAPS_ZERO 0にする
D3DSTENCILCAPS_REPLACE 基準値で置き換え
D3DSTENCILCAPS_INCRSAT +1する(最大値ではそのまま)
D3DSTENCILCAPS_DECRSAT -1する(最小値ではそのまま)
D3DSTENCILCAPS_INVERT ビット反転
D3DSTENCILCAPS_INCR +1する(最大値では0に戻す)
D3DSTENCILCAPS_DECR -1する(最小値では最大値に戻す)

 大分ごちゃごちゃとしてきました。図にしてみます。

 何らかのステンシルバッファ値に対して、参照値(16)を決めて、比較(大なり)の結果合格した場合(そのまま)とそうじゃない場合(0にする)での振る舞いを決めてあげています。

 諸々の設定をした後は、通常のレンダリングを行うだけです。レンダリングの結果はZバッファテストをすべて不合格にしてあるのでステンシルバッファのみに記録されます。
 次にステンシルバッファを使用したままZバッファを有効(D3DRS_ZFUNC, D3DCMP_ALWAYS)にすると、両方のテストに合格したピクセルがバックバッファに生成されるようにします。

 ステンシルバッファは「何をしたいのか」を明確にすれば、おのずと設定する項目が決まってきます。デカールをしたいならば、参照値を1などにして描いた部分はすべて使用する設定にすればよいですし、最近流行の擬似HDR(High Dynamic Range)でも明るくしたい部分を参照値で抽出して、その画像をさらに加工するというプロセスを踏みます。

 今回の章では、ステンシルバッファの基本的な設定方法に視点を置きました。技術についてはまたの機会にします(^-^;