はじめに
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が取得できるようになります。
シェーダーグラフで_CameraDepthTextureを使ってみる
_CameraDepthTextureという名前のTexture2Dを定義すると、テクスチャを介してデプス値が取れます。
_CameraDepthTextureの使い方が分かったところで、次に_CameraDepthTextureの内部的な実装を追っていきたいと思います。
今回読むcsファイル
ファイル名 | 内容 |
---|---|
ForwardRenderer.cs | URPのフォワードレンダリングの実装 |
DepthOnlyPass.cs | DepthOnlyパスを持つオブジェクトだけを描画するパス |
まとめ
最初に、_CameraDepthTexture
にデプス値が書き込まれるまでの流れをまとめます。
1) _CameraDepthTextureのシェーダープロパティIDを作成
ForwardRenderer.cs にて "_CameraDepthTexture"のハンドルが作成されます。
RenderTargetHandle m_DepthTexture;
...
m_DepthTexture.Init("_CameraDepthTexture");
Initは以下のような実装になっています。
public int id { set; get; }
...
public void Init(string shaderProperty)
{
id = Shader.PropertyToID(shaderProperty);
}
2) _CameraDepthTextureをDepthOnlyPassに渡してレンダリング実行
m_DepthTextureをm_DepthPrepass(DepthOnlyPassクラス)へ追加し、EnqueuePassでレンダリング実行キューへ登録しています。
if (requiresDepthPrepass)
{
m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);
EnqueuePass(m_DepthPrepass);
}
DepthOnlyPass(m_DepthPrepass)では、以下のようなメソッドで描画処理が実行されていきます。
- Configure(描画の下準備)
- Execute(描画の実行)
- FrameCleanup(描画の後片付け)
3) _CameraDepthTexture用のRenderTextureを確保しつつ、RenderTargetに指定
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メソッドでレンダリング実行
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メソッドで確保レンダーテクスチャの解放などを行います。
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行目あたりです。
RenderTargetHandle m_DepthTexture;
...
m_DepthTexture.Init("_CameraDepthTexture");
Initメソッドの実装は以下のようになっています。
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メソッドによって、レンダリングの実行キューへ登録されます。
if (requiresDepthPrepass)
{
m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);
EnqueuePass(m_DepthPrepass);
}
m_DepthPrepass は DepthOnlyPass 型で定義されており、
レンダリングのタイミング(RenderPassEvent)、レンダリング対象の条件(RenderQueueRange、OpaqueLayerMask)を指定してnewしています。
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の中で行われているみたいです。
今度はDepthOnlyPassの中身の実装を読んでいきます。
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のコンストラクタは以下のように定義されています。
/// <summary>
/// Create the DepthOnlyPass
/// </summary>
public DepthOnlyPass(RenderPassEvent evt, RenderQueueRange renderQueueRange, LayerMask layerMask)
{
m_FilteringSettings = new FilteringSettings(renderQueueRange, layerMask);
renderPassEvent = evt;
}
DepthOnlyPassのコンストラクタは、ForwardRendererのコンストラクタの中で実行されています。
public ForwardRenderer(ForwardRendererData data) : base(data)
{
(省略)
m_DepthPrepass = new DepthOnlyPass(RenderPassEvent.BeforeRenderingPrepasses, RenderQueueRange.opaque, data.opaqueLayerMask);
data.opaqueLayerMask は ForwardRendererData上で設定したOpaque Layer Mask の値が入ってきます。
DepthOnlyPass コンストラクタ まとめ
以上のことを踏まえると、以下のことが分かります。
・影の描画直後にDepthOnlyPassを実行 (RenderPassEvent.BeforeRenderingPrepasses)
・RenderQueueRange は Opaque(レンダーキューが 0 ~ 2500のものだけ)を描画対象にする
・OpaqueLayerMaskで指定されたものだけを描画する
2 : Setupメソッド
Setupメソッドは以下のように定義されています。
/// <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メソッドが実行されています。
if (requiresDepthPrepass)
{
m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);
EnqueuePass(m_DepthPrepass);
}
cameraTargetDescriptor
, m_DepthTexture
は以下のように実装されています。
RenderTextureDescriptor cameraTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
RenderTargetHandle m_DepthTexture;
...
m_DepthTexture.Init("_CameraDepthTexture");
Setupメソッド まとめ
Setupメソッドでは以下を行っているようです。
・カラーフォーマット(colorFormat)をRenderTextureFormat.Depthに設定
・デプスバッファのprecisionビット数(depthBufferBits)を設定
・MSAAを無効化するように上書き (baseDescriptor.msaaSamples = 1)
・最後にbaseDescriptorをメンバ変数(descriptor)へコピーする
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
は 一時的なレンダーテクスチャを作成するための名前として使用されます。
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 は使用されます。
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メソッドはレンダリングの直前に実行されるメソッドです。
ここでは、一時的に使用するレンダーテクスチャの確保などを行っています。
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
cmd.GetTemporaryRT(depthAttachmentHandle.id, descriptor, FilterMode.Point);
ConfigureTarget(depthAttachmentHandle.Identifier());
ConfigureClear(ClearFlag.All, Color.black);
}
Executeメソッド
レンダリングを実行しています
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メソッドで確保した一時レンダーテクスチャの解放を行っています。
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は以下のように定義されています。
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は以下のように定義されています
/// <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 |