【Universal Render Pipeline】_CameraDepthTextureの実装を読んでみた

# はじめに
URP (Universal Render Pipeline)では、**_CameraDepthTexture**を利用することで画面のデプス情報(深度の情報)を取得することができます。

今回は_CameraDepthTextureへのデプス値の書き込みが、URP内部ではどのようにして実装されているのか追ってみました。


# 環境
URPテンプレートでプロジェクトを作成したものをそのまま使用します。

Unity 2020.1.2f1
Universal RP 8.2.0

## _CameraDepthTextureの使い方
_CameraDepthTextureの実装を追う前に **_CameraDepthTexture** の使い方をおさらいします。

### Depth Texture を有効にする
Universal Render Pipeline Asset の Depth Texture を有効にすると、シェーダーから_CameraDepthTextureが取得できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/8e3bffc7-7fcf-1850-1535-bb293a46cb88.png)

## シェーダーグラフで_CameraDepthTextureを使ってみる
_CameraDepthTextureという名前のTexture2Dを定義すると、テクスチャを介してデプス値が取れます。
<img width = 800 src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/e889e810-e6f1-14a5-47e3-315160d4b40b.png">

<img width = 640 src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/397ed8c8-b1b3-c6b7-81e4-6c637190af87.png">

_CameraDepthTextureの使い方が分かったところで、次に_CameraDepthTextureの内部的な実装を追っていきたいと思います。

# 今回読むcsファイル
|ファイル名|内容|
|:---:|:---:|
|ForwardRenderer.cs|URPのフォワードレンダリングの実装|
|DepthOnlyPass.cs| DepthOnlyパスを持つオブジェクトだけを描画するパス|


# まとめ

最初に、```_CameraDepthTexture``` にデプス値が書き込まれるまでの流れをまとめます。

## 1)  _CameraDepthTextureのシェーダープロパティIDを作成

ForwardRenderer.cs にて "_CameraDepthTexture"のハンドルが作成されます。

```ForwardRenderer.cs
RenderTargetHandle m_DepthTexture;
...
m_DepthTexture.Init("_CameraDepthTexture");
```

Initは以下のような実装になっています。

```RenderTargetHandle.cs
public int id { set; get; }
...
public void Init(string shaderProperty)
{
    id = Shader.PropertyToID(shaderProperty);
}
```

## 2) _CameraDepthTextureをDepthOnlyPassに渡してレンダリング実行
m_DepthTextureをm_DepthPrepass(DepthOnlyPassクラス)へ追加し、EnqueuePassでレンダリング実行キューへ登録しています。

```ForwardRenderer.cs
if (requiresDepthPrepass)
{
    m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);
    EnqueuePass(m_DepthPrepass);
}
```

DepthOnlyPass(m_DepthPrepass)では、以下のようなメソッドで描画処理が実行されていきます。
1. Configure(描画の下準備)
2. Execute(描画の実行)
3. FrameCleanup(描画の後片付け)


## 3) _CameraDepthTexture用のRenderTextureを確保しつつ、RenderTargetに指定

```DepthOnlyPass.cs
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
    cmd.GetTemporaryRT(depthAttachmentHandle.id, descriptor, FilterMode.Point);
    ConfigureTarget(depthAttachmentHandle.Identifier());
    ConfigureClear(ClearFlag.All, Color.black);
}
```

```depthAttachmentHandle``` には、ForwardRendererの ```m_DepthTexture``` が入っています。


## 4) Executeメソッドでレンダリング実行
```DepthOnlyPass.cs
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;
        var drawSettings = CreateDrawingSettings(m_ShaderTagId, ref renderingData, sortFlags);
        drawSettings.perObjectData = PerObjectData.None;

        ref CameraData cameraData = ref renderingData.cameraData;
        Camera camera = cameraData.camera;
        if (cameraData.isStereoEnabled)
        {
            context.StartMultiEye(camera, eyeIndex);
        }

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

    }
    context.ExecuteCommandBuffer(cmd);
    CommandBufferPool.Release(cmd);
}
```

m_ShaderTagId はDepthOnlyが指定されています。

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


## 5) FrameCleanupメソッドでRenderTextureの解放
Configureメソッドで確保レンダーテクスチャの解放などを行います。

```DepthOnlyPass.cs
public override void FrameCleanup(CommandBuffer cmd)
{
    if (cmd == null)
        throw new ArgumentNullException("cmd");

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

```depthAttachmentHandle``` には、ForwardRendererの ```m_DepthTexture``` が入っています。


# ソースコード解読 : ForwardRenderer.cs
ForwardRenderer.csの93行目あたりに、**_CameraDepthTexture** 用の識別子**m_DepthTexture**を作成している箇所があります。
m_DepthTextureの定義場所は39行目あたりです。

```ForwardRenderer.cs
RenderTargetHandle m_DepthTexture;
...
m_DepthTexture.Init("_CameraDepthTexture");
```

Initメソッドの実装は以下のようになっています。

```RenderTargetHandle.cs
public void Init(string shaderProperty)
{
    id = Shader.PropertyToID(shaderProperty);
}
```

Initメソッドでは、文字列"_CameraDepthTexture" を ShaderプロパティIDへ変換し、struct内部に保存しているようです。

参考 RenderTargetHandle
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@7.1/api/UnityEngine.Rendering.Universal.RenderTargetHandle.html

## Depth描画パスの実行 
**m_DepthTexture** は **m_DepthPrepass** に渡されて、Pass内部でデプス情報の書き込みが行われます。
m_DepthPrepassは**EnqueuePass**メソッドによって、レンダリングの実行キューへ登録されます。

```ForwardRenderer.cs
if (requiresDepthPrepass)
{
    m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);
    EnqueuePass(m_DepthPrepass);
}
```

m_DepthPrepass は **DepthOnlyPass** 型で定義されており、
レンダリングのタイミング(**RenderPassEvent**)、レンダリング対象の条件(**RenderQueueRange**、**OpaqueLayerMask**)を指定してnewしています。

```ForwardRenderer.cs
DepthOnlyPass m_DepthPrepass;
...
m_DepthPrepass = new DepthOnlyPass(RenderPassEvent.BeforeRenderingPrepasses, RenderQueueRange.opaque, data.opaqueLayerMask);
```

### cameraTargetDescriptor
m_DepthPrepass.Setupの第1引数の **cameraTargetDescriptor** は **RenderTextureDescriptor** 型で、以下のように作成されています。

```
RenderTextureDescriptor cameraTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
```

**RenderTextureDescriptor** はRenderTextureの作成に使用するデータを詰め込んだstructです。

参考
RenderTextureDescriptor : https://docs.unity3d.com/ScriptReference/RenderTextureDescriptor.html
RenderingData : https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@7.0/api/UnityEngine.Rendering.Universal.RenderingData.html


# ソースコード解読 : DepthOnlyPass.cs
どうやら、**_CameraDepthTexture** への書き込みは、**DepthOnlyPass**の中で行われているみたいです。<br>
今度はDepthOnlyPassの中身の実装を読んでいきます。<br>

<details><summary>DepthOnlyPass.cs</summary><div>


```DepthOnlyPass.cs
using System;

namespace UnityEngine.Rendering.Universal.Internal
{
    /// <summary>
    /// Render all objects that have a 'DepthOnly' pass into the given depth buffer.
    ///
    /// You can use this pass to prime a depth buffer for subsequent rendering.
    /// Use it as a z-prepass, or use it to generate a depth buffer.
    /// </summary>
    public class DepthOnlyPass : ScriptableRenderPass
    {
        int kDepthBufferBits = 32;

        private RenderTargetHandle depthAttachmentHandle { get; set; }
        internal RenderTextureDescriptor descriptor { get; private set; }

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

        /// <summary>
        /// Create the DepthOnlyPass
        /// </summary>
        public DepthOnlyPass(RenderPassEvent evt, RenderQueueRange renderQueueRange, LayerMask layerMask)
        {
            m_FilteringSettings = new FilteringSettings(renderQueueRange, layerMask);
            renderPassEvent = evt;
        }

        /// <summary>
        /// Configure the pass
        /// </summary>
        public void Setup(
            RenderTextureDescriptor baseDescriptor,
            RenderTargetHandle depthAttachmentHandle)
        {
            this.depthAttachmentHandle = depthAttachmentHandle;
            baseDescriptor.colorFormat = RenderTextureFormat.Depth;
            baseDescriptor.depthBufferBits = kDepthBufferBits;

            // Depth-Only pass don't use MSAA
            baseDescriptor.msaaSamples = 1;
            descriptor = baseDescriptor;
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            cmd.GetTemporaryRT(depthAttachmentHandle.id, descriptor, FilterMode.Point);
            ConfigureTarget(depthAttachmentHandle.Identifier());
            ConfigureClear(ClearFlag.All, Color.black);
        }

        /// <inheritdoc/>
        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;
                var drawSettings = CreateDrawingSettings(m_ShaderTagId, ref renderingData, sortFlags);
                drawSettings.perObjectData = PerObjectData.None;

                ref CameraData cameraData = ref renderingData.cameraData;
                Camera camera = cameraData.camera;
                if (cameraData.isStereoEnabled)
                {
                    context.StartMultiEye(camera, eyeIndex);
                }

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

            }
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        /// <inheritdoc/>
        public override void FrameCleanup(CommandBuffer cmd)
        {
            if (cmd == null)
                throw new ArgumentNullException("cmd");

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

```

</div></summary>

## 1 : コンストラクタ
DepthOnlyPassのコンストラクタは以下のように定義されています。

```DepthOnlyPass.cs
/// <summary>
/// Create the DepthOnlyPass
/// </summary>
public DepthOnlyPass(RenderPassEvent evt, RenderQueueRange renderQueueRange, LayerMask layerMask)
{
    m_FilteringSettings = new FilteringSettings(renderQueueRange, layerMask);
    renderPassEvent = evt;
}
```

DepthOnlyPassのコンストラクタは、ForwardRendererのコンストラクタの中で実行されています。

```ForwardRenderer.cs
public ForwardRenderer(ForwardRendererData data) : base(data)
{
(省略)
    m_DepthPrepass = new DepthOnlyPass(RenderPassEvent.BeforeRenderingPrepasses, RenderQueueRange.opaque, data.opaqueLayerMask);
```


data.opaqueLayerMask は ForwardRendererData上で設定したOpaque Layer Mask の値が入ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/c22fb87b-ccbb-62b7-84e8-5a0f1864673a.png)

## DepthOnlyPass コンストラクタ まとめ
以上のことを踏まえると、以下のことが分かります。
・影の描画直後にDepthOnlyPassを実行 (RenderPassEvent.BeforeRenderingPrepasses)
・**RenderQueueRange** は Opaque(レンダーキューが 0 ~ 2500のものだけ)を描画対象にする
・OpaqueLayerMaskで指定されたものだけを描画する


## 2 : Setupメソッド
Setupメソッドは以下のように定義されています。

```DepthOnlyPass.cs
/// <summary>
/// Configure the pass
/// </summary>
public void Setup(
    RenderTextureDescriptor baseDescriptor,
    RenderTargetHandle depthAttachmentHandle)
{
    this.depthAttachmentHandle = depthAttachmentHandle;
    baseDescriptor.colorFormat = RenderTextureFormat.Depth;
    baseDescriptor.depthBufferBits = kDepthBufferBits;

    // Depth-Only pass don't use MSAA
    baseDescriptor.msaaSamples = 1;
    descriptor = baseDescriptor;
}
```

```baseDescriptor``` は、レンダーテクスチャを作成するための情報を詰め込んだstructです。
```depthAttachmentHandle``` は、レンダーテクスチャの確保・解放時に使用されるハンドルです


ForwardRenderer.csの240行目あたりで、Setupメソッドが実行されています。

```ForwardRenderer.cs
if (requiresDepthPrepass)
{
    m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);
    EnqueuePass(m_DepthPrepass);
}
```

```cameraTargetDescriptor```, ```m_DepthTexture``` は以下のように実装されています。

```ForwardRenderer.cs
RenderTextureDescriptor cameraTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
```

```ForwardRenderer.cs
RenderTargetHandle m_DepthTexture;
...
m_DepthTexture.Init("_CameraDepthTexture");
```



## Setupメソッド まとめ
Setupメソッドでは以下を行っているようです。
・カラーフォーマット(colorFormat)をRenderTextureFormat.Depthに設定
・デプスバッファのprecisionビット数(depthBufferBits)を設定
・MSAAを無効化するように上書き (baseDescriptor.msaaSamples = 1)
・最後にbaseDescriptorをメンバ変数(descriptor)へコピーする

```csharp:DepthOnlyPass.Setup()
baseDescriptor.colorFormat = RenderTextureFormat.Depth;
baseDescriptor.depthBufferBits = kDepthBufferBit

// Depth-Only pass don't use MSAA
baseDescriptor.msaaSamples = 1;
descriptor = baseDescriptor;
```


RenderTextureDescriptor
https://docs.unity3d.com/ScriptReference/RenderTextureDescriptor.html

### depthAttachmentHandle
```depthAttachmentHandle``` は 一時的なレンダーテクスチャを作成するための名前として使用されます。

```DepthOnlyPass.cs
private RenderTargetHandle depthAttachmentHandle { get; set; }
...
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
    cmd.GetTemporaryRT(depthAttachmentHandle.id, descriptor, FilterMode.Point);
    ConfigureTarget(depthAttachmentHandle.Identifier());
    ConfigureClear(ClearFlag.All, Color.black);
}
```

確保したレンダーテクスチャの**解放処理**にもdepthAttachmentHandle は使用されます。

```DepthOnlyPass.cs
public override void FrameCleanup(CommandBuffer cmd)
{
    if (cmd == null)
        throw new ArgumentNullException("cmd");

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

## Setupメソッド まとめ
・**RenderTextureDescriptor** はDepthテクスチャの作成に使用する情報を詰め込んだstruct
・**RenderTargetHandle** は一時レンダーテクスチャの確保・解放の名前(識別子)として利用する値

## Configureメソッド
Configureメソッドはレンダリングの直前に実行されるメソッドです。
ここでは、一時的に使用するレンダーテクスチャの確保などを行っています。

```DepthOnlyPass.cs
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
    cmd.GetTemporaryRT(depthAttachmentHandle.id, descriptor, FilterMode.Point);
    ConfigureTarget(depthAttachmentHandle.Identifier());
    ConfigureClear(ClearFlag.All, Color.black);
}
```

## Executeメソッド
レンダリングを実行しています

```DepthOnlyPass.cs
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;
        var drawSettings = CreateDrawingSettings(m_ShaderTagId, ref renderingData, sortFlags);
        drawSettings.perObjectData = PerObjectData.None;

        ref CameraData cameraData = ref renderingData.cameraData;
        Camera camera = cameraData.camera;
        if (cameraData.isStereoEnabled)
        {
            context.StartMultiEye(camera, eyeIndex);
        }

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

    }
    context.ExecuteCommandBuffer(cmd);
    CommandBufferPool.Release(cmd);
}
```

## FrameCleanupメソッド
Configureメソッドで確保した一時レンダーテクスチャの解放を行っています。

```DepthOnlyPass.cs
public override void FrameCleanup(CommandBuffer cmd)
{
    if (cmd == null)
        throw new ArgumentNullException("cmd");

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


# 参考情報
## RenderPassEvent.BeforeRenderingPrepasses
RenderPassEventは以下のように定義されています。

```RenderPassEvent.cs
  public enum RenderPassEvent
  {
    BeforeRendering = 0,
    BeforeRenderingShadows = 50, // 0x00000032
    AfterRenderingShadows = 100, // 0x00000064
    BeforeRenderingPrepasses = 150, // 0x00000096
    AfterRenderingPrePasses = 200, // 0x000000C8
    BeforeRenderingOpaques = 250, // 0x000000FA
    AfterRenderingOpaques = 300, // 0x0000012C
    BeforeRenderingSkybox = 350, // 0x0000015E
    AfterRenderingSkybox = 400, // 0x00000190
    BeforeRenderingTransparents = 450, // 0x000001C2
    AfterRenderingTransparents = 500, // 0x000001F4
    BeforeRenderingPostProcessing = 550, // 0x00000226
    AfterRenderingPostProcessing = 600, // 0x00000258
    AfterRendering = 1000, // 0x000003E8
  }
```

RenderPrepassEvent
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@7.0/api/UnityEngine.Rendering.Universal.RenderPassEvent.html


## RenderQueueRange.opaque
RenderQueueRange.opaqueは以下のように定義されています

```RenderQueueRange.cs
    /// <summary>
    ///   <para>A range that includes only opaque objects.</para>
    /// </summary>
    public static RenderQueueRange opaque
    {
      get
      {
        return new RenderQueueRange()
        {
          m_LowerBound = 0,
          m_UpperBound = 2500
        };
      }
    }

```

## RenderOpaqueMask
**<a href = "https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@10.1/manual/urp-forward-renderer.html#filtering">公式ドキュメント</a>**によれば、Opaque Layer Maskは以下のように定義されています。

|Property|Description|
|:---:|:---:|
|Opaque Layer Mask|Select which opaque layers this Renderer draws|