1. サイトトップ
  2. ブログ
  3. Unity
  4. 【Unity】URPでカメラを分けずに任意のオブジェクトにポストプロセッシングをかける

【Unity】URPでカメラを分けずに任意のオブジェクトにポストプロセッシングをかける

こんにちは!情熱開発部プログラム課の倉平です。

4月も中旬となり来るGWの予定を立て始めている頃ではないでしょうか?
いくつになっても大型連休はワクワクしますね。
私は気になるゲームがちらほら出ているので連休中はガッツリ遊ぼうと思っています!

さて、主題に入りましょう!
特定のオブジェクトにだけ効果をかけたいけど、カメラを増やすのも面倒だなと感じることがあると思います。
今日はそんな時に役に立つカメラ追加無しで描画分けを行う方法を説明させていただきます。

今回の記事はSRPをある程度触ったことがある人向けとなっております。
描画Passの追加等の説明は省かせて頂いております。
こちらご了承ください。

使用したUnityのバージョン

  • Unity 2020.3.32f1

■概要説明

青、赤のPlaneオブジェクトがあるシンプルなシーンです。

こちらにカスタムエフェクトでセピア調効果をかけると当たり前ですが全体にかかります。

ステンシルバッファを使用して描画分けを行えば、
下の様に特定の箇所だけに効果をかけることが出来ます。
また、カメラ追加無しで行えます。

仕組みを簡単に説明するとこんな感じです。

■描画分けを実装してみる

①ステンシルバッファ描画用Passを追加

こちらステンシルバッファ描画Passの内容です。

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

// ステンシルバッファ描画Pass
public class DrawStencilRendererPass : ScriptableRenderPass
{
    private const string ProfilerTag = nameof(DrawStencilRendererPass);
    private new readonly ProfilingSampler profilingSampler = new ProfilingSampler(ProfilerTag);

    public DrawStencilRendererPass()
    {
        renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        var cmd = CommandBufferPool.Get(ProfilerTag);
        using (new ProfilingScope(cmd, profilingSampler))
        {
            context.ExecuteCommandBuffer(cmd);
            cmd.Clear();

            var camera = renderingData.cameraData.camera;

            // ステンシルバッファを描画対象にする
            var renderMgr = RenderManager.Instance;
            if (renderMgr.StencilBuffer == null)    // 無ければ生成
            {
                renderMgr.CreateStencilBuffer( new Vector2Int(camera.pixelWidth, camera.pixelHeight) );
            }
            ConfigureTarget( renderMgr.StencilBuffer );
            ConfigureClear( ClearFlag.All, Color.clear );

            // ステンシルバッファへ描画を行う
            SortingSettings sortingSettings = new SortingSettings(camera) { criteria = SortingCriteria.CommonOpaque };
            FilteringSettings filteringSettings = new FilteringSettings(
                                                    RenderQueueRange.all,
                                                    camera.cullingMask
                                                    );
            List<ShaderTagId> shaderTagIds = new List<ShaderTagId>
            {
                new ShaderTagId( "DrawStencil" )
            };
            var drawingSettings = CreateDrawingSettings(shaderTagIds, ref renderingData, SortingCriteria.CommonTransparent);
            context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);

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

Execute関数内でステンシルバッファへオブジェクトごとのリファレンス値の書き込みを行っています。

こちらに対応するためにオブジェクトのshaderにはステンシルバッファ描画用Passを追加します。
リファレンス値は描画分けに応じで変更してください。

        // ステンシルバッファ用 ステンシル書き込みPass
        Pass
        {
            Tags
            {
                "LightMode" = "DrawStencil"
            }

            Stencil
            {
                Ref 10 // 描画分けに応じで値は変える
                Comp Always
                Pass Replace
            }

            ColorMask 0
            ZTest LEqual
            ZWrite On
        }

②カスタムエフェクトのshader内に描画分け処理を追加

エフェクトのPropertiesとPass内に以下を追加します。

    Properties
    {    
        // 効果をかける対象のリファレンス値
        _StencilRef("Stencil Ref", Int) = 0
    Pass
    {    
        Stencil
        {
            Ref [_StencilRef]
            Comp Equal    // 描画先のリファレンス値と等しければPassを実行
        }

_StencilRefに設定された値が描画先のリファレンス値と一致している箇所だけに描画を行う処理です。

③カスタムエフェクトPass内に描画分け処理を追加

こちら変更前のカスタムエフェクトPassのエフェクト描画処理です。

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if( material == null ) return;

        if( !renderingData.cameraData.postProcessEnabled ) return;

        if( !volume.IsActive() ) return;

        // 描画先取得
        var renderTarget = afterPostProcessTexture.Identifier();

        var cmd = CommandBufferPool.Get(ProfilerTag);
        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();

        using (new ProfilingScope(cmd, profilingSampler))
        {
            // 一時的なRenderTextureを生成
            // Depthは使用しないので無しにする
            var tempTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
            tempTargetDescriptor.depthBufferBits = 0;
            cmd.GetTemporaryRT( tempRenderTargetHandle.id, tempTargetDescriptor );
            
            // 一時的なRenderTextureへグレースケールをかけた描画を写す
            // その後描画先へ描画結果をBlit
            Blit( cmd, renderTarget, tempRenderTargetHandle.id, material );
            Blit( cmd, tempRenderTargetHandle.id, renderTarget );
            
            // 一時的なRenderTextureを解放する
            cmd.ReleaseTemporaryRT( tempRenderTargetHandle.id );

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

こちらを描画分け処理に対応します。

ステンシルバッファ描画用Passで作成したステンシルバッファを一時的なRenderTextureの代わりにすることで実装出来ます。

また、リファレンス値設定をVolumeに追加して可変出来るようにもしています。

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if( material == null ) return;

        if( !renderingData.cameraData.postProcessEnabled ) return;

        if( !volume.IsActive() ) return;

        var renderMgr = RenderManager.Instance;
        if( renderMgr == null ) return;

        // ステンシルバッファ取得
        var stencilBuffer = renderMgr.StencilBuffer;
        if( stencilBuffer == null ) return;

        // 描画先取得
        var renderTarget = afterPostProcessTexture.Identifier();

        var cmd = CommandBufferPool.Get(ProfilerTag);
        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();

        using (new ProfilingScope(cmd, profilingSampler))
        {
            // 効果をかけるステンシルのリファレンス値を設定
            material.SetInt(stencilRefPropertyId, volume.GetStencilRef());

            // 現在の描画内容をステンシルバッファにコピー。
            // 指定リファレンス値の部分だけに効果をかける
            Blit( cmd, renderTarget, stencilBuffer );
            Blit( cmd, renderTarget, stencilBuffer, material );

            // ステンシルバッファに保存された描画結果を描画先へコピー
            Blit( cmd, stencilBuffer, renderTarget );

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

④個別オブジェクトへエフェクトをかけてみよう

青Planeには10、赤Planeには20のリファレンス値を書き込むようにshaderに設定します。

その後、エフェクトの対象リファレンス値を10に設定することで、青Planeだけに効果がかかりました。

対象リファレンス値を赤Planeの20にすることで、赤Planeだけにも効果がかかることが確認できます。

■ 最後に

如何だったでしょうか。カメラ追加無しで描画分けを行う方法を説明させていただきました。
描画分けを行いたいけどカメラ追加はしたくない、そんな時には是非とも活用していただければ幸いです。

参考サイト

今回の記事を書くにあたり、以下のサイトを参考にさせていただきました。

【Unity】Uiversal Render Pipelineでカスタムポストエフェクトを実装する(公式未対応バージョン)


【免責事項】

本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。