ステンシルをテクスチャに書き出してポストエフェクトで使う【URP14】
ARドラム(#さくたまDrums)でURPで演出を作っているさくたまです。
- ARでのHumanStencilTextureを使ったポストエフェクトをバーチャルオブジェクトにも適用したい
- スクリーン座標で「ずらす」表現がしたい→Stencil Testをするだけではだめ
ということで,
1: エフェクトをかけたいオブジェクトのレンダリング時にステンシルを書き込み
2: ポストプロセスでステンシルをテクスチャに書き出し
3: テクスチャを使ってポストエフェクトをかける
という手順で実装しました.
URP14でのRenderTargetの書き方を調べるのに,前のバージョンが出てきたり,URPのラッパークラスをどう使うのかによってバリエーションがあり,「なぜか上手くいかない」ということが多く苦労しました.
とりあえず動くものができたのでその備忘録と,(気が向いたら)別の書き方も試すための比較として,残しておきたいと思います.勉強中で手探りでやったのでここはこうした方がいいとかあったらぜひ教えてください🙇
あまり人に読んでもらう目的でまとめられていないので現状は本当にコードの記録というだけになってますすみません
うまくいった方法
1. ステンシル書き込み
以下の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. ステンシルをテクスチャに書き出し
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を差し込める任意のマテリアルをセット
ステンシルの値があるところをテクスチャに書き出すシェーダー
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
参考にした記事・ドキュメント
Discussion