👻

UnityのRenderGraphSystem(URP17)に対応した一番シンプルなCustom RPを作ってみる その2

2024/03/08に公開
1

前回

UnityのRenderGraphSystem(URP17)に対応した一番シンプルなCustom RPを作ってみる
前回に引き続き、アウトプット練習記事です。

この記事の内容

  • LayerMaskとShaderTag(と、RenderQueueRange)でフィルターしたオブジェクトを描画するRenderPassを作成します。
  • 前回同様、URP+RendererFeatureを使用するのであれば、もっと汎用機能も追加されているRenderObjects(RendererFeature)が存在するので、やはり実装物自体に意味はありません。

記事で執筆時点での環境

  • Unity 2023.3.0b8
  • Universal RP 17.0.2
  • Core RP 17.0.2

やってみた

使用するシーン

  • 箱が三つ置いてあるだけの手抜きシーンです。
  • 事前にRenderer側の設定を変更し、UnivdersalRenderer側のObjectRendererの対象にならないようにしておきます。

RendererFeature

  • 前回同様に、従来の実装もRenderGraph対応後も同様のため以下のものを使用します。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal;

namespace Sample.CustomRP
{
    public class CustomRendererFeature : ScriptableRendererFeature
    {
        [SerializeField] private RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
        [SerializeField] private LayerMask layerMask = -1;
        [SerializeField] private RenderQueueType renderQueueType;
        [SerializeField] private List<string> shaderTagList = new() { "UniversalForward" };
        
        CustomSamplePass customSamplePass = null;

        public override void Create()
        {
            if (customSamplePass != null) return;
            
            customSamplePass = new CustomSamplePass(renderPassEvent, "CustomSamplePass",layerMask, renderQueueType,shaderTagList);
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            renderer.EnqueuePass(customSamplePass);
        }
    }

}

RenderGraphを使用しない従来の場合のRenderPass

  • RenderTarget周りは操作しないので、Executeのみ実装
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace Sample.CustomRP
{
    public class CustomSamplePass : ScriptableRenderPass
    {
        readonly string profilerTag;
        
        LayerMask layerMask;
        RenderQueueType renderQueueType;
        List<ShaderTagId> shaderTagIds = new();
        
        public CustomSamplePass(RenderPassEvent renderPassEvent, string profilerTag, LayerMask layerMask, RenderQueueType renderQueueType, List<string> shaderTagList)
        {
            this.renderPassEvent = renderPassEvent;
            this.profilerTag     = profilerTag;
            profilingSampler     = new ProfilingSampler(profilerTag);
            this.layerMask       = layerMask;
            this.renderQueueType = renderQueueType;
            foreach (var tag in shaderTagList)
            {
                shaderTagIds.Add(new ShaderTagId(tag));
            }
        }


        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // Render時のソート条件
            SortingCriteria sortingCriteria = (renderQueueType == RenderQueueType.Transparent)
                ? SortingCriteria.CommonTransparent
                : renderingData.cameraData.defaultOpaqueSortFlags;

            // 描画設定、今回はマテリアルのoverrideなどを行わないのでShaderTagとソート条件のみ。RenderStateBlockも同様
            DrawingSettings  drawingSettings  = CreateDrawingSettings(shaderTagIds, ref renderingData, sortingCriteria);
            RenderStateBlock renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
            
            
            // RenderQueueRangeとLayerMaskで描画対象になるRendererのフィルター条件を作成
            RenderQueueRange renderQueueRange = (renderQueueType == RenderQueueType.Transparent)
                                                    ? RenderQueueRange.transparent
                                                    : RenderQueueRange.opaque;
            FilteringSettings filteringSettings = new FilteringSettings(renderQueueRange, layerMask);

            var cmd = CommandBufferPool.Get(profilerTag);
            using (new ProfilingScope(cmd, profilingSampler))
            {
                // 描画
                context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings, ref renderStateBlock);
                
                cmd.Clear();
                context.ExecuteCommandBuffer(cmd);
            }
            CommandBufferPool.Release(cmd);
        }
    }
}

RenderGraphを使用する場合

  • RendererListHandleを取得して、利用するようになります。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
using ProfilingScope = UnityEngine.Rendering.ProfilingScope;

namespace Sample.CustomRP
{
    public class CustomSamplePass : ScriptableRenderPass
    {
        readonly string profilerTag;
        
        LayerMask layerMask;
        RenderQueueType renderQueueType;
        List<ShaderTagId> shaderTagIds = new();

        public CustomSamplePass(RenderPassEvent renderPassEvent, string profilerTag, LayerMask layerMask, RenderQueueType renderQueueType, List<string> shaderTagList)
        {
            this.renderPassEvent = renderPassEvent;
            this.profilerTag     = profilerTag;
            profilingSampler     = new ProfilingSampler(profilerTag);
            this.renderQueueType = renderQueueType;
            foreach (var tag in shaderTagList)
            {
                shaderTagIds.Add(new ShaderTagId(tag));
            }
        }

        // Passで使うデータを定義しておく
        internal class PassData
        {
            internal TextureHandle colorHandle;
            internal TextureHandle depthHandle;
            // このHandleをCommandBuffer.DrawRendererに渡す
            internal RendererListHandle rendererList;
        }

        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            //  RenderingDataでなく、ContextContainerから自分で必要なデータを撮るようになった
            UniversalCameraData    cameraData    = frameData.Get<UniversalCameraData>();
            UniversalRenderingData renderingData = frameData.Get<UniversalRenderingData>();
            UniversalLightData     lightData     = frameData.Get<UniversalLightData>();
            UniversalResourceData  resourceData  = frameData.Get<UniversalResourceData>();
            
            // 今回はRasterRenderPassで作成
            using (var builder = renderGraph.AddRasterRenderPass<PassData>(profilerTag, out var passData))
            {
                // ShaderなどでGlobalにアクセスする可能性がある場合は、Trueにしておく
                builder.UseAllGlobalTextures(true);
                
                // 特にターゲットを切り替える訳ではなくても、ターゲット設定をしないと怒られます。
                passData.colorHandle = resourceData.activeColorTexture;
                builder.SetRenderAttachment(passData.colorHandle,0);

                passData.depthHandle = resourceData.activeDepthTexture;
                builder.SetRenderAttachmentDepth(passData.depthHandle);
                
                // Render時のソート条件
                SortingCriteria sortingCriteria = (renderQueueType == RenderQueueType.Transparent)
                                                      ? SortingCriteria.CommonTransparent
                                                      : cameraData.defaultOpaqueSortFlags;
                
                // 描画設定、今回はマテリアルのoverrideなどを行わないのでShaderTagとソート条件のみ。
                // context.DrawRenderersを使用していた時とことなり、RenderStaeBlockのオーバーライドが不要なら用意しなくてよくなった。
                // RenderStaeteBlockを指定したい場合は、下記のRenderListParamに設定します。今回は無し。
                DrawingSettings  drawSettings     = RenderingUtils.CreateDrawingSettings(shaderTagIds, renderingData, cameraData, lightData, sortingCriteria);
                
                RenderQueueRange renderQueueRange = (renderQueueType == RenderQueueType.Transparent)
                                                        ? RenderQueueRange.transparent
                                                        : RenderQueueRange.opaque;
                FilteringSettings filteringSettings = new FilteringSettings(renderQueueRange, layerMask);
               
                // RenderListHandleの取得
                // AssempblyReferenceでURPパッケージ参照いれてたり、直接URPパッケージ内にカスタム作成する場合 or URPパッケージのAssemblyInfoでInternalアクセスを許可している場合はm、RenderingUtils.CreateRendererList()が使えるので楽
                // URP使用しない場合もあるのでCore RPのみ利用パターンはこれ(URP17といいつつ)
                RendererListParams rendererListParams = new RendererListParams(renderingData.cullResults, drawSettings, filteringSettings);
                passData.rendererList          = renderGraph.CreateRendererList(rendererListParams);
                
                // このPassで使用するリソースとして宣言する
                builder.UseRendererList(passData.rendererList);
                
                
                builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
                {
                    using (new ProfilingScope(context.cmd, profilingSampler))
                    {
                        // 描画
                        context.cmd.DrawRendererList(data.rendererList);
                    }
                });
            }
        }

    }
}

今回のまとめ

  • 特にMaterialとかCameraのオーバーライドしたり、RenderingLayerでのFilterしたりとかの特別な場合を除きあまりRendererFeatureで実装しないであろう内容になってます。(でもゲームによってはたまに使う場合がある)
  • 特定のオブジェクトを2回別タイミングで描画したいとかの場合もRenderObjectsを使うのが楽ですしね……
  • ScriptableRendererでRenderer自体をフルで作成するなら今回やったようなことをする必要があるかも?
  • いつかRenderer自体(さらにPipeline自体)を作る記事も書けたらいいな

Discussion