太郎Work

Unityとかで困ったこと等を残しておきます

Unityでステンシルを用いたアウトライン表現

f:id:tarowork:20140725005608p:plain
最終的にこんな感じになります。
WebPlayer版 Windowsで見ると表示がおかしい可能性があります
Stencil Test

はじめに

なぜ急にアウトラインかというときっかけは白猫プロジェクトでした。
遊んでいて真っ先に気になったのがタイトルにもあるキャラクターのアウトライン表現でした。
f:id:tarowork:20140724004610p:plain
プレイ画面のキャプチャ


この画像を見るとただのアウトラインではなく、非常に面白い表現になっていることがわかると思います。

  1. キャラクター単体のアウトラインは一番外側のみで顔の輪郭等には発生しない。
  2. 別のキャラクターと重なった部分にアウトラインは発生しない
  3. ステージの裏側に行くとアウトラインの色で塗りつぶされる

よくある手法なのかもしれませんが、非常に見やすくきれいな見た目だったので、自分なりにUnityを用いて作ってみました。
関係者ではないのでアプリではどのような実装がされているか分かりませんが、実際に作ってみると面白いものが出来上がったので個人的に記事を書こうと思います。

ちなみに白猫は課金が鬼畜ですが、作り込まれていて非常に面白いです。
現状の大きな不満点といえばやり込んでくるとタウンを開くときのロード時間が結構長くなることぐらいです。
よければ招待コードどうぞ CTWW47FXA
白猫プロジェクト | 株式会社コロプラ【スマートフォンゲーム&位置ゲー】

ステンシル

これを見てパッと思い浮かんだのはステンシルを使ったマスク化だったのでその方面で実装してみます。
ステンシルでいろいろ試しているうちに実は、アウトライン描画のZTestをOffにして背景を先に描画するだけでもこのような表現になることに気づきました。
f:id:tarowork:20140725004856p:plain
意味分かんないサンプルですみません…

しかし、ステンシルを使って出来ることを試すためにも今回は別の手法を試してみます。

ステンシルが何かは
wgld.org | WebGL: ステンシルバッファ |
こちらのサイトが非常にわかりやすく解説されています。
WebGLですが、考え方は全く同じです

簡単にまとめると

  • DepthBufferは奥行き情報で描画するかどうか判定
  • StencilBufferは領域で描画するかどうか判定
  • パスしたピクセルをFragmentShaderで描画

こんなかんじです。

OpenGLで利用できるStencil系の呼び出しはUnityのShaderLabでも「Stencil」で定義されています。調べてもあまりヒットしないので空気になっている気がしますが、Unity4.2からPro版のみアクセスできるようになっています。
iOS,AndroidでもUnityが動作するOSであれば基本的に対応しているはずです。今のところ確認したすべての端末で動作します。
Unity側のStencilリファレンスはこちら
Unity - Unity Manual

アウトライン

ステンシルの扱いがわかったところで本題のアウトラインを実装してみます。今回は書き込み番号はあまり考えていませんが、他のステンシル情報と競合しないようにマスク指定をしたほうがいいのかもしれません

1. まずキャラクター以外のモデルを通常描画します。この際ステンシル1番に書き込み

Stencil {
  Ref 1
  Comp always
  Pass replace
}

f:id:tarowork:20140727164852p:plain
見た目ではまだ何も分かりません

2. 次にキャラクターモデルの法線を外側に伸ばしたメッシュをアウトラインとして描画、ステンシルの2番に書き込みます。
アウトラインを最前面に持ってくるためZTestもOffにします。
また、今回も例によってユニティちゃんを使用しているのですが、カリングをしてしまうと本来の見え方と変わってしまうのでステンシル側も指定する必要があります。

Stencil {
  Ref 2
  CompFront always
  CompBack always
  PassFront replace
  PassBack replace
}
// unityちゃん対応
Cull Off
// ステンシルにのみ書き込む
ColorMask 0
ZTest Off

f:id:tarowork:20140727165455p:plain
一回り大きいモデルが描画されます。この画像はわかりやすいようにColorMaskを指定していません。実際は一つ前の画像のように何も描画されていないように見えます

3. キャラクターシェーダの2パス目で本体を描画します。ここではステンシル3番に書き込みます

Stencil {
  Ref 3
  CompFront always
  CompBack always
  PassFront replace
  PassBack replace
  FailFront replace
  FailBack replace
//ZFailBack replace
//ZFailFront replace
}

ZFail のみコメントアウトしていますが、外すと隠れている部分もステンシルが書き込まれます。
つまりこの指定だと手前に何も来なければ3番を書き込むことになります

f:id:tarowork:20140727170637p:plain
アウトラインはColorMask 0を指定しているので見えていません。

これで準備完了です。ここで現在書き込まれているステンシル番号をまとめてみます。
f:id:tarowork:20140727193006p:plain
分かりにくくなってしまいましたがステンシルバッファにはこのような値が書き込まれています。右側は実際には見えないので想定になります…

これでもうアウトラインが描画できることがわかるかと思います。
2番の部分のみに描画するだけです

Tags { "RenderType"="Overlay" "Queue"="Overlay" }
Stencil {
  Ref 2
  Comp equal
}

f:id:tarowork:20140727184018p:plain
しかも好きなテクスチャでアウトラインが書ける!

比較部分と参照値を変えるといろいろできます。

最後に

きっかけは白猫でしたが、実際に細かく使ってみると思っていた挙動と違っていたりしてかなり勉強になりました。調べても実用的なものがあまり出てこないので手探りになってしまいましたが、興味をもった方もいろいろ試してみると面白いと思います。

ステンシルに書き込んでおくことでアウトライン以外にも面白い表現を思いついたのでそちらは次の記事に書こうと思います。