Unity Shaderことはじめ２ ガウシアンブラー

# はじめに

ぼかしを使いたい場面があったのでガウシアンブラーについて調べた。

ぼかしはざっくり言うととある画素とその周辺画素の色を混ぜた色を出力すること。

単純な混ぜ方だと、注目画素と周辺画素10個の色を足して10で割ると平均フィルタ
になる。(画素色*0.1して足す)

画素にかける係数を変えると色々と特徴が変わり、ガウス関数から係数を決定したものがガウシアンブラーと言われる。

フィルタ周りの数学はちゃんと調べるととっても難しいので気力があれば頭がいい人の解説を見てみるといいかも
[学校で使ってた参考書](http://www.amazon.co.jp/dp/4274132641/ref=cm_sw_r_tw_dp_68uwwb1EYZ7TB)

# 参考にしたもの

- Unity > Menu > Assets > Import Package > Effects > Image Effects > Scripts > BlurOptimized
- [川瀬式縮小バッファブラー](https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=8&ved=0ahUKEwi61fCR9rHJAhXoUKYKHRwEAb8QFghLMAc&url=http%3A%2F%2Fwww.siliconstudio.co.jp%2Frd%2Fpresentations%2Ffiles%2FCEDEC2009%2FCEDEC2009_Anti-DownsizedBufferArtifacts.ppt&usg=AFQjCNFOD0hD5R6C86ZIo5zYIBOMhJnoRg&bvm=bv.108194040,d.dGY)
- [ガウシアンブラー](http://t-pot.com/program/79_Gauss/index.html)

# 画素にかける係数の求め方

ガウス関数は分散σで幅が変わり、３σでほぼ高さ０になる。
注目画素と片方向に３つづつサンプリングする場合、σ=1としてσ＝１、２、３の位置で高さを求め係数とする。
　※求めた係数は規格化定数Nで割るの忘れないこと！
サンプリング数を決めたので規格化定数Nはi,j=0〜3として計算する。

Unityのブラーシェーダーの変数curve4に計算結果が入っているので検算してみてもいいかも。

# ぼかしのポイント

- 画像を縮小してからぼかすとぼかし効果大、処理負荷低減(川瀬式縮小バッファブラー)
- 横方向処理パス、縦方向処理パスと処理をシェーダーパスを分割(ガウシアンフィルタの特性)
- 画素サンプリング距離を大きく取るとぼかし効果大
- ぼかし効果を繰り返すとぼかし効果大、処理負荷増大

# UnityのBlurシェーダー使用方法

CameraにBlurスクリプトをつければよい。
スクリプトをInspecterでみるとBlurシェーダーが設定されているので、
スクリプトをつけると自動でシェーダーがリンクされる。

# UnityのBlurシェーダー読み

Blur絡みのところだけ抜粋。
パス1で垂直方向に処理した後、パス2で水平方向を処理してぼかしをかける。

```
//HiddenでInspectorから選べなくなる。
//ImageEffectでScriptから呼ぶので隠してある
Shader "Hidden/FastBlur" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	
	CGINCLUDE
		#include "UnityCG.cginc"

		//入力画像 Unityから自動で渡される
		sampler2D _MainTex;
		//入力画像1ピクセルあたりの幅高さ
		uniform half4 _MainTex_TexelSize;
		//サンプリング距離 何画素間隔？
		uniform half4 _Parameter;
			
		//画像縮小 頂点シェーダー出力
		struct v2f_tap
		{
			float4 pos : SV_POSITION;
			half2 uv20 : TEXCOORD0;
			half2 uv21 : TEXCOORD1;
			half2 uv22 : TEXCOORD2;
			half2 uv23 : TEXCOORD3;
		};			
		//画像縮小 頂点シェーダー
		v2f_tap vert4Tap ( appdata_img v )
		{
			v2f_tap o;

			o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
        		//画像サンプリング位置
        		o.uv20 = v.texcoord + _MainTex_TexelSize.xy;				
			o.uv21 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h,-0.5h);	
			o.uv22 = v.texcoord + _MainTex_TexelSize.xy * half2(0.5h,-0.5h);		
			o.uv23 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h,0.5h);		

			return o; 
		}					
		//画像縮小フラグメントシェーダー 4点サンプリングして平均色を出力する
		fixed4 fragDownsample ( v2f_tap i ) : SV_Target
		{				
			fixed4 color = tex2D (_MainTex, i.uv20);
			color += tex2D (_MainTex, i.uv21);
			color += tex2D (_MainTex, i.uv22);
			color += tex2D (_MainTex, i.uv23);
			return color / 4;
		}

		// 画素にかける係数 ガウス関数から算出
		static const half curve[7] = { 0.0205, 0.0855, 0.232, 0.324, 0.232, 0.0855, 0.0205 };  // gauss'ish blur weights
		//頂点シェーダー出力
		struct v2f_withBlurCoords8 
		{
			float4 pos : SV_POSITION;
			half4 uv : TEXCOORD0;
			//uv値に直したサンプリング間隔
			half2 offs : TEXCOORD1;
		};	

		//水平方向ガウシアンブラー頂点シェーダー
		v2f_withBlurCoords8 vertBlurHorizontal (appdata_img v)
		{
			v2f_withBlurCoords8 o;
			o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
			
			o.uv = half4(v.texcoord.xy,1,1);
			//1ピクセルあたりの大きさ*サンプリング画素間隔でuvオフセット値を計算しておいてフラグメントシェーダーに渡す
			o.offs = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x;

			return o; 
		}
		//垂直報告ガウシアンブラー 頂点シェーダー
		v2f_withBlurCoords8 vertBlurVertical (appdata_img v)
		{
			v2f_withBlurCoords8 o;
			o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
			
			o.uv = half4(v.texcoord.xy,1,1);
			o.offs = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _Parameter.x;
			 
			return o; 
		}	
		//ガウシアンブラーフラグメントシェーダー
		half4 fragBlur8 ( v2f_withBlurCoords8 i ) : SV_Target
		{
			half2 uv = i.uv.xy; 
			half2 netFilterWidth = i.offs;  
			half2 coords = uv - netFilterWidth * 3.0;  
			
			half4 color = 0;
			//８画素係数をかけて足す
  			for( int l = 0; l < 7; l++ )  
  			{   
				half4 tap = tex2D(_MainTex, coords);
				color += tap * curve4[l];
				coords += netFilterWidth;
  			}
			return color;
		}
	ENDCG
	
	SubShader {
	  ZTest Off Cull Off ZWrite Off Blend Off
	// パス0 画像縮小
	Pass { 
		CGPROGRAM
		
		#pragma vertex vert4Tap
		#pragma fragment fragDownsample
		
		ENDCG
	}

	// パス1 垂直方向処理
	Pass {
		ZTest Always
		Cull Off
		
		CGPROGRAM 
		
		#pragma vertex vertBlurVertical
		#pragma fragment fragBlur8
		
		ENDCG 
		}	
		
	// パス2 水平方向処理
	Pass {		
		ZTest Always
		Cull Off
				
		CGPROGRAM
		
		#pragma vertex vertBlurHorizontal
		#pragma fragment fragBlur8
		
		ENDCG
		}	
	FallBack Off
}
```

# UnityのBlurスクリプト読み

ガウシアンブラーに関わるところだけ抜粋

```
using System;
using UnityEngine;

namespace UnityStandardAssets.ImageEffects
{
    //再生ボタンを押さなくてもスクリプト実行する
    [ExecuteInEditMode]
    //アタッチしたGameObjectにカメラコンポーネントがなければ自動追加
    [RequireComponent (typeof(Camera))]
    //コンポーネントメニューに項目追加
    [AddComponentMenu ("Image Effects/Blur/Blur (Optimized)")]

    public class BlurOptimized : PostEffectsBase
    {
        //パラメータの範囲を0-2に制限
        [Range(0, 2)]
        //ぼかしをかける前に画像をどれだけ縮小するか
        //1/(2^downsample)サイズに縮小
        public int downsample = 1;

        [Range(0.0f, 10.0f)]
        //ぼかし範囲 シェーダーの_Parameterに渡す
        public float blurSize = 3.0f;

        [Range(1, 4)]
        //何回ぼかし処理を繰り返すか
        public int blurIterations = 2;

        //シェーダー
        public Shader blurShader = null;
        //シェーダーを反映するマテリアル
        private Material blurMaterial = null;

        //ブラー使用可能ならマテリアル準備
        public override bool CheckResources () {
            CheckSupport (false);

            blurMaterial = CheckShaderAndCreateMaterial (blurShader, blurMaterial);

            if (!isSupported)
                ReportAutoDisable ();
            return isSupported;
        }
        //無効状態になったらマテリアルを即座に破棄
        public void OnDisable () {
            if (blurMaterial)
                DestroyImmediate (blurMaterial);
        }
        //全てのレンダリングが完了し RenterTexture にレンダリングされた後に呼び出される
        public void OnRenderImage (RenderTexture source, RenderTexture destination) {
            if (CheckResources() == false) {
                Graphics.Blit (source, destination);
                return;
            }

            //画面何分の一？
            float widthMod = 1.0f / (1.0f * (1<<downsample));
            //原画像バイリニア補間
            source.filterMode = FilterMode.Bilinear;
            //縮小画像サイズ
            int rtW = source.width >> downsample;
            int rtH = source.height >> downsample;

            //画像縮小のため一時テクスチャを確保
            RenderTexture rt = RenderTexture.GetTemporary (rtW, rtH, 0, source.format);
            //一時テクスチャもバイリニア補間
            rt.filterMode = FilterMode.Bilinear;
            //シェーダーパス0で元画像を縮小して一時テクスチャに転送
            Graphics.Blit (source, rt, blurMaterial, 0);
            //指定回数ぼかし処理を行う
            for(int i = 0; i < blurIterations; i++) {
                //ぼかしサンプリング距離。繰り返し毎に広げる
                float iterationOffs = (i*1.0f);
                //シェーダーにサンプリング距離設定。xだけ使う
                blurMaterial.SetVector ("_Parameter", new Vector4 (blurSize * widthMod + iterationOffs, -blurSize * widthMod - iterationOffs, 0.0f, 0.0f));

                // 垂直方向ぼかし結果保存のため一時テクスチャ確保
                RenderTexture rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format);
                rt2.filterMode = FilterMode.Bilinear;
                // ぼかし処理
                Graphics.Blit (rt, rt2, blurMaterial, 1);
                // 垂直ぼかしができたため原画像は不要。縮小画像一時テクスチャ解放
                RenderTexture.ReleaseTemporary (rt);
                rt = rt2;

                // 水平方向ぼかし。垂直方向と同様
                rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format);
                rt2.filterMode = FilterMode.Bilinear;
                Graphics.Blit (rt, rt2, blurMaterial, 2 + passOffs);
                RenderTexture.ReleaseTemporary (rt);
                rt = rt2;
            }

            //ぼかしが終わったので結果テクスチャに転送
            Graphics.Blit (rt, destination);
            //一時テクスチャ解放
            RenderTexture.ReleaseTemporary (rt);
        }
    }
}

```