【Unity】ステンシルバッファをポストエフェクトで使う方法(と謎の挙動)

ポストエフェクトでステンシルバッファを使う方法です。

なんかいろいろと挙動(の原因)がわからないところがあるのでメモがてら。
結論だけ知りたい方は「結論」の節まで飛ばしてもらえればわかるようになってます。

Unity2017.4.1

前提

今回のプロジェクトは次の記事で作ったものを使いまわします。

light11.hatenadiary.com

CommandBufferでポストエフェクトをかけることによりステンシルバッファによるアウトラインを実現した記事です。

ダメなケース、いいケース

まず、一般的なやり方でポストエフェクトをかけてみます。
上記の記事のカメラ用スクリプトをCommandBufferを使ったものから一般的なポストエフェクトのかけ方に変えます。

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class StencilOutline : MonoBehaviour
{
    [SerializeField]
    private Shader _shader;
    [SerializeField]
    private Color _outlineColor;

    private Material _material;

    private void Awake()
    {
        _material = new Material(_shader);
        _material.SetColor("_OutlineColor", _outlineColor);
    }
    
    private void OnRenderImage(RenderTexture source, RenderTexture dest)
    {
        Graphics.Blit(source, dest, _material);
    }
}

これをカメラにアタッチします。
ちなみにカメラのMSAA、HDR、オクルージョンカリングは無効にしておきます。

するとなぜかポストエフェクトがかかりません(アウトラインが黄色くなるのが正解です)。

f:id:halya_11:20180508232713p:plain

試しにこの状態で、ポストエフェクト用シェーダのステンシルの比較関数をEqualからNotEqualにしてみます。

Stencil{
    Ref 1
    Comp NotEqual
}

するとアウトライン以外が黄色で描画されるべきはずが、
全面黄色で描画されてしまいます。

f:id:halya_11:20180508232855p:plain

ステンシルバッファがクリアされてるか感じの挙動。
なぜ・・?
FrameDebuggerを見てもいまいちわかりません。

仕方がないので以前の記事と同じようにコマンドバッファでポストエフェクトをかけてみます。
(上で変更したポストエフェクトのシェーダのステンシル設定は戻しておきます。)

[RequireComponent(typeof(Camera))]
public class StencilOutline : MonoBehaviour
{

    [SerializeField]
    private Shader _shader;
    [SerializeField]
    private Color _outlineColor;

    private void Awake()
    {
        Initialize();
    }

    private void Initialize()
    {
        var camera = GetComponent<Camera>();
        var material = new Material(_shader);
        material.SetColor("_OutlineColor", _outlineColor);

        // CommandBufferを登録
        var commandBuffer = new CommandBuffer();
        int tempTextureIdentifier = Shader.PropertyToID("_WorldPostEffectTempTexture");
        commandBuffer.GetTemporaryRT(tempTextureIdentifier, -1, -1);
        commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, tempTextureIdentifier);
        commandBuffer.Blit(tempTextureIdentifier, BuiltinRenderTextureType.CurrentActive, material);
        commandBuffer.ReleaseTemporaryRT(tempTextureIdentifier);
        camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer);
    }
}

再生すると・・

f:id:halya_11:20180508233524p:plain

正常にアウトライン描画されました。
なぜ・・?

まあひとまずステンシルを使えたと安心したところで、
カメラのHDR設定を有効にしてみると、

f:id:halya_11:20180508233936p:plain

アウトラインが描画されなくなりました。
謎は深まるばかりです。

原因がわからず実に気持ちが悪いですが、 謎を謎のままに結論に行きます。

結論

結論としては、下記2つの条件を満たせばステンシルバッファをポストエフェクトで使用できます。

  1. CommandBufferを使ってポストエフェクトをかける
  2. CameraのHDRを無効にする

CommandBufferでポストエフェクトをかける方法は下の記事を参照してください。

light11.hatenadiary.com

原因がわかっていない状態で記事にすんなよって感じですが、ひとまずこれを結論とします。
わかる方いたらご教授いただきたいですm(_ _ )m

追記: 同じような現象に直面していそうな人がいた

同じような現象を先に書かれている方がいらっしゃいました。

qiita.com

OnRenderEmageを定義した時点でテンポラリのレンダーターゲットが作成されたり等内部で色々行うようになるっぽい

とのこと。
たしかにFrameDebuggerで、OnRenderImageを定義した時としていない時とでレンダーターゲットに差分が出てた気がします。

Unityの仕様上仕方ないのか・・?

関連

light11.hatenadiary.com

light11.hatenadiary.com