[URP]TransparentオブジェクトもDepthTextureに書き込む方法について

# 結果
中央に赤と青の半透明な二つのキューブがあります．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462025/c0d8f493-d936-07a3-1173-20ca6b613206.png)

DepthPrepassでレンダリングされる_CameraDepthTextureはこのようになっていて，半透明なキューブはDepthに書き込まれていません．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462025/4be738d8-2ee4-506c-01d7-a3858a68fb59.png)

追加でTransparent Depth Prepassを実装することで，半透明なキューブもデプステクスチャに描画できるようにしました．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462025/cf817390-a92a-53f2-bc69-4443d3f59ba5.png)



# はじめに
URPでは，シェーダー内で_CameraDepthTextureを利用することで，カメラによる深度情報を取得することができます．[[1]](https://qiita.com/r-ngtm/items/3052d3a2d2ec31260eb3)
しかしながら，_CameraDepthTextureでは，不透明オブジェクトのレンダリング後に作成されるのでTransparentオブジェクトの深度情報はここに含まれません．
従って，_CameraOpaqueTextureを使用したポストエフェクトなどでは，Transparentオブジェクトにエフェクトがかからず，問題になる場合があります．[[2]](https://forum.unity.com/threads/solved-urp-depth-of-field-transparency-render-queue-problem.859936/)
本記事ではTransparentオブジェクトに対するポストエフェクトを想定して，透明・不透明オブジェクトの深度情報を書き込んだ_CameraTransparentDepthTextureの作成をカスタムパスを追加することで実装する方法を述べます．

# 環境
URP設定で作成したプロジェクトを使用しています．
Universal RP 10.5.0
Unity 2020.3.15f1

# 方法
パスを追加して拡張することで実装します．
パスを追加するには，
1. ScriptableRenderPass
1. ScriptableRendererFeature
1. Shader

が必要です．
それぞれを簡単に説明すると，
1. カメラテクスチャの受け渡しやパスのタイミング，描画方法を設定（コマンドバッファ）
1. 1で設定したパスの生成，エディタ上での設定
1. どのように受け取ったtextureを加工するか
 
を記述することができます．

順にソースコードを示します．
## ScriptableRenderPass
``` C#:DrawTransparentDepthRenderPass.cs
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace GraphicExtention.PostProcessing.TransparentDepthTexture
{
    /// <summary>
    ///     Path that grabs the color texture of the camera.
    /// </summary>
    public class DrawTransparentDepthRenderPass : ScriptableRenderPass
    {
        // Depth Bufferを32bitにする
        int kDepthBufferBits = 32;
        
        // RenderTextureの識別用struct
        private RenderTargetHandle depthAttachmentHandle { get; set; }
        // 作成するRenderTextureの情報を入れるstruct
        internal RenderTextureDescriptor descriptor { get; private set; }

        FilteringSettings m_FilteringSettings;
        const string m_ProfilerTag = "Transparent Depth Prepass";
        ProfilingSampler m_ProfilingSampler = new ProfilingSampler(m_ProfilerTag);
        ShaderTagId m_ShaderTagId = new ShaderTagId("DepthOnly");

        private RenderTargetHandle m_transparentDepthTexture;
        
        private RenderQueueRange m_renderQueueRange;
        
        public DrawTransparentDepthRenderPass(RenderPassEvent evt, RenderQueueRange renderQueueRange)
        {
            m_renderQueueRange = renderQueueRange;
            renderPassEvent = evt;
        }
        
        // パスで使用するテクスチャなどの設定
        public void Setup(RenderTextureDescriptor baseDescriptor)
        {
            m_transparentDepthTexture.Init("_CameraTransparentDepthTexture");
            
            // カメラからのテクスチャをデプステクスチャ用に設定
            this.depthAttachmentHandle = m_transparentDepthTexture;
            baseDescriptor.colorFormat = RenderTextureFormat.Depth;
            baseDescriptor.depthBufferBits = kDepthBufferBits;

            // Depth-Only pass don't use MSAA
            baseDescriptor.msaaSamples = 1;
            
            descriptor = baseDescriptor;
        }
        
        // This method is called before executing the render pass.
        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            cmd.GetTemporaryRT(depthAttachmentHandle.id, descriptor, FilterMode.Point);
            ConfigureTarget(depthAttachmentHandle.Identifier());
            ConfigureClear(ClearFlag.All, Color.black);
        }
        
        // Here you can implement the rendering logic.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);

            using (new ProfilingScope(cmd, m_ProfilingSampler))
            {
                context.ExecuteCommandBuffer(cmd);
                cmd.Clear();

                var sortFlags = renderingData.cameraData.defaultOpaqueSortFlags;
                m_FilteringSettings = new FilteringSettings(m_renderQueueRange);
                var drawSettings = CreateDrawingSettings(m_ShaderTagId, ref renderingData, sortFlags);
                drawSettings.perObjectData = PerObjectData.None;

                ref CameraData cameraData = ref renderingData.cameraData;
                Camera camera = cameraData.camera;

                context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref m_FilteringSettings);

            }
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
        
        // Cleanup any allocated resources that were created during the execution of this render pass.
        public override void FrameCleanup(CommandBuffer cmd)
        {
            if (cmd == null)
                throw new ArgumentNullException("cmd");

            if (depthAttachmentHandle != RenderTargetHandle.CameraTarget)
            {
                cmd.ReleaseTemporaryRT(depthAttachmentHandle.id);
                depthAttachmentHandle = RenderTargetHandle.CameraTarget;
            }
        }
    }
}
```

作成するパスの名前(Transparent Depth Prepass)や，カメラから取得するテクスチャをデプステクスチャの設定にしたり，どのシェーダーを使用するのかを指定します．
_CameraTransparentDepthTextureという新しいテクスチャを作成するようにしました．
これでShaderから_CameraDepthTextureと同様，_CameraTransparentDepthTextureを呼び出せます．

## ScriptableRendererFeature
``` C#:DrawTransparentDepthRendererFeature
using System; 
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace GraphicExtention.PostProcessing.TransparentDepthTexture
{
    /// <summary>
    ///     Renderer feature to grab color texture and render objects that use it.
    /// </summary>
    [Serializable]
    public class DrawTransparentDepthRendererFeature : ScriptableRendererFeature
    {
        private DrawTransparentDepthRenderPass pass;

        public override void Create()
        {
            
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            pass ??= new DrawTransparentDepthRenderPass(RenderPassEvent.AfterRenderingTransparents, RenderQueueRange.all);
            pass.renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
            
            var cameraTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
            pass.Setup(cameraTargetDescriptor);
            if (pass != null)
            {
                renderer.EnqueuePass(pass);   
            }
        }
    }
}
```
先ほど作ったパスをここで，生成しています．
パスのタイミングもここで指定できます．

## Shader
``` HLSL:TransparentDepthTexture
Shader "TransparentDepthTexture"
{
    SubShader
    {
         Pass
        {
            Name "TransparentDepthOnly"
            Tags { "LightMode" = "TransparentDepthOnly" "Queue"="Geometry" "RenderType"="Opaque"}

            ZWrite On
            ColorMask 0

            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            
            #pragma exclude_renderers gles gles3 glcore
            #pragma target 4.5

            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment

            // -------------------------------------
            // Material Keywords
            #pragma shader_feature_local_fragment _ALPHATEST_ON
            #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A

            //--------------------------------------
            // GPU Instancing
            #pragma multi_compile_instancing
            #pragma multi_compile _ DOTS_INSTANCING_ON

            struct Attributes
            {
                float4 position     : POSITION;
                float2 texcoord     : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float2 uv           : TEXCOORD0;
                float4 positionCS   : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            float4 _MainTex_ST;

            Varyings DepthOnlyVertex(Attributes input)
            {
                Varyings output = (Varyings)0;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

                output.uv = TRANSFORM_TEX(input.texcoord, _MainTex);
                output.positionCS = TransformObjectToHClip(input.position.xyz);
                return output;
            }

            half4 DepthOnlyFragment(Varyings input) : SV_TARGET
            {
                clip(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv).a - 0.5);
                return 0;
            }

            ENDHLSL
        }   
    }
}
```

デプスに書き込むだけのシェーダーです．

# 使用
使用しているRendererを開くと，画像のようにAdd Renderer Featureというボタンが下にあります．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462025/4fc20e6c-91d6-da68-cd85-d9c47d27eee7.png)
RendererはProjectSettingsのGraphicsからどれを使用しているかすぐわかります．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462025/5029ab6d-dab6-8403-cd97-ad5e7a3d4287.png)


このボタンを押して，DrawTransparentDepthRendererFeatureを選択します．
すると，パスを追加することができました．

その後，Shader側で_CameraTransparentDepthTextureを使えば，半透明なオブジェクトも含まれたDepthTextureを使用できます．

# 終わりに
半透明なオブジェクトは計画的に使用することで破綻することを回避できます．
しかし，どうしても半透明なものをたくさん使いたい！！こともあるでしょう．
そんな時に起こる問題の解決の助けになればうれしいです．

# 参考
[1]https://qiita.com/r-ngtm/items/3052d3a2d2ec31260eb3
[2]https://forum.unity.com/threads/solved-urp-depth-of-field-transparency-render-queue-problem.859936/
[3]https://light11.hatenadiary.com/entry/2021/07/28/203152
[4]https://qiita.com/r-ngtm/items/6ce8ba371d4f2be6e559