Unityでフィルター処理してみた

Unityでフィルターを用いたエフェクト処理を色々と試してみたのでメモ。

## フィルター処理とは

オペレータと呼ばれる数値の集合と、注目ピクセルの周辺のピクセル色を一定の規則に従って計算する処理をマスク処理といいます。

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/108809/84990a65-7756-3923-37c1-666992c6a528.png)

画像の例だと、注目ピクセルとその周辺ピクセルを$S_n$で表し、オペレータの各要素を$M_n$で表した時、出力するべきピクセル色$X$は以下の式で計算できます。

```math
X = \sum_{i=1}^{9} S_iM_i
```

こうして得たピクセル色に対して、加算を行った数を除算すれば処理は完了です。

## 実装

```:MaskPattern.shader
Shader "Hidden/MaskPattern"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}

	SubShader
	{
		Cull Off ZWrite Off ZTest Always

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			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 = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;
				return o;
			}

			sampler2D _MainTex;
			uniform float _TexWidth;
			uniform float _TexHeight;
			uniform matrix _Matrix;
			uniform float _Div;

			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 ret_col;
				int u, v;
				for (u = -1; u < 2; ++u)
					for (v = -1; v < 2; ++v)
					{
						float x = i.uv.x + u / _TexWidth;
						float y = i.uv.y + v / _TexHeight;
						ret_col += tex2D(_MainTex, float2(x, y))*_Matrix[u + 1][v + 1];
					}
				return ret_col / _Div;
			}
			ENDCG
		}
	}
}
```

フィルター処理をするシェーダです。3×3のマスクに対応するようにしています。

Pass内で宣言している

* _TexWidth : テクスチャの幅
* _TexHeight : テクスチャの高さ
* _Matrix : オペレータ
* _Div : オペレータ計算後の除算に用いる

これらの変数は以下のスクリプトから代入しています。

```csharp:MaskPattern.cs
using UnityEngine;
using System.Collections;
using System;

public class MaskPattern : MonoBehaviour {
	[Tooltip("マスクパターンを実装するシェーダを持つマテリアル")]
	public Material mat;
	[Tooltip("マスク。3x3の部分しか使用しない。")]
	public Matrix4x4 matrix;
	[Tooltip("基本的にはマスク数の9、適用しないマスク値0の分の明度が必要な場合等で変更。")]
	public float div = 9;

	private void OnRenderImage(RenderTexture source, RenderTexture destination)
	{
		mat.SetFloat("_TexWidth", source.width);
		mat.SetFloat("_TexHeight", source.height);
		mat.SetMatrix("_Matrix", matrix);
		mat.SetFloat("_Div", div);
		Graphics.Blit(source, destination, mat);
	}
}
```

このスクリプトをカメラに取り付け、MaskPattern.shaderを適用したマテリアルをインスペクターからスクリプトに貼り付ければ動きます。
適宜MatrixやDivの値を変更することで、様々な効果が得られると思います。

注意ですが、**Matrixは4×4ですが、3×3の領域しか使っていません。なのでその他の領域を弄っても効果は得られません。**ガバガバな実装ですね、、ごめんなさい。
スクリプトから3×3のマトリックスを入れてやりたかったのですが、良い方法が見つけられず、変数大量に作ったりするのもなぁと思い妥協しました。もっといい方法ご存知でしたらご教示くださいませ。

## 使用例

実際に使ってみてどんな効果が得られるか見ていきたいと思います。
まず効果適用前の画像です。

![00.png](https://qiita-image-store.s3.amazonaws.com/0/108809/bc0b7997-8943-338b-c600-68fbb24e665f.png)

では、効果適用後の画像と設定したマスク値(使用できない部分は-999を入れてあります)を。

## 単純平滑化
![01.png](https://qiita-image-store.s3.amazonaws.com/0/108809/b3ab1d41-95d0-43e5-960c-84c215b195be.png)
![01.param.png](https://qiita-image-store.s3.amazonaws.com/0/108809/fcb48a74-c8c3-20b8-3281-512f2a3567f5.png)

平滑化は色の変化をゆるやかにする処理です。画像がちょっとぼやけて見えます。

## 加重平均平滑化
![02.png](https://qiita-image-store.s3.amazonaws.com/0/108809/99f46e03-b629-402a-e3a0-22e6cc595a0c.png)
![02.param.png](https://qiita-image-store.s3.amazonaws.com/0/108809/7844e355-c64f-1d7b-5df6-b71529550ecd.png)

重み付けをして、平均値を取る平滑化です。単純平滑化に比べて元の色を保持しています。
この例だとすごく微妙な違いですね、、

## エッジ抽出
![03.png](https://qiita-image-store.s3.amazonaws.com/0/108809/7f31d0b2-064f-8eb0-43d6-e38e89c7b42f.png)
![03.param.png](https://qiita-image-store.s3.amazonaws.com/0/108809/50a448cf-eae7-514c-63be-ef0d8a83f4ab.png)

エッジ抽出は画像内に含まれる輪郭線を抽出する処理です。今回使用したマスクはとても単純な横方向のエッジ検出をするマスクです。エッジ抽出のマスクパターンには

* Prewittオペレータ
* Sobelオペレータ
* Robertsオペレータ
* Robinsonオペレータ
* Kirschオペレータ

などが有名らしいです。

## 先鋭化
![04.png](https://qiita-image-store.s3.amazonaws.com/0/108809/e3534105-c2b6-21a0-ef49-791f287551ce.png)
![04.param.png](https://qiita-image-store.s3.amazonaws.com/0/108809/2e3e54da-352b-f350-2af5-f1470bbe1991.png)

先鋭化は、境界部分を鮮明にする処理です。色の変化の大きい部分を検出し、さらに変化を大きくすることで実現しています。