【Unity】索敵範囲外を暗くする表現の実装


自作ゲームに索敵要素を取り入れてキャラクターの視界外を暗くしたい、そんなときの実装の一例を書いてみます。

環境

Unity 2020.3.20f1
Universal RP 10.6.0

手法

影を描画する手法のひとつであるステンシルシャドウボリュームを応用してみます。

ステンシルシャドウボリュームとは、影を落とすオブジェクトをライト方向から見たときの輪郭となる頂点を引き延ばし、その引き延ばされてできた領域内を影として描画する手法です。
ステンシルバッファを利用するためこの名前がついています。

こちらのサイトに概念がわかりやすくまとめられています。

トゥーン調のキャラクターの顔に落ちる髪の影を(ライトによってできる影だと形を制御しにくいため)ステンシルシャドウボリュームの応用で理想的な形に出す、というような使われ方もします。

実装

実装といってもコードはほぼ書かずに実現できます。

純粋なステンシルシャドウボリュームではなく任意の範囲を影にしたいため、その範囲を覆うメッシュをまず用意します。
わかりやすいようCubeのメッシュで見ていきます。
CubeオブジェクトのLayerは新しく追加したものに変更します。ここでは追加Layer名をCoverとしています。

影を描画するべきCubeの内側の範囲は、表面と裏面の差分で求まります。

差分を描画するために、ForwardRendererDataファイル(デフォルト名はUniversalRenderPipelineAsset_Renderer.asset)をインスペクタから編集します。

  1. Coverオブジェクトは通常描画しないので、FilteringプルダウンのCoverのチェックをはずす
  2. Coverオブジェクトの裏面部分のステンシル値を+1するRendererFeatureを追加
  3. Coverオブジェクトの表面部分のステンシル値を-1するRendererFeatureを追加
  4. ステンシル値が0未満の部分を描画するRendererFeatureを追加

追加した各RendererFeatureの設定は以下のようになります。

裏面/表面の指定は RendererFeatureでオーバーライドするマテリアルのシェーダでやっています。
裏面のみを通すシェーダコードは以下です。表面のみを通すには"Cull Front"を"Cull Back"と書き換えるだけです。

CullFront.shader
Shader "Custom/CullFront"
{
    SubShader
    {
        Tags { "RenderType" = "Opaque" }

        Pass
        {
            ZWrite Off
            Cull Front
            Blend Zero One
        }
    }
}

3つめのRendererFeatureでステンシルが負の値の部分を描画して完成です。

もちろん表現はオーバーライドするマテリアルのシェーダ次第でなんとでも。

メッシュ生成


動的にメッシュ生成する場合、UnityのMeshクラスには三角形化した配列で渡します。
”三角形分割 耳刈り取り法” とかで調べると実装法がいろいろ出てきます


ちなみに、ディファードレンダリングであればワールド座標を格納したGBufferと暗くしたい範囲を書き出したテクスチャを使って.. というアプローチもでき、複雑なメッシュを生成するより楽かもしれません。
UnivarsalRPはいまのところ(ver 10.6.0)ディファード非対応なので、やるならレンダーパイプラインに手を加えたりが必要です。