✍️

【Unity/URP】一部のオブジェクトをポストエフェクトの対象外にする➀(カラーと深度の書き込み)

2025/01/15に公開

はじめに

この記事では、UnityのURPにおいて指定したオブジェクトがポストエフェクトの影響を受けないようにする方法を紹介します。ポストエフェクトの対象を制御する方法はいくつかありますが、いずれもシーンにTransparentsが混在する場合がネックになると思います。今回は、この対策に重点を置いた方法を考えたいと思います。

方針

ざっくり下のような流れでレンダリングを行うことで実現を目指します。
ちょっと長くなるので2回の記事に分けて掲載します。この記事では➁➂の解説がメインになります。

➀ 通常どおり対象オブジェクトを描画
➁ RenderPassで対象オブジェクトの色をテクスチャに書き込む
➂ RenderPassで対象以外のオブジェクトの深度をテクスチャに書き込む
➃ 通常どおりポストエフェクトをかける
➄ ➁と➂のテクスチャを使って遮蔽物がない部分だけ対象オブジェクトを再描画

正直なところ、この方法はパフォーマンスや表現力の面ではあまり最適な方法とは言えません...
ただし、一度導入してしまえばポストエフェクトやオブジェクトのシェーダーを加工する必要がないのでお手軽です。一つの提案として受け取ってもらえたら幸いです。

完成すると下の画像ような感じになります。この例の場合、カプセル型のオブジェクトを対象にしています。

ポストエフェクトあり

ポストエフェクトなし

コード

動作環境
Unity 2022.3.17f1
URP 14.0.9

今回解説するスクリプトのみ掲載します。全文はgithubをご参考ください。

CustomTexturePass.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace IgnorePostProcessing
{
    /// <summary>
    /// テクスチャに書き込む値のタイプ.
    /// </summary>
    public enum TextureType
    {
        Color,
        Depth
    }

    public class CustomTexturePass : ScriptableRenderPass
    {
        /// <summary>
        /// 出力先のRenderTextureの設定.
        /// </summary>
        public class TextureSettings
        {
            public ClearFlag clearFlag;
            public Color clearColor;
            public string textureName;
            public RenderTextureFormat format;
            public int depthBufferBits;

            public TextureSettings(ClearFlag clearFlag, Color clearColor, RenderTextureFormat format, int depthBufferBits, string textureName)
            {
                this.clearFlag = clearFlag;
                this.clearColor = clearColor;
                this.format = format;
                this.depthBufferBits = depthBufferBits;
                this.textureName = $"_{textureName}";
            }
        }

        public RTHandle destination;

        List<ShaderTagId> _shaderTagIds;
        ProfilingSampler _profilingSampler;
        FilteringSettings _filteringSettings;
        TextureSettings _textureSettings;
        Shader _overrideShader;
        int _shaderPassIndex;

        public CustomTexturePass(string samplerName, LayerMask layerMask, TextureType textureType, Shader overrideShader, int shaderPassIndex)
        {
            // Transparentsの後に描画する. すべてのレンダーキューのオブジェクトが対象.
            renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
            _profilingSampler = new ProfilingSampler(samplerName);
            _filteringSettings = new FilteringSettings(RenderQueueRange.all, layerMask);

            // 設定されたTextureTypeに応じてRenderTextureの構成を決める.
            _textureSettings = textureType == TextureType.Color
                ? new TextureSettings(ClearFlag.Color, Color.clear, RenderTextureFormat.ARGB32, 0, samplerName)
                : new TextureSettings(ClearFlag.Depth, Color.black, RenderTextureFormat.Depth, 32, samplerName);
            
            _overrideShader = overrideShader;
            _shaderPassIndex = shaderPassIndex;

            // デフォルトのシェーダーパスを設定.
            _shaderTagIds = new List<ShaderTagId>()
            {
                new ShaderTagId("SRPDefaultUnlit"),
                new ShaderTagId("UniversalForward"),
                new ShaderTagId("UniversalForwardOnly")
            };
        }

        public void Setup(RenderTextureDescriptor descriptor)
        {
            // レンダーテクスチャを確保.
            descriptor.colorFormat = _textureSettings.format;
            descriptor.depthBufferBits = _textureSettings.depthBufferBits;
            descriptor.msaaSamples = 1;
            RenderingUtils.ReAllocateIfNeeded(ref destination, descriptor);
        }
        
        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            // レンダーターゲットを設定.
            ConfigureTarget(destination);
            ConfigureClear(_textureSettings.clearFlag, _textureSettings.clearColor);
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            var cmd = CommandBufferPool.Get();
            using (new ProfilingScope(cmd, _profilingSampler))
            {
                context.ExecuteCommandBuffer(cmd);
                cmd.Clear();
                
                // 描画方法の設定.
                var drawingSettings = CreateDrawingSettings(_shaderTagIds, ref renderingData, SortingCriteria.BackToFront);

                if (_overrideShader != null)
                {
                    // シェーダーをオーバーライド.
                    drawingSettings.overrideShader = _overrideShader;
                    drawingSettings.overrideShaderPassIndex = _shaderPassIndex;
                }

                // テクスチャにレンダリングし、シェーダーに共有する.
                context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings);
                Shader.SetGlobalTexture(_textureSettings.textureName, destination);
            }
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        public void Dispose()
        {
            destination?.Release();
        }
    }
}
CustomTexture.cs
using UnityEngine;
using UnityEngine.Rendering.Universal;

namespace IgnorePostProcessing
{
    public class CustomTexture : ScriptableRendererFeature
    {
        [SerializeField] TextureType _textureType;
        [SerializeField] LayerMask _layerMask;
        [SerializeField] Shader _shader;
        [SerializeField] int _shaderPassIndex;

        public CustomTexturePass Pass { get; private set; }

        public override void Create()
        {
            Pass = new CustomTexturePass(name, _layerMask, _textureType, _shader, _shaderPassIndex);
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            Pass.Setup(renderingData.cameraData.cameraTargetDescriptor);
            renderer.EnqueuePass(Pass);
        }

        protected override void Dispose(bool disposing)
        {
            Pass?.Dispose();
            base.Dispose(disposing);
        }
    }
}

CustomTexturePass(RenderPass)

まず、Transparentsの描画が完了した後にオブジェクトの色と深度をレンダーテクスチャに書き込むようにします。このために、CustomTexturePassというRenderPassを自作します。

TextureSettings

今回は、一つのRenderPassでカラー用・深度用とテクスチャの設定を切り替えやすいように、専用のクラスを用意しました。

CustomTexturePass.cs
/// <summary>
/// 出力先のRenderTextureの設定.
/// </summary>
public class TextureSettings
{
    public ClearFlag clearFlag;
    public Color clearColor;
    public string textureName;
    public RenderTextureFormat format;
    public int depthBufferBits;

    public TextureSettings(ClearFlag clearFlag, Color clearColor, RenderTextureFormat format, int depthBufferBits, string textureName)
    {
        this.clearFlag = clearFlag;
        this.clearColor = clearColor;
        this.format = format;
        this.depthBufferBits = depthBufferBits;
        this.textureName = $"_{textureName}";
    }
}

clearFlag:リセットの対象を決めます。カラーバッファか深度バッファかなど。
clearColor:何色でリセットするか決めます。
format:データの表現方法を決めます。使用するチャネルやビット数など。
depthBufferBits:深度バッファの精度を決めます。
textureName:レンダーテクスチャの名前を決めます。

コンストラクタ

コンストラクタで先程のクラスや各変数を初期化します。

CustomTexturePass.cs
public CustomTexturePass(string samplerName, LayerMask layerMask, TextureType textureType, Shader overrideShader, int shaderPassIndex)
{
    // Transparentsの後に描画する. すべてのレンダーキューのオブジェクトが対象.
    renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
    _profilingSampler = new ProfilingSampler(samplerName);
    _filteringSettings = new FilteringSettings(RenderQueueRange.all, layerMask);

    // 設定されたTextureTypeに応じてRenderTextureの構成を決める.
    _textureSettings = textureType == TextureType.Color
        ? new TextureSettings(ClearFlag.Color, Color.clear, RenderTextureFormat.ARGB32, 0, samplerName)
        : new TextureSettings(ClearFlag.Depth, Color.black, RenderTextureFormat.Depth, 32, samplerName);
    
    _overrideShader = overrideShader;
    _shaderPassIndex = shaderPassIndex;

    // デフォルトのシェーダーパスを設定.
    _shaderTagIds = new List<ShaderTagId>()
    {
        new ShaderTagId("SRPDefaultUnlit"),
        new ShaderTagId("UniversalForward"),
        new ShaderTagId("UniversalForwardOnly")
    };
}

renderPassEventRenderPassEvent.AfterRenederingTransparents に設定するとTransparentsの描画後にPassが実行されます。
また、描画対象をフィルタリングする FilteringSettingsRenderQueueRange.all で初期化することで、指定したレイヤーの全てのオブジェクトが描画されます。
ShaderTagId はどのシェーダーパスで描画を行うかを設定します。ここではURPで一般的な3つのパスを使っています。

Setup()・Configure()

パスを実行する前のレンダーターゲットを設定する処理を見ていきます。

CustomTexturePass.cs
public void Setup(RenderTextureDescriptor descriptor)
{
    // レンダーテクスチャを確保.
    descriptor.colorFormat = _textureSettings.format;
    descriptor.depthBufferBits = _textureSettings.depthBufferBits;
    descriptor.msaaSamples = 1;
    RenderingUtils.ReAllocateIfNeeded(ref destination, descriptor);
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
    // レンダーターゲットを設定.
    ConfigureTarget(destination);
    ConfigureClear(_textureSettings.clearFlag, _textureSettings.clearColor);
}

Setup()では、先ほど作成したTextureSettingsをもとにレンダーテクスチャの特性を決めるdescriptorを設定し、テクスチャを確保しています。 msaaSampler を1にすることで、アンチエイリアスを行わずにレンダリングできます。
Configure()では、確保したテクスチャをレンダーターゲットに指定し、ターゲットをリセットする方法を設定しています。

Execute()

このメソッド内でテクスチャへの描画を実行します。

CustomTexturePass.cs
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
    var cmd = CommandBufferPool.Get();
    using (new ProfilingScope(cmd, _profilingSampler))
    {
        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();
        
        // 描画方法の設定.
        var drawingSettings = CreateDrawingSettings(_shaderTagIds, ref renderingData, SortingCriteria.BackToFront);

        if (_overrideShader != null)
        {
            // シェーダーをオーバーライド.
            drawingSettings.overrideShader = _overrideShader;
            drawingSettings.overrideShaderPassIndex = _shaderPassIndex;
        }

        // テクスチャにレンダリングし、シェーダーに共有する.
        context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings);
        Shader.SetGlobalTexture(_textureSettings.textureName, destination);
    }
    context.ExecuteCommandBuffer(cmd);
    CommandBufferPool.Release(cmd);
}

CreateDrawingSettings() で使用するシェーダーや描画の順序などを決める構造体を初期化します。今回は SortingCriteria.BackToFront で奥のオブジェクトから描画されるようにしました。
また、各オブジェクトのシェーダーを上書きできるように overrideShader という変数を書き変える処理も入れています。
最後に context.DrawRenderers() を呼び出せばレンダリングが実行されます。

CustomTexture(RendererFeature)

RenderPassをパイプラインに組み込むには、RendererFeatureを用意する必要があります。
ここではCustomTexutreというRendererFeatureを作成し、URPのRendererに追加していきます。
コードを再掲しますが、特別なことはしていないので解説は省略します。

CustomTexture.cs
public class CustomTexture : ScriptableRendererFeature
{
    [SerializeField] TextureType _textureType;
    [SerializeField] LayerMask _layerMask;
    [SerializeField] Shader _shader;
    [SerializeField] int _shaderPassIndex;

    public CustomTexturePass Pass { get; private set; }

    public override void Create()
    {
        Pass = new CustomTexturePass(name, _layerMask, _textureType, _shader, _shaderPassIndex);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        Pass.Setup(renderingData.cameraData.cameraTargetDescriptor);
        renderer.EnqueuePass(Pass);
    }

    protected override void Dispose(bool disposing)
    {
        Pass?.Dispose();
        base.Dispose(disposing);
    }
}

Rendererに追加

ここはUnityエディタ上の操作です。作成したRendererが使えるようにします。
使用しているUniversal Renderer Dataアセットを選択し、【Add Renderer Feature>Custom Texture】を選んで追加します。

今回は、オブジェクトの色と深度をそれぞれテクスチャに格納したいので、この操作を2回行って2つのCustomTextureを追加しました。

CustomTextureを設定

色を格納するためのCustomTextureは下のように設定しました。

LayerMaskに指定している【IgnorePostProcessing】は、対象オブジェクトに割り当てるために追加しておいたレイヤーです。
また、深度用のCustomTextureは次のように設定してあります。

深度は対象以外のオブジェクトについて記録しておきたいので、LayerMaskでIngnorePostProcessingのチェックを外しました。
また、ここでは全ての描画をURPのLitシェーダーで行うように設定しました。ShaderPassIndexに指定している【3】というのは、このシェーダーの3番目(0から数えて)のパスを使うという意味で、これはDepthOnlyという名前の付いた深度書き込み用のパスになっています。


FrameDebuggerで確認してみると、作成した2つのRenderPassが実行されていることがわかります。


CustomColorTexture


CustomDepthTexture
※わかりにくい...

最後に

ひとまず今回の記事はここまでとなります。次の記事では、上記の結果を利用してポストエフェクト後にオブジェクトを再度描画する処理を作ります。
ここまで読んでいただきありがとうございました!次の記事も読んでもらえたら嬉しいです。

リンク

https://github.com/kr405/UnityIgnorePostProcessing
https://zenn.dev/kr405/articles/f8bf26ad3dd29c

Discussion