【Unity】Universal Render Pipelineで独自のレンダリングパスを追加する

Universal Render Pipelineで独自のレンダリングパスを追加する方法についてまとめました。

Unity2021.1.11f1
Universal RP 11.0.0

はじめに

UnityのUniversal Render Pipeline(URP)は、SRPを使って組まれたレンダリングパイプラインのプリセットです。
これには不透明描画・半透明描画などの主要なパスが定義されているほか、パスを追加して拡張できる仕組みが用意されています。
この記事ではURPにパスを追加する方法をまとめます。

ちなみにURPの基礎については以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

パスを定義する

まずScriptableRenderPassを継承してパスを定義します。
ソースコードは以下の通りです。

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class PostTransparentPass : ScriptableRenderPass
{
    // FrameDebuggerやProfiler用の名前
    private const string ProfilerTag = nameof(PostTransparentPass);
    private readonly ProfilingSampler _profilingSampler = new ProfilingSampler(ProfilerTag);

    // どのタイミングでレンダリングするか
    private readonly RenderPassEvent _renderPassEvent = RenderPassEvent.AfterRenderingTransparents;

    // 対象とするRenderQueue
    private readonly RenderQueueRange _renderQueueRange = RenderQueueRange.all;

    // ShaderのTagsでLightModeがこれになっているシェーダのみをレンダリング対象とする
    private readonly ShaderTagId _shaderTagId = new ShaderTagId(nameof(PostTransparentPass).Replace("Pass", ""));

    private FilteringSettings _filteringSettings;

    public PostTransparentPass()
    {
        _filteringSettings = new FilteringSettings(_renderQueueRange);
        renderPassEvent = _renderPassEvent;
    }

    // レンダリング処理前に呼ばれる
    // レンダーターゲットを変えたりできる
    public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    {
    }

    // レンダリング処理を書く
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        var cmd = CommandBufferPool.Get(ProfilerTag);
        using (new ProfilingScope(cmd, _profilingSampler))
        {
            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();
            CommandBufferPool.Release(cmd);
            
            var drawingSettings =
                CreateDrawingSettings(_shaderTagId, ref renderingData, SortingCriteria.CommonTransparent);
            context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings);
        }
    }

    // レンダリング処理後に呼ばれる
    // レンダリング処理に使用したリソースを片づけたりする
    public override void FrameCleanup(CommandBuffer cmd)
    {
    }
}

説明はコメントに書いた通りとなりますが、描画タイミング、描画対象のRenderQueue、描画対象のLightModeなどを指定できます。
レイヤーやオブジェクトのソート順なども指定できるので、かなり自由に描画処理を書けます。

今回は、Transparent描画の後に、LightModeがPostTransparentとなっているシェーダを持つオブジェクトのみを描画するようにしてみました。

パスに対応するシェーダを書く

さて前節のパスではLightModeがPostTransparentになっているオブジェクトだけを描画対象としました。
よって、このLightModeを持つシェーダを作らないといけません。
テスト用に簡単なシェーダを書いてみます。

Shader "PostTransparentShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Tags { "RenderType" = "Transparent" "RenderPipeline" = "UniversalRenderPipeline" }
        
        Pass
        {
            // LightModeをPostTransparentに設定しておく
            Tags { "LightMode" = "PostTransparent" }
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS   : POSITION;  
                float2 uv : TEXCOORD0;               
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            
            CBUFFER_START(UnityPerMaterial)
            sampler2D _MainTex;  
            CBUFFER_END

            Varyings vert (Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = IN.uv;
                return OUT;
            }
            
            half4 frag (Varyings IN) : SV_Target
            {
                return tex2D(_MainTex, IN.uv);
            }
            ENDHLSL
        }
    }
}

これで前節のパスで描画対象となるシェーダを書けました。

ScriptableRendererFeatureを作る

次にScriptableRendererFeatureを継承したクラスを作ります。
これは先ほど作ったパスを生成、設定するScriptableObjectです。

using UnityEngine;
using UnityEngine.Rendering.Universal;

[CreateAssetMenu]
public sealed class PostTransparentRendererFeature : ScriptableRendererFeature
{
    private PostTransparentPass _currentPass;

    public override void Create()
    {
        // パスを生成する
        _currentPass = new PostTransparentPass();
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        // パスをレンダリングパイプラインに追加
        renderer.EnqueuePass(_currentPass);
    }
}

Forward Renderer Dataに設定する

次に作成したRenderer FeatureをForward Renderer Dataに設定します。
Forward Renderer DataのインスペクタからAdd Renderer Feature > Post Transparent Renderer Featureを選択します。

f:id:halya_11:20210712182734p:plain
Forward Renderer Data

これで設定完了です。

レンダリングする

ここまで設定したら前述のシェーダを使ったオブジェクトを作成してレンダリング結果を見てみます。

f:id:halya_11:20210712183433p:plain
レンダリング結果

左の球がURPのLitシェーダ、右の立方体が今回のシェーダを使ったオブジェクトです。
Frame Debuggerを見てみると、半透明描画の後にパスが差し込まれていることを確認できます。

f:id:halya_11:20210712183610p:plain
Frame Debugger

正常に半透明描画の後にパスを追加できていることが確認できました。

関連

light11.hatenadiary.com