ShaderTips

シェーダーTips

主にUnityシェーダーについての記事を書いています。

【Unity】SRP (Scriptable Render Pipeline) 入門②

前回はSRPで画面を塗りつぶしました。

今回は不透明オブジェクトと半透明オブジェクトを描画します。

ny-program.hatenablog.com

スクリプト

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental;
using UnityEngine.Experimental.Rendering;

[ExecuteInEditMode]
public class DrawObjAssetPipe : RenderPipelineAsset
{
    [SerializeField]
    private float modelRenderResolutionRate = 0.7f;
    public float ModelRenderResolutionRate => modelRenderResolutionRate;
    protected override RenderPipeline CreatePipeline()
    {
        return new DrawObjPipeInstance(this);
    }

#if UNITY_EDITOR
    [UnityEditor.MenuItem("SRP-Demo/02 - Create Basic Asset Pipeline")]
    static void CraeteDrawObjAssetPipeline()
    {
        // このクラスのインスタンスを作成
        var instance = ScriptableObject.CreateInstance<DrawObjAssetPipe>();
        // アセット化
        UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/Shader/SRP/1-DrawObjAssetPipe.asset");
    }
#endif
}



public class DrawObjPipeInstance : RenderPipeline
{
    private const int MAX_CAMERA_COUNT = 4;

    private const string FORWARD_SHADER_TAG = "ToonForward";

    private CommandBuffer[] commandBuffers = new CommandBuffer[MAX_CAMERA_COUNT];

    private DrawObjAssetPipe drawObjAssetPipe;

    private CullingResults cullingResults;

    private RenderTargetIdentifier[] renderTargetIdentifiers = new RenderTargetIdentifier[(int)RenderTextureType.Count];

    private enum RenderTextureType
    {
        ModelColor,
        ModelDepth,

        Count,
    }
    public DrawObjPipeInstance(DrawObjAssetPipe asset)
    {
        drawObjAssetPipe = asset;

        // CommandBufferの事前生成
        for (int i = 0; i < commandBuffers.Length; i++)
        {
            commandBuffers[i] = new CommandBuffer();
            commandBuffers[i].name = "ToonRP";
        }
    }

    /// <summary>
    /// 描画処理
    /// </summary>
    protected override void Render(ScriptableRenderContext context, Camera[] cameras)
    {
        for (int i = 0; i < cameras.Length; i++)
        {
            var camera = cameras[i];
            var commandBuffer = commandBuffers[i];

            // カメラプロパティ設定
            context.SetupCameraProperties(camera);

            // カメラからカリングのための情報を取得
            if (!camera.TryGetCullingParameters(false, out var cullingParameters))
            {
                continue;
            }
            // ScriptableCullingParametersに基づいてカリングをスケジュール
            cullingResults = context.Cull(ref cullingParameters);

            // RenderTexture作成
            CreateRenderTexture(context, camera, commandBuffer);

            // モデル描画用RTのClear
            ClearModelRenderTexture(context, camera, commandBuffer);

            // ライト情報のセットアップ
            SetupLights(context, camera, commandBuffer);

            // 不透明オブジェクト描画
            DrawOpaque(context, camera, commandBuffer);

            // Skybox描画
            if (camera.clearFlags == CameraClearFlags.Skybox)
            {
                context.DrawSkybox(camera);
            }

            // 半透明オブジェクト描画
            DrawTransparent(context, camera, commandBuffer);

            // CameraTargetに描画
            RestoreCameraTarget(context, commandBuffer);

#if UNITY_EDITOR
            // Gizmo
            if (UnityEditor.Handles.ShouldRenderGizmos())
            {
                context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
            }
#endif

            // PostProcessing

#if UNITY_EDITOR
            // Gizmo
            if (UnityEditor.Handles.ShouldRenderGizmos())
            {
                context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
            }
#endif

            // RenderTexture解放
            ReleaseRenderTexture(context, commandBuffer);
        }

        context.Submit();
    }

    /// <summary>
    /// レンダーテクスチャ作成
    /// </summary>
    private void CreateRenderTexture(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer)
    {
        commandBuffer.Clear();

        var width = camera.targetTexture?.width ?? Screen.width;
        var height = camera.targetTexture?.height ?? Screen.height;

        var modelWidth = (int)((float)width * drawObjAssetPipe.ModelRenderResolutionRate);
        var modelHeight = (int)((float)height * drawObjAssetPipe.ModelRenderResolutionRate);

        // カラー用とデプス用RenderTextureを作成
        commandBuffer.GetTemporaryRT((int)RenderTextureType.ModelColor, modelWidth, modelHeight, 0, FilterMode.Bilinear, RenderTextureFormat.Default);
        commandBuffer.GetTemporaryRT((int)RenderTextureType.ModelDepth, modelWidth, modelHeight, 0, FilterMode.Point, RenderTextureFormat.Depth);

        // コマンドバッファの実行をスケジュールします。
        context.ExecuteCommandBuffer(commandBuffer);

        // RenderTextureの識別子を生成
        for (int i = 0; i < (int)RenderTextureType.Count; i++)
        {
            renderTargetIdentifiers[i] = new RenderTargetIdentifier(i);
        }
    }

    /// <summary>
    /// RenderTextureをクリア
    /// </summary>
    private void ClearModelRenderTexture(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer)
    {
        commandBuffer.Clear();

        // RenderTarget設定
        commandBuffer.SetRenderTarget(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], renderTargetIdentifiers[(int)RenderTextureType.ModelDepth]);

        if (camera.clearFlags == CameraClearFlags.Depth || camera.clearFlags == CameraClearFlags.Skybox)
        {
            commandBuffer.ClearRenderTarget(true, false, Color.black, 1.0f);
        }
        else if (camera.clearFlags == CameraClearFlags.SolidColor)
        {
            commandBuffer.ClearRenderTarget(true, true, camera.backgroundColor, 1.0f);
        }

        context.ExecuteCommandBuffer(commandBuffer);
    }

    /// <summary>
    /// ライトのセットアップ
    /// </summary>
    private void SetupLights(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer)
    {
        commandBuffer.Clear();

        // DirectionalLightの探索
        int lightIndex = -1;
        for (int i = 0; i < cullingResults.visibleLights.Length; i++)
        {
            var visibleLight = cullingResults.visibleLights[i];
            var light = visibleLight.light;


            if (light == null || light.shadows == LightShadows.None || light.shadowStrength <= 0f || light.type != LightType.Directional)
            {
                continue;
            }

            lightIndex = i;
            break;
        }

        // ディレクショナルライトが無効の時、シェーダーのライトヴァリアントを無効に
        if (lightIndex < 0)
        {
            commandBuffer.DisableShaderKeyword("ENABLE_DIRECTIONAL_LIGHT");
            context.ExecuteCommandBuffer(commandBuffer);
            return;
        }

        // ディレクショナルライトが有効の時、シェーダーのライトヴァリアントの有効に
        {
            var visibleLight = cullingResults.visibleLights[lightIndex];
            var light = visibleLight.light;

            commandBuffer.EnableShaderKeyword("ENABLE_DIRECTIONAL_LIGHT");
            commandBuffer.SetGlobalColor("_LightColor", light.color * light.intensity);
            commandBuffer.SetGlobalVector("_LightVector", -light.transform.forward);
            context.ExecuteCommandBuffer(commandBuffer);
        }
    }

    /// <summary>
    /// 不透明オブジェクトの描画
    /// </summary>
    private void DrawOpaque(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer)
    {
        commandBuffer.Clear();

        commandBuffer.SetRenderTarget(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], renderTargetIdentifiers[(int)RenderTextureType.ModelDepth]);
        context.ExecuteCommandBuffer(commandBuffer);

        // 描画順 
        var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonOpaque };
        // ここで指定したTagを記述したシェーダーのみ描画される
        var settings = new DrawingSettings(new ShaderTagId(FORWARD_SHADER_TAG), sortingSettings);
        var filterSettings = new FilteringSettings(
            // 指定されたレンダーキューの間のオブジェクトのみ描画する(0-2500)
            new RenderQueueRange(0, (int)RenderQueue.GeometryLast),
            // カメラに指定されたレイヤーのみ描画する
            camera.cullingMask
            );

        // Rendering
        context.DrawRenderers(cullingResults, ref settings, ref filterSettings);
    }

    /// <summary>
    /// 半透明オブジェクトの描画
    /// </summary>
    private void DrawTransparent(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer)
    {
        commandBuffer.Clear();

        commandBuffer.SetRenderTarget(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], renderTargetIdentifiers[(int)RenderTextureType.ModelDepth]);
        context.ExecuteCommandBuffer(commandBuffer);

        // Filtering, Sort
        var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonTransparent };
        var settings = new DrawingSettings(new ShaderTagId(FORWARD_SHADER_TAG), sortingSettings);
        var filterSettings = new FilteringSettings(
            // 指定されたレンダーキューの間のオブジェクトのみ描画する(2500-3000)
            new RenderQueueRange((int)RenderQueue.GeometryLast, (int)RenderQueue.Transparent),
            camera.cullingMask
            );

        // 描画
        context.DrawRenderers(cullingResults, ref settings, ref filterSettings);
    }

    /// <summary>
    /// 画面に対して描画する
    /// </summary>
    private void RestoreCameraTarget(ScriptableRenderContext context, CommandBuffer commandBuffer)
    {
        commandBuffer.Clear();

        var cameraTarget = new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget);

        commandBuffer.SetRenderTarget(cameraTarget);
        commandBuffer.Blit(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], cameraTarget);

        context.ExecuteCommandBuffer(commandBuffer);
    }

    private void ReleaseRenderTexture(ScriptableRenderContext context, CommandBuffer commandBuffer)
    {
        commandBuffer.Clear();

        for (int i = 0; i < (int)RenderTextureType.Count; i++)
        {
            commandBuffer.ReleaseTemporaryRT(i);
        }

        context.ExecuteCommandBuffer(commandBuffer);
    }
}

解説

    protected override void Render(ScriptableRenderContext context, Camera[] cameras)
    {
        for (int i = 0; i < cameras.Length; i++)
        {
            var camera = cameras[i];
            var commandBuffer = commandBuffers[i];

            // カメラプロパティ設定
            context.SetupCameraProperties(camera);

Render関数の頭を見ていきます。 引数にScriptableRenderContextとシーン上のカメラが配列で渡されいます。

ScriptableRenderContext

Unity - Scripting API: ScriptableRenderContext

コマンドバッファの実行タイミングやオブジェクトやスカイボックス、影の描画といったカスタムレンダーパイプラインの状態の保存とコマンドの実行を担うコンテキストになります。

なお、コマンドバッファについてはこちらをご参照ください。

ny-program.hatenablog.com

代表的なメソッドを紹介します。

public void ExecuteCommandBuffer(Rendering.CommandBuffer commandBuffer);

Unity - Scripting API: Rendering.ScriptableRenderContext.ExecuteCommandBuffer 引数のコマンドバッファを予約しておきます。 この段階でコマンドバッファの実行は行われません。

public void Submit();

Unity - Scripting API: Rendering.ScriptableRenderContext.Submit ExecuteCommandBufferで予約したコマンドバッファを実行します。

SRPの主な処理の流れは以下のようになります。

1.コマンドバッファに描画に必要な処理を登録

2.ExecuteCommandBufferでコマンドバッファを予約

カメラごとに1と2を実行

3.最後にSubmitで予約したコマンドバッファを実行

ShaderTagId

    private const string FORWARD_SHADER_TAG = "ToonForward";
    ・
    ・
    /// <summary>
    /// 不透明オブジェクトの描画
    /// </summary>
    private void DrawOpaque(ScriptableRenderContext context, Camera camera, CommandBuffer commandBuffer)
    {
        commandBuffer.Clear();

        commandBuffer.SetRenderTarget(renderTargetIdentifiers[(int)RenderTextureType.ModelColor], renderTargetIdentifiers[(int)RenderTextureType.ModelDepth]);
        context.ExecuteCommandBuffer(commandBuffer);

        // 描画順
        var sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonOpaque };
        // ここで指定したTagを記述したシェーダーのみ描画される
        var settings = new DrawingSettings(new ShaderTagId(FORWARD_SHADER_TAG), sortingSettings);
        var filterSettings = new FilteringSettings(
            // 指定されたレンダーキューの間のオブジェクトのみ描画する(0-2500)
            new RenderQueueRange(0, (int)RenderQueue.GeometryLast),
            // カメラに指定されたレイヤーのみ描画する
            camera.cullingMask
            );

        // Rendering
        context.DrawRenderers(cullingResults, ref settings, ref filterSettings);
    }

今回のパイプラインでは描画設定に"ToonForward"をShaderTagIdとして設定しています。 ここで設定したTagが記述されたシェーダのみが描画されるため、対応した専用のシェーダを記述する必要があります。

不透明オブジェクトのシェーダー

Shader "ToonRP/Unilt"
{
    Properties
    {
        _Color ("Color", COLOR) = (1, 1, 1, 1)
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }
        LOD 100

        Pass
        {
            Name "Unlit"
            Tags {"LightMode" = "ToonForward"}

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile _ ENABLE_DIRECTIONAL_LIGHT   

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            float4 _Color;

            sampler2D _MainTex;
            float4 _MainTex_ST;

#if ENABLE_DIRECTIONAL_LIGHT
            float4 _LightColor;
            float4 _LightVector;
#endif

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                col *= _Color;

#if ENABLE_DIRECTIONAL_LIGHT
                col *= _LightColor;
#endif
                return col;
            }
            ENDHLSL
        }
    }
}

半透明オブジェクトのシェーダー

Shader "ToonRP/UnlitTransparent"
{
    Properties
    {
        _Color("Color", COLOR) = (1, 1, 1, 1)
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100

        Pass
        {
            Name "Transparent"
            Tags {"LightMode" = "ToonForward"}
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile _ ENABLE_DIRECTIONAL_LIGHT   

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            float4 _Color;

            sampler2D _MainTex;
            float4 _MainTex_ST;

#if ENABLE_DIRECTIONAL_LIGHT
            float4 _LightColor;
            float4 _LightVector;
#endif

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                col *= _Color;

#if ENABLE_DIRECTIONAL_LIGHT
                col *= _LightColor;
#endif
                return col;
            }
            ENDHLSL
        }
    }
}

実際に作成したシェーダがこちらです。 前項の通り、Tags以下に "LightMode" = "ToonForward" が記述されています。 このタグでシェーダの絞り込みが行われているため,この記述が入ったシェーダのみが描画されます。

あとは前回の記事の実装したSRPを適用の作業を行えば反映されます。 ny-program.hatenablog.com

描画結果はこちらです。 不透明・半透明どちらも描画されています。 f:id:Ny_Program:20210620150954p:plain