知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】ガウシアンブラー【1】 #64

前回の成果

soramamenatan.hatenablog.com

CommandBufferを使用して、色を反転させた。


今回やること

今回はガウシアンブラーを使用して、ブラーをしていこうと思います。

参考サイト様

edom18.hateblo.jp


事前準備

まず、Scene上にQuadを配置して適当なスケールにします。

f:id:soramamenatan:20200731175742p:plain

そして、QuadのMeshRendererのElement0にデフォルトのUnlitShaderで制作したMaterialをアタッチします。

最後に、今回制作するScriptをアタッチします。
Textureには以下を、

f:id:soramamenatan:20200731180122p:plain

動物園のイラスト | かわいいフリー素材集 いらすとや:より引用

Shaderには今回制作するShaderをアタッチしてください。
f:id:soramamenatan:20200731175856p:plain

念の為、UnlitShaderを貼っておきます。
ソースコードは何も触っていないです。

UnlitShader
Shader "Unlit/GaussianBlurUnlit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}


ソースコード

using UnityEngine;

public class GaussianBlur : MonoBehaviour {
    [SerializeField]
    private Texture _texture;

    [SerializeField]
    private Shader _shader;

    [SerializeField, Range(1f, 10f)]
    private float _offset = 1f;

    [SerializeField, Range(10f, 1000f)]
    private float _blur = 10f;

    private Material _material;
    private Renderer _renderer;
    private RenderTexture _rt1;
    private RenderTexture _rt2;
    private float[] _weights = new float[10];
    private bool _isInitialized = false;

    private void Awake() {
        Initialize();
    }

    private void OnValidate() {
        if (!Application.isPlaying) {
            return;
        }
        UpdateWeights();
        Blur();
    }

    private void Initialize() {
        if (_isInitialized) {
            return;
        }
        _material = new Material(_shader);
        _material.hideFlags = HideFlags.HideAndDontSave;
        _rt1 = RenderTexture.GetTemporary(_texture.width / 2, _texture.height / 2, 0, RenderTextureFormat.ARGB32);
        _rt2 = RenderTexture.GetTemporary(_texture.width / 2, _texture.height / 2, 0, RenderTextureFormat.ARGB32);
        _renderer = GetComponent<Renderer>();
        UpdateWeights();
        _isInitialized = true;
    }

    public void Blur() {
        if (!_isInitialized) {
            Initialize();
        }
        Graphics.Blit(_texture, _rt1);
        _material.SetFloatArray("_Weights", _weights);
        float x = _offset / _rt1.width;
        float y = _offset / _rt1.height;
        _material.SetVector("_Offset", new Vector4(x, 0, 0, 0));
        Graphics.Blit(_rt1, _rt2, _material);
        _material.SetVector("_Offset", new Vector4(0, y, 0, 0));
        Graphics.Blit(_rt2, _rt1, _material);
        _renderer.material.mainTexture = _rt1;
    }

    private void UpdateWeights() {
        float total = 0;
        float d = _blur * _blur * 0.001f;
        for (int i = 0; i < _weights.Length; i++) {
            float x = 1.0f + i * 2f;
            float w = Mathf.Exp(-0.5f * (x * x) / d);
            _weights[i] = w;
            if (i > 0) {
                w *= 2.0f;
            }
            total += w;
        }

        for (int i = 0; i < _weights.Length; i++) {
            _weights[i] /= total;
        }
    }

    private void OnDestroy() {
        RenderTexture.ReleaseTemporary(_rt1);
        RenderTexture.ReleaseTemporary(_rt2);
    }
}


Shader

Shader "Unlit/GaussianBlur" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest

            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            half4 _Offset;
            static const int samplingCount = 10;
            half _Weights[samplingCount];

            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 = 0;

                [unroll]
                for (int j = samplingCount - 1; j > 0; j--) {
                    col += tex2D(_MainTex, i.uv - (_Offset.xy * j)) * _Weights[j];
                }

                [unroll]
                for (int j = 0; j < samplingCount; j++) {
                    col += tex2D(_MainTex, i.uv + (_Offset.xy * j)) * _Weights[j];
                }
                return col;
            }
            ENDCG
        }
    }
}


ガウシアンブラーとは

画像処理において、ガウシアンぼかし (ガウスぼかし、ガウシアンブラー、ガウシアンフィルター、ガウスフィルター、Gaussian Blur)とは、ガウス関数をもちいて画像をぼかす処理である。
デジタルカメラの撮像画像などからノイズを除去したり、アンシャープマスク処理、エッジ抽出の前処理などに応用できる。

ガウシアンぼかし - Wikipedia:より引用

簡単に説明すると、ガウス関数で画像をぼかす処理のことです。
以下の画像を見ていただくとわかりやすいかと思います。

f:id:soramamenatan:20200731180842p:plain

Extending Unity 5 rendering pipeline: Command Buffers - Unity Technologies Blog:より引用


ガウス関数

ガウシアンブラーは、ガウス関数で画像をぼかす処理と前述しました。
では、ガウス関数について説明します。

ガウス関数は以下の式になります。

 \displaystyle
y = \frac{1}{\sqrt{2 \pi  σ}} exp\biggl(- \frac{(x - μ)^2}{2 σ^2}\biggr)

グラフは以下のような釣り鐘のような形になります。

f:id:soramamenatan:20200731181830p:plain

定義されている数を変化させることで、ガウス関数がどのように変化するか見ていきます。


数値を変化させてグラフの変化を見る

σを変化

  • 黄色 : σ = 1
  • 緑色 : σ = 0.1
  • 青色 : σ = 0.05

f:id:soramamenatan:20200731182758p:plain

σを変化させると、グラフの傾斜が大きくなることがわかります。


μを変化

  • 黄色 : μ = 0.5
  • 緑色 : μ = 0
  • 青色 : μ = -0.5

f:id:soramamenatan:20200731183210p:plain

μを変化させると、グラフの傾斜はそのままで横にズレることがわかります。


今回利用する形で変化

今回は、中央がx=0でyの最大値が1となるガウス関数を使用します。

ですので、定義は以下となります。

 \displaystyle
y = exp\biggl(- \frac{(x)^2}{2 σ^2}\biggr)

元のガウス関数と比べてみるとわかりやすいと思います。

 \displaystyle
y = \frac{1}{\sqrt{2 \pi  σ}} exp\biggl(- \frac{(x - μ)^2}{2 σ^2}\biggr)

こちらの数式のσの値を変えると変化は以下となります。

  • 黄色 : σ = 0.1
  • 緑色 : σ = 0.2
  • 青色 : σ = 0.3

f:id:soramamenatan:20200731184916p:plain

中心がx=0で、yの最大値が1になっているのがわかります。


ガウス関数でブラーする

ガウス関数の数式を変化させると、グラフがどのように変化するかが理解できました。

では、これを実際にブラーにどう落とし込むかの説明に移ります。

グラフのxをサンプリング点からの距離yを重みとして扱います。

f:id:soramamenatan:20200731191407p:plain

例として、-0.2,-0.1,0,0.1,0.2の5点を取ってみて重みをみてみようと思います。


各点での重み

-0.2~0.2までの重みは以下となります。
重みの精確さは余り重要ではないので、だいたいの値とします。

重み

f:id:soramamenatan:20200802120426p:plain

図での表記 x 重み
A -0.2 0.35
B -0.1 0.78
C 0 1
D 0.1 0.78
E 0.2 0.35

ただし、今回使用する重みはピクセルの色に乗算するものになります。
ですので、重みの合計を1にする必要があります。

正規化した重み

f:id:soramamenatan:20200802121517p:plain

図での表記 x 重み
A -0.2 0.1
B -0.1 0.24
C 0 0.3
D 0.1 0.24
E 0.2 0.1

これで各点の重みを合計した値が1となりました。
この値をピクセルの色に乗算してあげることでブラーがかかります。


ガウス関数を使う利点

ガウス関数でのxyが何を表すのかが理解できたと思います。

ガウス関数の特徴である、3次元で用いる場合にxとyを切り離して処理できることにあります。

切り離せない場合ですと、各テクセルに対して処理を行わないといけません。
ですので、仮に5x5のテクスチャだった場合に25回処理を行う必要があります。
しかし、切り離せる場合はxとyを分けて処理ができます。
なので、5+5で10回ですみます。
これが仮に100x100だとすると、10000回と200回でかなり差が出てしまいます。
ですので、処理回数を抑えるためにガウス関数を使用します。

今回は以上となります。
ここまでご視聴ありがとうございました。