CORETECH ENGINEER BLOG

株式会社サイバーエージェント SGEコア技術本部 技術ブログ

エディタバージョンの互換性を保ちつつRenderGraphに対応する【NOVA Shader】

はじめに

こんにちは。 サイバーエージェントゲームエンターテイメント事業部・SGEコア技術本部(以下、コアテク)のグラフィックスチーム所属の畳です。
コアテクでは、多機能なパーティクルシェーダーであるNOVA ShaderOSSとして公開しています。
前回はAdaptive Probe Volumesを自作シェーダーに対応させる方法についてご紹介しました。

今回は、NOVA Shader バージョン 2.7 にて実施した RenderGraph 対応に関する知見をもとに、Unity バージョンの互換性を保ちながら RenderGraph に対応する方法をご紹介します。
本記事は Unity エディター バージョン 6000.0.35f1 を使用して執筆しています。

RenderGraphとは

RenderGraphとは、描画処理を効率的に管理するためのフレームワークです。
URPにおいてはUnity6から導入された仕組みです。
RenderGraph を活用するには、ScriptableRenderPass などで専用の記述が必要です。
こちらについてはコアテクブログの一連のRenderGraph解説記事をご覧ください。
blog.sge-coretech.com

NOVA Shader ではバージョン 2.7 より RenderGraph に対応しており、本記事ではその対応における Tips を交えてご紹介します。

RenderGraph対応における互換性の考慮

NOVA Shader には背景を歪ませる Distortionディストーション)が実装されており、この機能は ScriptableRendererFeature を使用して構成されています。
Distortion は爆発の熱気、水中の揺らぎ、ポータルや魔法効果などで利用される一般的な表現技術です。

Unity6 以降では、従来の ScriptableRenderPass.Execute メソッドなどが非推奨(Obsolete)となりました。 RenderGraph に対応するには RecordRenderGraph メソッドを実装する必要があります。
また、互換性の高い設計を考慮する場合、Unity のバージョンごとコンパイルできようコードを分岐させる必要があります。
そのため、次のようにエディタバージョンのプリプロセッサディレクティブを利用して条件分けを行います。

public class MyPass : ScriptableRenderPass
{
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        // 各種処理
    }

#if UNITY_2023_3_OR_NEWER
    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        // 各種処理
    }
#endif
}

ここでのポイントは、Execute メソッドは Unity 6 環境でも有効化されている点です。
Unity 6 では RenderGraph がデフォルトで有効になりますが、Project SettingsのCompatibility Mode (Render Graph Disabled)にチェックを入れることで、RenderGraphが無効になり旧方式である Execute メソッドが実行されるようになります。
そのため、新旧両方の API を併記しておく必要があることに注意が必要です。
自前のSRPのUnity6移行の過渡期にそのような状況になるかと思います。

Obsolete API利用に関する警告抑制

Unity 6 では旧システム用のメソッドにObsoleteAttributeが適用されているため、次のように旧システムを利用していなくてもコンパイル時に警告が出力されてしまいます

warning CS0672: Member 'MyPass.Execute(ScriptableRenderContext, ref RenderingData)' overrides obsolete member 'ScriptableRenderPass.Execute(ScriptableRenderContext, ref RenderingData)'. Add the Obsolete attribute to 'MyPass.Execute(ScriptableRenderContext, ref RenderingData)'

警告の通りoverride側にもObsoleteAttributeを適用すれば良いのですが、Unity6未満の環境だと逆に「Obsoleteなメンバーが、Obsoleteでないメンバーをオーバーライドしている」とのことで別の警告(CS0809)が出力されてしまうため、ここでもUnityエディタバージョンを考慮する必要があります。
これらを踏まえた最終的なコードは次のようになります。

public class MyPass : ScriptableRenderPass
{
#if UNITY_2023_3_OR_NEWER
    [Obsolete("This rendering path is for compatibility mode only (when Render Graph is disabled).", false)]
#endif
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        // 各種処理
    }

#if UNITY_2023_3_OR_NEWER
    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        // 各種処理
    }
#endif
}

RenderGraphと共通のAPIに関する警告抑制

さて、前述の方法で警告について対処できましたが、関数単位での警告抑制が適切でないケースがあります。
それはUnity6においてもObsoleteでないメソッド内でObsoleteなメソッドを呼び出している場合です。

たとえば、Unity6においてScriptableRendererFeature.AddRenderPassesメソッド内でRenderingUtils.ReAllocateIfNeededメソッドを呼び出していた場合、 ReAllocateIfNeededメソッドはobsoleteなメソッドなのでCS0618の警告が出力されます。

public class MyFeature : ScriptableRendererFeature
{
    private RTHandle _myHandle;

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        var desc = renderingData.cameraData.cameraTargetDescriptor;
        // warning CS0618
        RenderingUtils.ReAllocateIfNeeded(ref _myHandle, desc);

        // (以下略)
    }
}

これに対し、前述のように関数に対してObsoleteAttributeを適用すると、ReAllocateIfNeededメソッドはobsoleteなもののAddRenderPassesメソッドはobsoleteでないため、今度はCS0809の警告が出力されてしまいます。

そのため、こういったケースでは行単位での警告抑制が有効です。

public class MyFeature : ScriptableRendererFeature
{
    private RTHandle _myHandle;

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        var desc = renderingData.cameraData.cameraTargetDescriptor;
#pragma warning disable CS0618
        RenderingUtils.ReAllocateIfNeeded(ref _myHandle, desc);
#pragma warning restore
        // (以下略)
    }
}

ここではpragma warningディレクティブを利用しており、disableとrestoreで括ることによって警告を抑制しています。

なお、RenderGraphではRTHandleの代わりにTextureHandleを利用するため、RTHandleの生成は不要な処理となります。 そのため、次のようにProject SettingsのCompatibility Mode (Render Graph Disabled)が有効になっているかを判定して条件分岐をするとよいでしょう。

public class MyFeature : ScriptableRendererFeature
{
    private RTHandle _myHandle;

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
#if UNITY_2023_3_OR_NEWER
        // Compatibility Mode (Render Graph Disabled)が有効になっているかを判定
        if (GraphicsSettings.GetRenderPipelineSettings<RenderGraphSettings>().enableRenderCompatibilityMode)
#endif
        {
            var desc = renderingData.cameraData.cameraTargetDescriptor;
#pragma warning disable CS0618
            RenderingUtils.ReAllocateIfNeeded(ref _myHandle, desc);
#pragma warning restore
        }
        // (以下略)
    }
}

おわりに

以上、エディタバージョンの互換性を保ちつつRenderGraphに対応する方法についてご紹介しました。
本記事の内容が、皆さまのご参考となりましたら幸いです。