素朴なUnlitのScriptableRenderPipelineを自作する

Camera Stacking対応目前、ということでそろそろSRPの時代を感じています。 学習がてらに簡単なUnlitのパイプラインを作成してみました。

なお、以下を参考にしました。SRPDefaultUnlitとか良く見つけたなと思います。先人の知恵に感謝です。

learning.unity3d.jp

using UnityEngine;
using UnityEngine.Rendering;

[CreateAssetMenu(menuName = "MyRenderPipelineAsset")]
public class MyRenderPipelineAsset : UnityEngine.Rendering.RenderPipelineAsset
{
    class MyRenderPipeline : RenderPipeline
    {
        protected override void Render(ScriptableRenderContext context, Camera[] cameras)
        {
            foreach(var camera in cameras)
            {
                context.SetupCameraProperties(camera);

                ClearRenderTarget(context, camera);

#if UNITY_EDITOR
                if (camera.cameraType == CameraType.SceneView)
                    ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
#endif

                if (!camera.TryGetCullingParameters(out ScriptableCullingParameters cullingParameters))
                    continue;

                var cullingResults = context.Cull(ref cullingParameters);

                RenderOpaque(context, camera, ref cullingResults);

#if UNITY_EDITOR
                if (camera.cameraType == CameraType.SceneView)
                    context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
#endif
                RenderTransparent(context, camera, ref cullingResults);
#if UNITY_EDITOR
                if (camera.cameraType == CameraType.SceneView)
                    context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
#endif
            }

            context.Submit();
        }

        private void ClearRenderTarget(ScriptableRenderContext context, Camera camera)
        {
            var cmd = CommandBufferPool.Get();
            cmd.Clear();

            switch (camera.clearFlags)
            {
                case CameraClearFlags.SolidColor:
                case CameraClearFlags.Depth:
                    var clearDepath = true;
                    var clearColor = camera.clearFlags == CameraClearFlags.SolidColor;
                    cmd.ClearRenderTarget(clearDepath, clearColor, camera.backgroundColor);
                    context.ExecuteCommandBuffer(cmd);
                    break;
                case CameraClearFlags.Skybox:
                    cmd.ClearRenderTarget(true, false, camera.backgroundColor);
                    context.ExecuteCommandBuffer(cmd);
                    context.DrawSkybox(camera);
                    break;
            }

            CommandBufferPool.Release(cmd);
        }

        private void RenderOpaque(ScriptableRenderContext context, Camera camera, ref CullingResults cullingResults)
        {
            var sortingSettings = new SortingSettings(camera)
            {
                criteria = SortingCriteria.CommonOpaque
            };

            var drawingSettings = new DrawingSettings(new ShaderTagId("SRPDefaultUnlit"), sortingSettings);
            var filterSettings = new FilteringSettings(
                new RenderQueueRange(RenderQueueRange.minimumBound, (int)RenderQueue.GeometryLast),
                camera.cullingMask
            );

            context.DrawRenderers(cullingResults, ref drawingSettings, ref filterSettings);
        }

        private void RenderTransparent(ScriptableRenderContext context, Camera camera, ref CullingResults cullingResults)
        {
            var sortingSettings = new SortingSettings(camera)
            {
                criteria = SortingCriteria.CommonTransparent
            };

            var drawingSettings = new DrawingSettings(new ShaderTagId("SRPDefaultUnlit"), sortingSettings);
            var filterSettings = new FilteringSettings(
                new RenderQueueRange((int)RenderQueue.GeometryLast + 1, RenderQueueRange.maximumBound),
                camera.cullingMask
            );

            context.DrawRenderers(cullingResults, ref drawingSettings, ref filterSettings);
        }
    }

    protected override RenderPipeline CreatePipeline()
    {
        return new MyRenderPipeline();
    }
}

描画結果 f:id:enrike3:20191009020310p:plain

シーンビュー。ちゃんとGizmoが表示されます。 PreImageEffect/PostImageEffectの違いは正直謎です… f:id:enrike3:20191009020543p:plain

フレームデバッガ。想定通りの挙動です。
f:id:enrike3:20191009020752p:plain

コードの全体の流れとしては以下の通りです

カメラ毎に以下を行う

  • レンダーターゲットをクリア (今回の例だとDepthのみクリアしカラーはSkyBoxで埋める)
  • 描画対象を抽出 (カリング)
    • カメラ渡せばカリングパラメータがとれる便利メソッドがあるので完全にそれまかせ
  • 不透明描画
    • 不透明描画の場合は手前から描くとZTestが効率的なので、SortingSettingはOpaque用(不透明)
    • RnderQueueRangeは、UnityはMaterialのRenderQueueは2500以下を不透明描画とみなすので、下限 ~ 2500
  • 透明描画
    • 透明/半透明描画の場合は奥から描かないと破綻するので、SortingSettingはTransparent用(透明)
    • RnderQueueRangeは、UnityはMaterialのRenderQueueは2500より上を透明描画とみなすので、2501 ~ 上限
  • エディタの場合はカメラがシーンビューの場合はおまじないコードをかく。Gizmoとか

描画そこまで詳しいわけじゃないですけど、Unlitだからだいたいこんなもんでしょうたぶんきっと。SkyBoxはOpaque描画の最後にもってきたほうがいいとかあるかもしれないけど。

Unlitではなくライト対応にする場合は、まずベースの描画をしてからライト毎の加算をしたり 影をつくるためにシャドウマップに書き込んだりが発生してくると思います。 また、ポストエフェクトを使うならRenderTextureに描画をまとめてからごにょごにょしたり。

しかし、実際に使い物になるレベルに引き上げるのは大変です。今回のような素朴なSRPですらシーンカメラ用の謎のおまじないを書く必要があって、しかもこれだけで足りているのかよくわかりません。 今回の例はあくまでSRPとはこんなものという学習用、大づかみ用と割り切ってやはりUnity公式がメンテし機能追加していくUniversalRPを使っていきたいところです。

次の記事は UniversalRPのモジュラー構造について書くつもりです。書くつもりです。書くつもりです。