Unityのポストプロセスで平均ブラーを実装する方法

Unityの公式アドオンで提供されているポストプロセスには、一般的なぼかしエフェクトが含まれていません。トランジションやオプション表示といった画面の転換にブラーを使いたいというケースはけっこうあるように思います。今回はそんなブラーの実装を詳しく解説していこうと思います。

Contents

平均ブラーの原理

今回実装するブラーは、平均ブラーと呼ばれるものです。平均ブラーは、周囲のテクセルから色の平均値を計算するブラー方式です。中心となるテクセルから、周囲8ピクセル分のカラーを合算し、その平均を取る、という手順です。

一回目の平均を取った後に、テクスチャサイズを半分にして二回目の平均を取ります。その後、さらにテクスチャサイズを半分にして三回目の平均を取ります。そして、最後的にサイズが1/4になったテクスチャをもとのサイズに戻して完成です。

平均ブラーの欠点

平均ブラーの大きなデメリットは、ブラーサイズを大きくすると、オブジェクトの輪郭がダブって見えることです。このように、平均ブラーではあまり強いブラーをかけることができません。

C#でのオフスクリーン処理

平均ブラーでは、スクリプトによるオフスクリーン処理も重要です。先ほど説明した、平均を取るプロセスを3回呼び出しています。なお、このスクリプトは、カメラコンポーネントを持つゲームオブジェクトにアタッチしてください

using UnityEngine;

// !Cameraコンポーネントを持つゲームオブジェクトにアタッチしてください!
// ExecuteInEditMode            : プレイしなくても動作させる
// ImageEffectAllowedInSceneView: シーンビューにポストエフェクトを反映させる
[ExecuteInEditMode, ImageEffectAllowedInSceneView]
public class BasicBlurEffectInserter : MonoBehaviour
{
    [SerializeField]
    private Material _material;

    private int resolution;
    private Vector2 displaySize;

    private void Awake()
    {
        resolution = Shader.PropertyToID("_Resolution"); //シェーダーのプロパティIDを検索
    }

    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        displaySize.x = Screen.currentResolution.width; //ディスプレイの高さを取得
        displaySize.y = Screen.currentResolution.height; //ディスプレイの幅を取得
        _material.SetVector(resolution, displaySize); //シェーダーのプロパティに流し込み

        //////////横幅を半分にしたレンダーテスクチャを作成(まだ、なにも描かれていない)
        var rth = RenderTexture.GetTemporary(src.width / 2, src.height); 
        Graphics.Blit(src, rth, _material); //シェーダー処理を加えて、横半分のレンダーテクスチャにコピー
        /////////
        
        /////////先のテクスチャサイズから、さらに縦半分にしたレンダーテスクチャを作成(まだ、なにも描かれていない)
        var rtv = RenderTexture.GetTemporary(rth.width, rth.height / 2); 
        Graphics.Blit(rth, rtv, _material); //シェーダー処理を加えて、縦半分のレンダーテクスチャにコピー
        /////////

        Graphics.Blit(rtv, dest, _material); //元サイズから1/4になったレンダーテクスチャを、元のサイズに戻す
        
        RenderTexture.ReleaseTemporary(rtv); //テンポラリレンダーテスクチャの開放
        RenderTexture.ReleaseTemporary(rth); //開放しないとメモリリークするので注意
    }
}

OnRenderImage()は、Unityのフレーム描画サイクルの中で呼び出されるコールバックで、カメラのレンダリングが完了したときに発火します。後述するシェーダー処理を、Graphis.Blitというメソッドを通すことで適用しています。平均ブラーのポストプロセスでは、Graphics.Blitを三回呼び出しているので、ドローコールは三回分の負荷となっています。

シェーダー内でのブラー処理

フラグメントシェーダー内でのブラー処理は、以下の通りです。計算のスタートとなるテクセルを中心に、周囲8テクセル分を取得し、その平均を返します。_BlurLevelはマテリアルのプロパティから、_Resolutionは前述のC#スクリプトから動的に渡しています。

float _BlurLevel; //ブラーの強度
float2 _Resolution; //C#から渡ってくるディスプレイサイズ

fixed4 frag(v2f i) : SV_Target
{
    float offsetU = _BlurLevel / _Resolution.x; //U方向のオフセットを計算
    float offsetV = _BlurLevel / _Resolution.y; //V方向のオフセットを計算
    
    float4 color = tex2D(_MainTex, i.uv);

    color += tex2D(_MainTex, i.uv + float2(offsetU, 0.0f)); //右のテクセルのカラーをサンプリング
    
    color += tex2D(_MainTex, i.uv + float2(-offsetU, 0.0f)); //左のテクセルのカラーをサンプリング
    
    color += tex2D(_MainTex, i.uv + float2(0.0f, offsetV)); //下のテクセルのカラーをサンプリング
    
    color += tex2D(_MainTex, i.uv + float2(0.0f, -offsetV)); //上のテクセルのカラーをサンプリング
    
    color += tex2D(_MainTex, i.uv + float2(offsetU, offsetV)); //右下のテクセルのカラーをサンプリング
    
    color += tex2D(_MainTex, i.uv + float2(offsetU, -offsetV)); //右上のテクセルのカラーをサンプリング
    
    color += tex2D(_MainTex, i.uv + float2(-offsetU, offsetV)); //左下のテクセルのカラーをサンプリング
    
    color += tex2D(_MainTex, i.uv + float2(-offsetU, -offsetV)); //左上のテクセルのカラーをサンプリング

    color /= 9.0f; //8テクセル分の色が加算されているので、9で除算し平均化

    return color;
}

シェーダー全体

ここまで解説してきたコードをまとめると、以下のようになります。なお、このシェーダーを一回だけ適用しても、効果的なブラーはかかりません。前述したC#スクリプトと合わせて使用する必要があるので、注意してください。

Shader "CAGraphicsAcademy/BasicBlur"
{
    Properties{
        [HideInInspector] _MainTex("Texture", 2D) = "white" {}
        _BlurLevel("Blur Level", int) = 1
    }

    SubShader
    {
        Cull Off        // カリングは不要
        ZTest Always    // ZTestは常に通す
        ZWrite Off      // ZWriteは不要

        Tags { "RenderType" = "Opaque" }

        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;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _BlurLevel; //ブラーの強度
            float2 _Resolution; //C#から渡ってくるディスプレイサイズ

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

            fixed4 frag(v2f i) : SV_Target
            {
                float offsetU = _BlurLevel / _Resolution.x; //U方向のオフセットを計算
                float offsetV = _BlurLevel / _Resolution.y; //V方向のオフセットを計算
                
                float4 color = tex2D(_MainTex, i.uv);

                color += tex2D(_MainTex, i.uv + float2(offsetU, 0.0f)); //右のテクセルのカラーをサンプリング
                
                color += tex2D(_MainTex, i.uv + float2(-offsetU, 0.0f)); //左のテクセルのカラーをサンプリング
                
                color += tex2D(_MainTex, i.uv + float2(0.0f, offsetV)); //下のテクセルのカラーをサンプリング
                
                color += tex2D(_MainTex, i.uv + float2(0.0f, -offsetV)); //上のテクセルのカラーをサンプリング
                
                color += tex2D(_MainTex, i.uv + float2(offsetU, offsetV)); //右下のテクセルのカラーをサンプリング
                
                color += tex2D(_MainTex, i.uv + float2(offsetU, -offsetV)); //右上のテクセルのカラーをサンプリング
                
                color += tex2D(_MainTex, i.uv + float2(-offsetU, offsetV)); //左下のテクセルのカラーをサンプリング
                
                color += tex2D(_MainTex, i.uv + float2(-offsetU, -offsetV)); //左上のテクセルのカラーをサンプリング

                color /= 9.0f; //8テクセル分の色が加算されているので、9で除算し平均化

                return color;
            }
            ENDCG
        }
    }
}

まとめ

今回紹介した平均ブラーは、大きなブラーをかけられないというデメリットがあるものの、考え方が非常に簡単で、初めてポストプロセスを実装する方にもおすすめです。もうすこし実践的にきれいなブラーを実装したいという方は、ガウシアンブラーで調べてみてください。こちらは加重平均を用いたブラーなので、より自然なボケ味を表現することができます。

返信を残す

メールアドレスが公開されることはありません。