🫠

ステンシルをテクスチャに書き出してポストエフェクトで使う【URP14】

2024/07/05に公開

  • ARでのHumanStencilTextureを使ったポストエフェクトをバーチャルオブジェクトにも適用したい
  • スクリーン座標で「ずらす」表現がしたい→Stencil Testをするだけではだめ

ということで,
1: エフェクトをかけたいオブジェクトのレンダリング時にステンシルを書き込み
2: ポストプロセスでステンシルをテクスチャに書き出し
3: テクスチャを使ってポストエフェクトをかける
という手順で実装しました.

URP14でのRenderTargetの書き方を調べるのに,前のバージョンが出てきたり,URPのラッパークラスをどう使うのかによってバリエーションがあり,「なぜか上手くいかない」ということが多く苦労しました.
とりあえず動くものができたのでその備忘録と,(気が向いたら)別の書き方も試すための比較として,残しておきたいと思います.勉強中で手探りでやったのでここはこうした方がいいとかあったらぜひ教えてください🙇
あまり人に読んでもらう目的でまとめられていないので現状は本当にコードの記録というだけになってますすみません

うまくいった方法

1. ステンシル書き込み

以下のHumanStencil.shaderをつけたマテリアルを,エフェクトをかけたいオブジェクトにアタッチします.

HumanStencil.shader
Shader "Unlit/HumanStencil"
{
    Properties
    {
        _Stencil ("Human Stencil Value", Range(0, 255)) = 2
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            ColorMask 0
            ZWrite Off

            Stencil
            {
                //ステンシルの値
                Ref [_Stencil]

                //ステンシルバッファの値の判定方法
                //Alwaysなのでステンシルバッファのテストは常に通過する
                Comp Always

                //ステンシルバッファに値を書き込むかどうか
                //Replaceなので既存の値をRefの値に置き換える
                Pass Replace
            }
        }
    }
}

2. ステンシルをテクスチャに書き出し

StencilToTexRendererFeature.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class StencilToTexRendererFeature : ScriptableRendererFeature
{
    class StencilToTexturePass : ScriptableRenderPass
    {
        private readonly Material _stencilToTextureMaterial;
        private readonly Material _postEffectMaterial;
        private readonly RenderTexture _renderTexture;
        private RTHandle _cameraRenderTargetHandle;

        public StencilToTexturePass(Material stencilToTextureMaterial, Material postEffectMaterial)
        {
            _stencilToTextureMaterial = stencilToTextureMaterial;
            _postEffectMaterial = postEffectMaterial;
            _renderTexture = new RenderTexture(100, 100, 0, RenderTextureFormat.ARGB32);
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            if (cameraTextureDescriptor.width != _renderTexture.width ||
                cameraTextureDescriptor.height != _renderTexture.height)
            {
                _renderTexture.Release();
                _renderTexture.width = cameraTextureDescriptor.width;
                _renderTexture.height = cameraTextureDescriptor.height;
                _renderTexture.Create();
            }
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            if (renderingData.cameraData.camera.cameraType == CameraType.Preview)
            {
                return;
            }
            CommandBuffer cmd = CommandBufferPool.Get("StencilToTexture");
            
            _cameraRenderTargetHandle = renderingData.cameraData.renderer.cameraColorTargetHandle;
            
            // StencilをRenderTextureに書き込む
            CoreUtils.SetRenderTarget(cmd, _renderTexture, renderingData.cameraData.renderer.cameraDepthTargetHandle);
            // obsolete
            // cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, _stencilToTextureMaterial);
            Blitter.BlitTexture(cmd, _cameraRenderTargetHandle, new Vector4(1, 1, 0, 0), _stencilToTextureMaterial, 0);

            // 3. ポストエフェクトにStencilToTextureを渡し,カメラにレンダリングする
            cmd.SetRenderTarget(_cameraRenderTargetHandle);
            _postEffectMaterial.SetTexture(Shader.PropertyToID("_OcclusionTex"), _renderTexture);
            Blit(cmd, ref renderingData, _postEffectMaterial);

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

    private Material _stencilToTextureMaterial;
    public Material postEffectMaterial;

    private StencilToTexturePass _stencilToTexturePass;

    public override void Create()
    {
        _stencilToTextureMaterial = new Material(Shader.Find("Custom/StencilToTexture"));
        _stencilToTexturePass = new StencilToTexturePass(_stencilToTextureMaterial, postEffectMaterial)
        {
            renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing
        };
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(_stencilToTexturePass);
    }
}

StencilToTexRendererFeatureをアクティブなRendererにアタッチ._OcclusionTexを差し込める任意のマテリアルをセット

ステンシルの値があるところをテクスチャに書き出すシェーダー

StencilToTexture
Shader "Custom/StencilToTexture"
{
    Properties
    {
        _Stencil ("Stencil Value", Range(0, 255)) = 2
    }
    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Stencil
        {
            Ref [_Stencil]
            Comp Equal
        }
        
        Pass
        {
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment frag
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl"

            half4 frag(Varyings i) : SV_Target
            {
                return half4(1, 1, 1, 1);
            }
            
            ENDHLSL
        }
    }
}

3. テクスチャを使ってポストエフェクトをかける

_OcclusionTexを使ってよしなに.
今回のエフェクトは使い回しでShader Graphで作っています.
一回途中経過のPreviewを見ながらshaderを作れたら,スクショをChat-GPTに突っ込んでshaderLab化してもらうというのが結構ちゃんと再現されて良いという気づきも.

ARFoundationのHumanStencilTextureをセットするとこうなります

上手くいかなかったこと

  • RenderTextureではなくRTHandleを作ろうとしていた(上手く使えばRenderTextureを内部で作ってくれるはずなのできっと書き方が悪かった)
    テクスチャの初期化がうまくいかずにこんな表示になってガン萎えしました
    (この時点のコードちゃんと取っておけばよかった...何が起きたのかは結局今のところわかってない)
  • RenderTextureをnewしていた
    RenderTexture.Release()しても内部でオブジェクトは残るので,テクスチャ作り直す時はnewせずにCreate()で内部的に生成させなければいけない.でないとメモリがぐんぐんと使われて...😱
  • PostEffect系のシェーダーをCGPROGRAMで書こうとして何も起きなかった(URP,SRPに載せる処理はHLSLPROGRAMで書かないといけない)
    https://docs.unity3d.com/ja/2021.3/Manual/shader-shaderlab-code-blocks.html

参考にした記事・ドキュメント

https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@13.1/api/UnityEngine.Rendering.Blitter.html#UnityEngine_Rendering_Blitter_BlitTexture_UnityEngine_Rendering_CommandBuffer_UnityEngine_Rendering_RTHandle_UnityEngine_Vector4_System_Single_System_Boolean_
https://ny-program.hatenablog.com/entry/2021/05/22/192713
https://zenn.dev/r_ngtm/articles/unity-mrt-urp10-urp14
https://qiita.com/up-hash/items/f0ab4c19fb409c954b96
https://zenn.dev/inpro/articles/e6fffb5b2da2a4

Discussion