【Unity】ステンシルを利用したトランジション表現

に公開

秋涼の候、本ブログをご覧のゲーム好きの皆様におかれましてはTGS2025での発表も記憶に新しいことと存じますが、いかがお過ごしでしょうか。

ゲーム事業部 技術課のK.Yです。
近頃は、描画の最適化対応やシェーダーの作成など、専ら描画周りの業務にあたっています。

今回はUnityのステンシルバッファを利用した面白い描画表現を試してみましたので、簡単な解説を添えて紹介いたします!
拙稿ではございますが、最後までお付き合いいただけますと幸いです。

概要

さて、早速ではございますが、完成した動画をご覧ください!

https://youtu.be/iEwkqXMawLY

ご覧いただいたのは、キャラクター(ユニティちゃん)はそのままに、ローポリで構成された背景がエフェクトの内側だけリアルなルックの背景に切り替わる様子です。
(プログラマの手によるもの故、アート面のクオリティについてはご容赦を……!)

オブジェクトごとに表示・非表示を切り替えるのではなく、エフェクトの形状に背景をくり抜いて、別の背景を表示する……という見た目を実現しています。
視覚的に派手な効果ではありますが、実装にあたっては大仰なスクリプトを書いたり特別なアセットを用意したりする必要はございません。

最低限必要なものは、ステンシル関連の設定を扱えるシェーダーのみです!
むしろそれ以外はこれといった対応をしておりませんので、要点さえ分かれば簡単に再現していただけることと存じます。

それでは、具体的に何をしているのか見ていきましょう。

解説

前提

ステンシル関連の設定を扱えるシェーダーが最低限必要だと前述いたしました通り、今回の実装で肝要なのはステンシルの操作です。
はじめに、背景の書き換えに使用するステンシル参照値をステンシルバッファに書き込み、後の背景描画時にステンシル参照値を比較して背景の書き分けを行う……というのが、基本的な考え方です。
ちなみにここでいう“ステンシル参照値”はShaderLabのStencilにおける“Ref”シンタックスを指しています。

具体例

対応が必要なステンシル操作について順序立てて示すと、以下のように表すことができます。
①Materialごとのステンシル設定の準備
ステンシル値書き込み用エフェクトのMaterial、ローポリ背景のMaterialおよびリアルルック背景のMaterialのステンシル値を任意の共通の値に設定する。

◆ステンシル値書き込みエフェクト用Materialのステンシル設定を以下のように変更します。
・比較条件(“Comp”シンタックス)を“Always”に設定し、常にステンシルテストに合格させる。
・ステンシルテスト合格時のバッファ操作(“Pass”シンタックス)を“Replace”に設定し、指定したステンシル値を書き込めるようにする。

Stencil
{
        Ref (任意の値)
        Comp Always
        Pass Replace
}

※他の用途でもステンシルを使用する場合は、共存させられるような設定に変更する必要があります。

◆ローポリ背景用Materialのステンシル設定を以下のように変更します。
・比較条件(“Comp”シンタックス)を“NotEqual”に設定し、指定のステンシル値以外が書き込まれたピクセルをテストに合格させる。
・ステンシルテスト合格時のバッファ操作(“Pass”シンタックス)を“Keep”に設定し、あらかじめ書き込んだステンシル値を変更しないようにする。

Stencil
{
        Ref(任意の値)
        Comp NotEqual
        Pass Keep
}

◆リアルルック背景用Materialのステンシル設定を以下のように設定します。
・比較条件(“Comp”シンタックス)を“Equal”に設定し、指定のステンシル値が書き込まれたピクセルのみテストに合格させる。
・ステンシルテスト合格時のバッファ操作(“Pass”シンタックス)を“Keep”に設定し、あらかじめ書き込んだステンシル値を変更しないようにする。

Stencil
{
        Ref(任意の値)
        Comp Equal
        Pass Keep
}

② エフェクトの描画
描画タイミングが背景オブジェクトよりも先になるようにRendering Queueを調整し、ステンシルバッファにステンシル値を書き込む。

③ ローポリ背景、およびリアル背景オブジェクトの描画

――以上の手順が、エフェクトで背景を書き分けるための最低限の対応です。
補足ですが、コードブロックで示しているのはステンシル設定の例であって、「このようにシェーダーに記載すべき」という例ではないことにご注意ください。
シェーダーをどのように運用するかの想定によって対応が変わるところではありますが、扱いやすさという観点では、ステンシルの設定項目をShader Property化しておき、Material側で任意の設定を変更できる形で持っておくのが無難かと存じます。

また、キャラクターやその他演出の影響を受けないオブジェクトについては、特に別途対応の必要はございません。
ステンシルに関係のない描画であれば、そのまま通常通り描画されるはずです。

発展

さて、これまでステンシルを利用したトランジション表現の実装についてお伝えいたしましたが、これはあくまでも最低限の実装であって、実用レベルではありません
なぜならステンシルによる描画のマスクは隠蔽されたRendererのカリングを行うものではないからです。

つまり、見えていないオブジェクトであっても描画コストが乗ってしまうのです!
ステンシルテストによって不合格となったピクセルは描画されないので、その分のフラグメントシェーダーのコストはカットできるものの、頂点シェーダの実行コストや、シェーダプロパティのGPUへの転送など、フラグメントシェーダーの手前でかかるコストは必要になります
特に描画物の多い場面での利用や、モバイル端末での利用を想定している場合は、このコストがボトルネックとなる可能性が非常に高いです。
ステンシルによって完全に隠蔽されるオブジェクトを判定するオクルージョンカリングを実装するなどして、描画コストを抑える手段を用意して、実用レベルに引き上げましょう!

低負荷で効果的な演出を実現できると素敵ですね。

最後に

本記事ではステンシルを利用した演出の一案をご紹介させていただきました。
今回お伝えした内容が、少しでも皆様のお役に立てたならば幸いです。

基本的に、見栄えの良さと描画コストは表裏一体です。
良いルックを目指して実装した表現のせいでパフォーマンスが低下して画面がカクカク……となってしまっては本末転倒ですので、描画負荷にも注意を向けて、最小のコストで最高の効果を得られる実装を模索していきましょう!


◆動画制作に使用させていただいたアセット

本稿内で紹介している動画の制作にあたって、ユニティちゃんを使用しています。
© Unity Technologies Japan/UCL

Discussion