【Unity】【シェーダ】Radial Blur(放射状ブラー)のポストエフェクトを実装する

シンプルなRadial Blurのポストエフェクトの実装方法を紹介します。

はじめに

この記事ではRadial Blurのポストエフェクトの実装方法を紹介します。
ポストエフェクトの基礎については以下の記事で紹介していますので、必要に応じて参照してください。

light11.hatenadiary.com

シェーダ(最適化前)

シェーダはこのように書きます。

Shader "Hidden/RadialBlur"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _SampleCount("Sample Count", Range(4, 16)) = 8
        _Strength("Strength", Range(0.0, 1.0)) = 0.5
    }
    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 = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            half _SampleCount;
            half _Strength;
 
            fixed4 frag (v2f i) : SV_Target
            {
                half4 col           = 0;
                // UVを-0.5~0.5に変換
                half2 symmetryUv    = i.uv - 0.5;
                // 外側に行くほどこの値が大きくなる(0~0.707)
                half distance       = length(symmetryUv);
                for(int j = 0; j < _SampleCount; j++) {
                    // jが大きいほど、画面の外側ほど小さくなる値
                    float uvOffset     = 1 - _Strength * j / _SampleCount * distance;
                    // jが大きくなるにつれてより内側のピクセルをサンプリングしていく
                    // また画面の外側ほどより内側のピクセルをサンプリングする
                    col                 += tex2D(_MainTex, symmetryUv * uvOffset + 0.5);
                }
                col                 /= _SampleCount;
                return col;
            }

            ENDCG
        }
    }
}

説明はコメントの通りです。

シェーダ(最適化済み)

前節のシェーダは説明のため、フラグメントシェーダを最適化していません。
最適化すると下記のようになります。

fixed4 frag (v2f i) : SV_Target
{
    half4 col           = 0;
    half2 symmetryUv    = i.uv - 0.5;
    half distance       = length(symmetryUv);
    half factor         = _Strength / _SampleCount * distance;
    for(int j = 0; j < _SampleCount; j++) {
        half uvOffset       = 1 - factor * j;
        col                 += tex2D(_MainTex, symmetryUv * uvOffset + 0.5);
    }
    col                 /= _SampleCount;
    return col;
}

スクリプト

シェーダが書けたらあとはスクリプトを書きます。

using UnityEngine;

[ExecuteInEditMode]
public class RadialBlur : MonoBehaviour {

    [SerializeField]
    private Shader _shader;
    [SerializeField, Range(4, 16)]
    private int _sampleCount      = 8;
    [SerializeField, Range(0.0f, 1.0f)]
    private float _strength           = 0.5f;

    private Material _material;

    private void OnRenderImage(RenderTexture source, RenderTexture dest)
    {
        if (_material == null) {
            if (_shader == null) {
                Graphics.Blit(source, dest);
                return;
            }
            else {
                _material   = new Material(_shader);
            }
        }
        _material.SetInt("_SampleCount", _sampleCount);
        _material.SetFloat("_Strength", _strength);
        Graphics.Blit(source, dest, _material);
    }
}

こっちは最適化してません。使うときにすればいいやということで・・
Strengthがゼロだったらそもそもコンポーネントを非アクティブにするなどしたほうがいいと思います。

これをカメラにアタッチすればエフェクトがかかります。

結果

f:id:halya_11:20181018232047g:plain

処理負荷

SampleCountだけfor文を回しているので、SampleCountが処理負荷とほぼ比例します。
少なければモバイルでも使えるのではないかと思います。(もちろん一概には言えませんが・・)

関連

light11.hatenadiary.com

light11.hatenadiary.com