シンプルなRadial Blurのポストエフェクトの実装方法を紹介します。
はじめに
この記事ではRadial Blurのポストエフェクトの実装方法を紹介します。
ポストエフェクトの基礎については以下の記事で紹介していますので、必要に応じて参照してください。
シェーダ(最適化前)
シェーダはこのように書きます。
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がゼロだったらそもそもコンポーネントを非アクティブにするなどしたほうがいいと思います。
これをカメラにアタッチすればエフェクトがかかります。
結果
処理負荷
SampleCountだけfor文を回しているので、SampleCountが処理負荷とほぼ比例します。
少なければモバイルでも使えるのではないかと思います。(もちろん一概には言えませんが・・)