はじめに
検索トップに出る以下の記事内容が上手く動かなかったため、本記事を作成しました。
◆[Unity] ステンシルバッファをImageEffectで利用するシンプルなサンプル
本記事は、Unity2017.3.1f1で動作を確認しています。
サンプルコードは図の通り動作しますが、
私自身なぜこのように記述するのか、説明ができない部分があります。
疑問点の共有、ならびに問題解決の一助となりましたら幸いです。
また、標準のポストプロセス機能は既にPostProcessingStackに置き換わっていて、
ImageEffectは古い技術となりますが、現在でも独自のポストプロセスを実装することは可能です。
ステンシルが動かない?
はじめに、私が悩まされた箇所から説明します。
後述するサンプルが動かない場合、カメラのAllow MSAA設定を確認してください。
デフォルトでカメラのMSAAが有効になっていますが、MSAAが有効になっていると、ImageEffectのOnRenderImage関数ではステンシルバッファが参照できないようです。
以下はFrameDebuggerの画面です。
シーン上にあるカメラのうち一つでもMSAAが有効になっていると、画像のようにシーンのレンダリング後/ImageEffectの処理前に、MSAAの描画パスが追加されます。
おそらく、この描画パスで(ステンシルバッファを含む)デプスバッファの情報が捨てられてしまうために、後続のImageEffectではステンシルバッファが参照できません。
MSAAを無効にすることで、問題を回避できます。
ステンシルを書き込むシェーダー
基本的なシェーダの書き方については割愛します。
Shader "SampleShader/StencilWriter" {
Properties{
_StencilRef("Stencil Reference", float) = 1
}
SubShader{
Tags{ "Queue" = "Transparent" }
Stencil{
Ref [_StencilRef]
Comp Always
Pass Replace
}
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target{
return half4(1,0,0,1);
}
ENDCG
}
}
}
ここで書き込んだステンシル値を使用して、
図の赤色部分だけを後のImageEffectで加工します。
#ステンシルバッファを利用するImageEffect
元記事と異なる内容です。
using UnityEngine;
public class StencilMask : MonoBehaviour {
public Shader shader = null;
private Material material = null;
void OnEnable() {
if (material == null) {
material = new Material(shader);
}
}
void OnRenderImage(RenderTexture source, RenderTexture dest) {
RenderTexture rt = RenderTexture.GetTemporary (source.width, source.height, 24, source.format);
Graphics.SetRenderTarget(rt.colorBuffer, source.depthBuffer);
Graphics.Blit(source, rt, material);
Graphics.Blit(rt, dest);
RenderTexture.ReleaseTemporary(rt);
}
}
元記事では[ImageEffectOpaque]タグが必須と書かれていましたが、
[ImageEffectOpaque]タグはステンシルとは関係がなく、
ImageEffectの処理順を変えるだけのものです。
◆Unityマニュアル:グラフィックスコマンドバッファ
通常のBlitでは描画の際、描画対象となるカラーバッファのみが使用されます。
SetRenderTarget関数を使用して、描画対象のデプスバッファを明示することで、次に実行されるBlitにデプスバッファを渡すことができます。
destがスクリーン(null)であるために、colorBufferは参照できません。
このためRenderTextureを使用します。
sourceのdepthBufferを使用するにもかかわらず、
RenderTexture rtのデプスを24ビットとしていることには疑問が残りますが、
0ビットした場合、ステンシルテストが必ずパスしてしまいます。
なお、Blit関数のマニュアルには、
デプスやステンシルを使用する場合、GL関数を使用してBlitと同等の処理を実装するよう、
記載されています。
https://docs.unity3d.com/ja/2017.3/ScriptReference/Graphics.Blit.html
#シェーダー
Shader "SampleShader/StencilPicker" {
Properties{
_StencilRef("Stencil Reference", float) = 1
}
SubShader{
Cull Off
ZWrite Off
ZTest Always
Stencil{
Ref [_StencilRef]
Comp Equal
}
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f {
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target {
fixed4 col = fixed4(frac(i.uv * float2(16, 9)),1,1); // 市松模様
return col;
}
ENDCG
}
}
}
#結果
以下のようにStencilWriterで描画した部分だけに、StencilPickerが描画されます。