すぎしーのXRと3DCG

主にXR, Unity, 3DCG系の記事を投稿していきます。

【Unity, XR】Single Pass Instanced向けURP Render FeatureのTips 【2020.3, 2021.2】

概要

UnityのUniversal Render Pipelineには Render Feature という描画処理を差し込む機能があります。
この Render Feature ですが、XRのSingle Pass Instanced 対応をする場合はクセがあります。

今回はそんなSingle Pass Instanced向けのRender FeatureのTipsを共有します。

追記

  • 2022/03/27 「おまけ、IntermediateTextureMode.Alwaysの指定なしでも機能させる方法」項目を追加

略語

用語が長いので、以下では略語で紹介します。

用語 略語
Universal Render Pipeline URP
Render Feature RF
Single Pass Instanced SPI
Render Texture RT

動作環境

  • Unity 2020.3.30f1 + Universal RP 10.8.1
  • Unity 2021.2.16f1 + Universal RP 12.1.6

Tips Unity 2020

ポストエフェクトでは cmd.Blit は使用不可で cmd.DrawMesh を使用

詳細は以下のドキュメントに記載されています。

docs.unity3d.com

※以下、上記ドキュメントを 「SPI Blit Example」と呼びます

NOTE: Do not use the cmd.Blit method in URP XR projects because that method has compatibility issues with the URP XR integration.

互換性の問題でSPIでは cmd.Blit は正しく機能しないようで、
もし、ポストエフェクト的なことを実施したい場合は cmd.DrawMesh + _CameraColorTarget から画面キャプチャを取得して Blit を実現します。

サンプルのColorBlitRenderFeature について

ちなみに SPI Blit Example の ColorBlitRenderFeature のサンプルは描画の緑成分を抽出するポストエフェクトになっています。

f:id:tsgcpp:20220321235931p:plainf:id:tsgcpp:20220321235957p:plain

(2021.2以降) SwapBufferはSPIでは実質的に使用不可

Unity 2021.2 ではRFでポストエフェクトを実装しやすいようにSwapBufferという機能が追加されています。

残念ながらSwapBufferはSPIでは使用できません。。。(もし使用できる場合は教えていただけるとありがたいです!!)。
原因はSwapBufferの処理が隠蔽されていること、SwapBufferを使用するBlitメソッドが cmd.Blit を使用するためです。

※SwapBufferについては以下の動画をご参照ください。

www.youtube.com

画面キャプチャは不透明オブジェクトのみ

さて、ポストエフェクトと言いましたが、URP + SPIにおいては欠点があります。

不透明オブジェクトの描画結果しか取得できません

これは、シェーダーに渡されるテクスチャの名前 (_CameraOpaqueTexture) の通り、
不透明オブジェクトのみ描画した画面キャプチャが渡されるためです。

f:id:tsgcpp:20220323003420p:plain

f:id:tsgcpp:20220322000310p:plain
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing の場合

f:id:tsgcpp:20220322012404p:plain
renderPassEvent = RenderPassEvent.BeforeRenderingTransparents の場合

SwapBufferも使用できないため、残念ながらSPIでのポストエフェクトは透過オブジェクトを対象にできないなど制限が強いです。。。

完全な描画結果が取れない理由

SPIにおいて、スクリーン向けRTにアクセスする術がないためです。

  • ScriptableRenderer.GetCameraColorFrontBuffer が internalメソッドのためアクセスできない
  • (2021.2以降)SwapBuffer用のRTにアクセスできない

Unity 2021.2以降でのポストエフェクトにはIntermediateTextureMode.Alwaysの指定が必要

Unity2021.2から IntermediateTextureMode という設定項目が追加されました。

IntermediateTextureMode.Alwaysを指定しないとSPI Blit Exampleの ColorBlitRenderFeature は機能しません!

docs.unity3d.com

名前の通り、中間テクスチャ(IntermediateTexture)を使用するかの指定になります。

f:id:tsgcpp:20220322004654p:plain

IntermediateTextureMode.Alwaysの指定が必要な理由

指定していない場合は cmd.DrawMesh の描画先が中間テクスチャではなくスクリーンのRTに直接描画されるのですが、
その後、FinalBlitPassによってスクリーンのRTが上書きされてしまうためです。

以下のコードにIntermediateTextureModeによる条件分岐があります。 github.com

SPI Blit Example には、注意書きがないのでIntermediateTextureMode.Alwaysの指定を忘れると Unity 2021.2 では実質的に描画結果に現れません。

判明するまで数時間かかりました。。。一応Report投げときました。

ちなみに非SPI(Multi Pass) や NonXRではSwapBufferが使用可能なので問題になりません。

Unity 2020.3 以前での中間テクスチャ

Unity 2020.3 + Universal RP 10 では IntermediateTextureMode は有りませんが、
実は RF が Forward Rendererに1つでも追加されている場合に、中間テクスチャが自動で有効になっていたようです。

以下のコードの rendererFeatures.Count != 0 という判定からわかります。

github.com

おまけ、IntermediateTextureMode.Alwaysの指定なしでも機能させる方法

IntermediateTextureMode.Alwaysを指定する方が確実と思われますが、一応紹介します。

SPI Blit ExampleのColorBlitPassを以下のようにrenderingData.cameraData.renderer.cameraColorTargetを指定することで、 IntermediateTextureMode.Alwaysを指定していない場合でも機能させることができます。

--- a/Assets/ColorBlitPass.cs
+++ b/Assets/ColorBlitPass.cs
@@ -34,7 +34,7 @@ internal class ColorBlitPass : ScriptableRenderPass
         using (new ProfilingScope(cmd, m_ProfilingSampler))
         {
             m_Material.SetFloat("_Intensity", m_Intensity);
-            cmd.SetRenderTarget(new RenderTargetIdentifier(m_CameraColorTarget, 0, CubemapFace.Unknown, -1));
+            cmd.SetRenderTarget(new RenderTargetIdentifier(renderingData.cameraData.renderer.cameraColorTarget, 0, CubemapFace.Unknown, -1));
             //The RenderingUtils.fullscreenMesh argument specifies that the mesh to draw is a quad.
             cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, m_Material);
         }

IntermediateTextureMode.Alwaysの指定なしでも機能する理由

  • renderingData.cameraData.renderer.cameraColorTargetは基本的に中間テクスチャとなっているため
    • 以下のコードを読む限りcreateColorTextureがtrueの場合に中間テクスチャが描画先にになるのですが、スクリーン(非SceneView)向け描画は基本的にtrueになるみたいです

github.com

createColorTexture == falseになる(中間テクスチャを描画先にしない)パターンのほうが珍しいですが、
SPIの場合で中間テクスチャを描画先にする場合は、renderingData.cameraData.renderer.cameraColorTargetを使用するならIntermediateTextureMode.Alwaysを指定する方が確実だと思います。

URP + SPI で可能な表現の例

不透明オブジェクトの描画結果を使用したポストエフェクト

透明オブジェクトは対象にできませんが、不透明オブジェクトならポストエフェクトをかけることができます。

f:id:tsgcpp:20220321235957p:plain

アルファブレンドによるフェードイン・フェードアウト

シェーダーのアルファブレンドは問題なく使用できるため、フェードイン・フェードアウトは実現できます。

            Blend SrcAlpha OneMinusSrcAlpha
...
            half4 frag (Varyings input) : SV_Target
            {
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
                return half4(0, 0, 0, saturate(_Intensity));
            }

雑感

なんとか3月中に1つ記事が書けました! 本当はAddressablesに関する記事を書いていたんですが、検証の自動テストがGameCIで実現できず。。。

Unity2020でRFを使用すると中間テクスチャが必ず使用されるようになるのも知るきっかけになってよかったです(RF使用する場合のパフォーマンスの懸念自体は増えましたが)。

世間は卒業シーズン、新卒入社で新生活を始める人も多そうですね。 これからUnity、XR始める人にも興味を持っていただければ幸いです!

それでは~