Hitmanの"インスティンクト"風ポストエフェクト

# バージョンについて
今回の記事では
`Unity2021.3.25f1` , `URP12` , を使用しています。
本当はURP14に対応させたかったのですが......色々行き詰って今回はこうなりました m( _ _ )m
詳細は後述（有識者求む）
もし解決したら、そっちはそっちで記事にしようかと思います。


今回のレポジトリ
https://github.com/kamahir0/InstinctEffect
# 序論
**Hitman**というゲームシリーズがあります。

簡単に言うと...
作りこまれた箱庭型のステージ内を歩き回る**ターゲット**を、周囲の**NPC**にバレないよう暗殺するステルスゲーです。こ～れ大好きなんですよね私。

そんなHitmanですが、"**インスティンクト**"という便利機能が近年は標準搭載となっています。

![スクリーンショット 2024-05-07 230635.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780101/04b0a15f-ba93-6b03-89dc-94ca4ffb2c5d.png)
<br>
──さあ　キミの死角はどこかな
<br>
![スクリーンショット 2024-05-07 230647.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780101/454b6a41-c2f1-9953-cffb-2985beb6fced.png)
<br>
**スケスケだぜ！！**
<br>
と、こんな風に壁の向こう側にいる人間もシルエットで見えるようになるという。
NPCらの様子をうかがって隠密行動するゲームですから、とっても便利ですよね。

このインスティンクトのグラフィック表現に注目し、（簡単にですが）Unityで再現してみよう！というのが今回の内容です。

方法は色々と考えられますが、今回は敢えて**ポストエフェクト**でやってみようと思います。

ポストエフェクトでやる理由は、今さらビルトインレンダーでもできるような実装をフツーにやっても面白くないと考えたためです。
あと最近URPを覚えたんで、**揮ってみたいんですよね──この”力”を**。
# 作戦
上述の通りポストエフェクトで行く訳ですが、具体的にどうやるのか。
流れとしては、
1. 人オブジェクトのシェーダーでステンシルに書き込む
1. ScriptableRendererFeature/Passでポストエフェクトをかける

で、2番目のポストエフェクトでステンシルテストを行うワケです。なおRenderPassEventは`AfterRenderingTransparents` か `BeforeRenderingPostProcessing` に設定します。

## ポストエフェクトの実装
URP12のScriptableRenderPassにおいて、ポストエフェクトは`CommandBuffer.Blit`メソッドでかけられます。
```c#:InstinctPass.cs
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
//～～～～～～～～～～～～～～～～～～～～～～～～

    //一時レンダーテクスチャーを作成
    var commandBuffer = CommandBufferPool.Get();
    var desc = new RenderTextureDescriptor(cameraData.camera.scaledPixelWidth, cameraData.camera.scaledPixelHeight);
    commandBuffer.GetTemporaryRT(tempID, desc);
    
//～～～～～～～～～～～～～～～～～～～～～～～～
    
    //一時RTコピーしてからレンダーターゲットへ逆コピーすると共に、マテリアル適用でエフェクトをかける
    commandBuffer.Blit(currentTargetID, tempID);
    commandBuffer.Blit(tempID, currentTargetID, material);
    
    //処理実行
    context.ExecuteCommandBuffer(commandBuffer);
}
```
Blitメソッドは、あるレンダーテクスチャーから別のレンダーテクスチャーへとテクスチャのコピーを行うメソッドです。しかし**第３引数にマテリアルを渡すオーバーロード**が存在し、これを利用することによってコピー時にマテリアルが適用され、ポストエフェクトがかかります。

予めScriptableRendererFeatureにてSelializeFieldでシェーダーをセットしておき、そこからC#でマテリアルを作成してScriptableRenderPassにて上のコードの様に使います。（Feature/Passのコード全文はGitHubのリポジトリをご覧ください。）

となると、次はコピー時に使われるシェーダーの中身ですね。
```HLSL:InstinctEffect.shader
Shader "Hidden/InstinctEffect"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	HLSLINCLUDE
	#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
	#include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl"

	sampler2D _MainTex;
    float4 _FilterColor;
    float4 _PeopleColor;
    float4 _BossColor;
    float _Strength;

	half4 Frag0(Varyings input) : COLOR
	{
		half4 col = tex2D(_MainTex, input.uv);
        half4 effected = lerp(col, _FilterColor * col, _Strength);
        return effected;
	}

    half4 Frag1(Varyings input) : COLOR
	{
		half4 col = tex2D(_MainTex, input.uv);
        half4 effected = lerp(col, _PeopleColor * col, _Strength);
        return effected;
	}

    half4 Frag2(Varyings input) : COLOR
	{
		half4 col = tex2D(_MainTex, input.uv);
        half4 effected = lerp(col, _BossColor * col, _Strength);
        return effected;
	}
	
	ENDHLSL
	SubShader
	{
		Tags { "RenderType" = "Qpaque" "RenderPipeline" = "UniversalPipeline" }
		LOD 100
		Cull Off
		ZWrite Off
		ZTest Always

		//何もいない
        Pass
		{
            Stencil
            {
                Ref 0
                Comp Equal
            }

			HLSLPROGRAM
			#pragma vertex Vert
			#pragma fragment Frag0
			ENDHLSL
		}
        //People
        Pass
		{
            Stencil
            {
                Ref 1
                Comp Equal
            }

			HLSLPROGRAM
			#pragma vertex Vert
			#pragma fragment Frag1
			ENDHLSL
		}
        //Boss
        Pass
		{
            Stencil
            {
                Ref 2
                Comp Equal
            }
            
			HLSLPROGRAM
			#pragma vertex Vert
			#pragma fragment Frag2
			ENDHLSL
		}
	}
}
```
<br>

**すっげぇキモいコードだな！**
<br>
Shaderlabでこういう書き方はあまり見ないかもしれませんね。
これは、まず最初の`HLSLINCLUDE`～`ENDHLSL` 間で、変数やフラグメントシェーダの定義、.hlslファイルのインクルード等を記述しています。

このシェーダーは見ての通りマルチパスですが、全てのパスにおいて共通の要素が多いためこのような書き方をしました。 
おかげで各パス1つあたりの記述はかなりスッキリしています。
```HLSL:InstinctEffect.shader
Pass
{
    Stencil
    {
        Ref 0
        Comp Equal
    }

    HLSLPROGRAM
    #pragma vertex Vert
    #pragma fragment Frag0
    ENDHLSL
}
  ```
このように。

3つのパスはそれぞれ、ステンシル時の`Ref`値が0, 1, 2、またフラグメントシェーダは`Frag0`, `Frag1`, `Frag2`となっています。
これらのフラグメントシェーダは見ての通りほとんど同じ処理で、要約すれば **「_Strengthの値に応じた濃さで色を乗算する」** というものです。違うのは乗算する色だけ。

つまり、全体としてやりたいこととしては **「ステンシルの値に応じた色を各ピクセルに乗算させたい」「乗算する色の濃さはパラメータで可変にしたい」** といった感じです。

## ステンシルに書き込む側の実装
前段でステンシルテストを行っていることを示しました。
ということは、当然ステンシルに書き込む側の存在が必要となります。ぶっちゃけここまで来たらもうウィニングランですね。
```HLSL:PeopleStencil.shader
Shader "Custom/Hitman/PeopleStencil"
{
    SubShader
    {
        Tags {"RenderPipeline"="UniversalPipeline"}
        Pass
        {
            Name "InstinctStencil_L1"
            Tags {"Queue"="Geometory"}
            ZTest Always
            ZWrite Off
            ColorMask 0

            Stencil
            {
                Ref 1
                Comp Greater
                Pass Replace
            }
        }
    }
}
```
```HLSL:Hitman/BossStencil.shader
Shader "Custom/Hitman/BossStencil"
{
    SubShader
    {
        Tags {"RenderPipeline"="UniversalPipeline"}
        Pass
        {
            Name "InstinctStencil_L2"
            Tags {"Queue"="Geometory"}
            ZTest Always
            ZWrite Off
            ColorMask 0

            Stencil
            {
                Ref 2
                Comp Always
                Pass Replace
            }
        }
    }
}
```
People → NPC
Boss → ターゲット
を、それぞれ表しています。

一応 `ColorMask`にだけ触れますと....
これが0になっていると、頂点・フラグメントシェーダを定義せずとも勝手にステンシルや深度値を書き込むだけの（＝描画は行わない）シェーダーになります。

一般NPC扱いさせたいオブジェクトにはPeopleのマテリアルを、
ターゲット扱いさせたいオブジェクトにはBossのマテリアルを、追加してください。
（皆さん知っての通り、MeshRendererには複数のマテリアルがセットできるので）

# 完成
![Instinct_forQiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780101/3edf359b-679c-e5e7-66f2-5df067adcd8b.gif)
今回はかなり大ざっぱな再現なのでこんな感じです。
我こそは暗殺ステルスゲーを作らんという方がおられれば、これをベースに色々手を加えてみるのもいいんじゃないかと。**準備は一任します。**

# URP14への対応
序論で述べた通り、今回は最新のUnity2022に搭載されているURP14ではなく、それより古いURP12で実装しています。

12→14間では`RenderTargetIdentifier`、`RTHandle`まわりの仕様が大きく変わっており、`RenderTargetIdentifier`でレンダーテクスチャーを色々操作する12以前のやり方はいずれ消えるとのこと。

こりゃあURP14に対応するしかないでしょって思うんですが、私がさんざん試したところ....**ついぞポストエフェクト時のシェーダーでステンシルを正しく読み取ることができませんでした。**
書き込みはちゃんとできてるっぽいんですけどね。

原因分かる方がいらっしゃったら是非教えてほしいです。
今回は以上です。