Unityで半透明(Translucent)なParticleのDepthBufferを取得したかった話

Unity
更新2021/05/23
結論うまく行ってないんだけど、試行錯誤の備忘録として。

Unityで半透明なParticleのDepthBufferをとりたい

## なぜ?
すると半透明なパーティクルのDepthBfferというかテクスチャを取得する必要がある。

## むずかしさ

Depth Textureを取るために[Unity公式の案内 に沿ってカメラの深度テクスチャを取ろうとすると落とし穴がある。
_CameraDepthTextureを使うと半透明のオブジェクトのDepthは無視されるのだ。

ちなみに説明もそれなりに罠で
不透明(opaque)オブジェクト(マテリアルとシェーダーはレンダーキュー <= 2500)を使用するのに設定される)はデプステクスチャにレンダリングされることに注意してください。
https://docs.unity3d.com/ja/2018.4/Manual/SL-CameraDepthTexture.html
と書いてあるが、これは訳が怪しく、英語のページをみると
Note that only “opaque” objects (that which have their materials and shaders setup to use render queue <= 2500) are rendered into the depth texture.
https://docs.unity3d.com/Manual/SL-CameraDepthTexture.html
と書いてあって render queue 2500未満のものしか拾わないからねという意味であることがわかる。

これはやや困りで、半透明なオブジェクトすなわちTranslucentなオブジェクトはrender queue2500からの数値を振られる。alpha testすなわちcutoffとかはギリギリ入るのだが、Particleを半透明で表示させてる場合は無視されてしまう。

_CameraDepthTextureはなぜ半透明オブジェクトを拾わないかその気持はわからないが、その実装はおそらく0-2500までのrender queue拾ってきてとハードコードされているからのような気がしている ->【Universal Render Pipeline】_CameraDepthTextureの実装を読んでみた

なんとかしたい

DepthBufferは _CameraDepthTexture の専売特許ではないはずなので、なんとかしてDepthBuffferを引きずり出す方法を探す。
するとCommandBuffer経由でDepthを引き釣り出している例を見つけた。

【Unity】無駄なドローコールなしで深度バッファを取得する方法  のでこちらでできないか試してみる。
紹介されている方法から少し変更を加えて、ダイレクトにDepth buferをRenderTexureに引っ張ってみる。
private void Start()
{
    m_Camera = GetComponent<Camera>();

    // カラーバッファを生成
    m_ColorBuffer = new RenderTexture(Screen.width, Screen.height, 0);
    m_ColorBuffer.Create();

    // 深度バッファを生成
    m_DepthBuffer = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.Depth);
    m_DepthBuffer.Create();

    m_Camera.SetTargetBuffers(m_ColorBuffer.colorBuffer, m_DepthBuffer.depthBuffer);
    AddCommand();
}

private void AddCommand()
{
    // DepthBufferをセットするコマンド
    {
        CommandBuffer command = new CommandBuffer();
        command.name = "Set depth texture";

        command.SetGlobalTexture("_DepthTexture", m_DepthBuffer);
        m_Camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, command);
    }

    // DetphBfferを出力用のRenderTexture m_FinalBuffer に描きこむコマンド
    {
        CommandBuffer command = new CommandBuffer();
        command.name = "blit to Back buffer";

        command.SetRenderTarget(m_FinalBuffer);
        command.Blit(m_DepthBuffer, m_FinalBuffer, mat);

        m_Camera.AddCommandBuffer(CameraEvent.AfterEverything, command);
    }
}
これで、DepthBuffer(右下)をかろうじて取れるようになった。



一方で2つ問題が残った。

### カメラのターゲット先になっているDisplayから別のDisplayに移動すると、DepthBufferの更新が止まる。

どうやらそういうものらしい。CommundBufferがそういうものなのだと思う。
これまでRenderTexureをする場合、Displayが違っても問題なく書き出されていたから行けるものだと思って探し回ったが、有力な情報はなかったので諦め。

### テクスチャのアルファがDepthに反映されない。

DepthはたしかにAlphaを無視するので、それはそうなのだが、Alphaが明確にゼロのテクセルは透過してほしい。
DepthBfferの書き出しはShadowCasterタグがついたshaderで行われるという。なので、Unityで透明なオブジェクトにDepth of Fieldが効かない時の対処法に習いShadowCasterのPixcelShaderでAlphaがゼロのときはDiscrodするようにしてみた。

しかし改善せず。。。

そもそもこのDepthを書き出すときにShadowCasterは関与していなさそうだった。それならこのDepthBufferはいったいどのShaderが書き出したんだ...? 謎は深まるばかり。

撤退

一応は半透明パーティクルでもDepthを引っ張り出せたので、作りたい表現的に一定なんとかなりそう。ただひたすらに気持ち悪さが残った。仮設だが、Particleという仕組み自体がやや特殊で素直なPipelineに当てはめることはできないのかもしれない。

そして自分がMRTまったくワカランということだけがひたすらわかった...。
ほんとになんもわからんので、知見ある方いたらぜひ教えてください...。

書いた人/このブログについて

サーバーサイドエンジニア。お仕事ではRubyとPerl。趣味ではC#やHLSLなどを少しと、3DCGでPythonをごまかしながら使う感じ。Unityとインフラ周りも好き。

自分の備忘録もGoogle検索に任せたくてこのブログ書いてる。Phoenix製でEC2上で稼働中。