Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@r-ngtm

【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

シェーダーグラフで_CameraDepthTextureを使ってみる

_CameraDepthTextureという名前のTexture2Dを定義すると、テクスチャを介してデプス値が取れます。

_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が指定されています。

    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_DepthTexturem_DepthPrepass に渡されて、Pass内部でデプス情報の書き込みが行われます。
m_DepthPrepassはEnqueuePassメソッドによって、レンダリングの実行キューへ登録されます。

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

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

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

cameraTargetDescriptor

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

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の中で行われているみたいです。

今度はDepthOnlyPassの中身の実装を読んでいきます。

DepthOnlyPass.cs
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;
            }
        }
    }
}

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

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)へコピーする

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

公式ドキュメントによれば、Opaque Layer Maskは以下のように定義されています。

Property Description
Opaque Layer Mask Select which opaque layers this Renderer draws
4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
r-ngtm

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
4
Help us understand the problem. What is going on with this article?