ステンシルバッファをUnityで利用した表現で、面白かった2D的な表現について書いてみようと思います。
ステンシルバッファとは?
「ステンシルバッファとは」 意味/解説/説明/定義 : IT用語辞典
ステンシルバッファとは、3次元グラフィックス(3DCG)において、物体の重ね合わせなどにより描画しなくても良い領域を効率よく判定するために用意された特殊なバッファ領域。ポリゴンを描画する際にステンシルバッファを参照し、ピクセル単位で描画を禁止したり許可したりすることができる。
要するに、マスクです。
これを使用すれば、マスクに応じて「色を塗らない」や「異なる色を塗る」といった実装が可能になります。
ちなみにuGUIの「マスク」もコレを使用して表現しています。
ステンシルバッファで色々と試す
ステンシルバッファは要するに唯のマスクな訳ですが、マスクはマスクなりに色々と面白い使い方が出来そうです。
壁の向こうにいるキャラクターのシルエットを表現
例えば「壁の向こうにいるキャラクターのシルエットを表現」する際に、ステンシルバッファは使えます。
建物の奥にいる2Dキャラクターを表現する際、キャラクター上に絶対手前に表示するアイコンを表現するのも良いですが、こういった表現も面白いです。
2Dマスク
後は2Dのマスクにも使えます。
これはUnity 5.4で実装予定の2D: Maskingと多分同じ物です。マスクの範囲内だけ表示し、それ以外は透明にしています。
ImageEffectがかからないスプライト表現
ImageEffectで特定のスプライトに対してのみエフェクトをかける事も可能です。Camera二つ用意した方が楽な気もしますが、透過がメインの上に前後関係が激しい2DではDepthを使うタイプは面倒なので、ステンシルでくり抜いてしまいます。
ただ、ステンシルはアルファほど綺麗に抜けないので、アウトライン周りに工夫が必要に見えます。
完全に暗い2Dライト
2Dライト的な奴で見える範囲を限定するのも面白いかもしれません。普通にライトを書くタイプと異なり、ライトの範囲外を完全に隠すことが出来ます。
ここまでやらなくとも、単純にキャラクター前面にマスクを置くだけでも十分面白そうです。
また実際にやる場合は、ライト表現する方(ステンシル書き込んでる方)に「ライト側のエッジを段々と暗くする」みたいな表現が必要に見えます。
ステンシルバッファの挙動 イメージ
ステンシルバッファは少し難し分かりにくい印象がありますが、実際は本当に単純なマスクです。
「バッファ」や「数値」で説明すると分かりにくかったので、簡単なImageEffectを用意しました。このエフェクトは、ステンシルバッファが塗られている箇所に、塗った値に応じて色を付与します。ステンシルが塗られていなければ「黒」を塗ります。
まずは、ステンシルバッファが何も塗られていないシーンを用意します。まだステンシルバッファに値を設定していないので、画面は真っ暗です。
キャラクターにステンシルバッファを1で塗るシェーダーを設定します。ステンシルはシェーダーに設定した内容を元に、スプライトやモデルを描画したタイミングで塗られます。
ステンシル1の部分だけ赤くなるので、エフェクト上はこんな感じでキャラクターが浮かび上がります。
ついでに周囲の星でステンシルバッファを塗ります。星はステンシルバッファ2を設定するので、青く表現されます。
描画順に塗るので、前面に表示されているステンシルが後方に表示しているキャラクターを一部塗りつぶしている所があります。逆も。
最後にステンシルバッファのミソ、ステンシルバッファの塗られている部分の色を変更を行います。
例えば、赤い六角形を塗る際、ステンシルバッファ1が塗られている場所は少し灰色に、ステンシルバッファ2が塗られている場所は赤く塗る…みたいな事をやるシェーダーを作り適応します。
「塗る際にステンシルバッファを判定」しているので、ステンシルバッファの判定より前面に描画されている(後に描画されている)スプライトは、色が変わっていません。
ステンシルバッファを実装するには
ステンシルバッファはシェーダーに設定した値を元に、ピクセルの色を塗るタイミングで処理されます。
シェーダーへの設定について
まず塗る側ですが、シェーダーにこのように記述すると塗られます。
Stencil
{
Ref 1 // どのステンシルバッファに対しての処理か
Comp Always // 実行される条件は(常に)
Pass Replace // 実行されるコマンドは(上書き)
}
読む側はこのように記述します。条件が一致しない場合、そのステンシルが設定されているシェーダーは無視されます。
Stencil
{
Ref 1 // どのステンシルバッファに対しての処理か
Comp Equal // 実行の条件は(バッファの値がRefの時)
}
画像のアルファも使用してステンシルを抜く
ちなみに、そのままだとピクセルではなくポリゴンで塗られるので、下画像上のような大雑把なマスクが行われます。
綺麗にスプライトで抜きたい場合、フラグメントシェーダーで色を塗る際にif (c.a < 0.001) discard;
のような形で、アルファが一定以下ならばピクセルを破棄する等も必要です。。
長くなったので、分割します。
飽きなければ、次は各エフェクトの少し詳しい実装について。