-->


Shader "UI/BoxBlur2Pass"
{
Properties
{
[HideInInspector]_MainTex("-",2D)="white"{} //uGUI及びGraphics.Blitが標準で扱うテクスチャ
[HideInInspector]_CustomTex("-",2D)="white"{}
[HideInInspector] _BlurCount("-", float) = 0
[HideInInspector] _BlurDistance("-", float) = 0
}
SubShader
{
Tags
{
"LightMode"="UniversalForward"
"RenderPipeline" = "UniversalPipeline"
"Queue" = "Transparent"
}
Cull Off
ZWrite Off
HLSLINCLUDE
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
//uGUI Image > Source Imageにアタッチされた画像を参照できるようにする
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
//最終的に画面に出力されるテクスチャを宣言、中身のデータはC#側から渡される
TEXTURE2D(_CustomTex);
SAMPLER(sampler_CustomTex);
//C#から渡された_CustomTexのサイズを参照できるようにする
float4 _CustomTex_TexelSize;
//SRP Batcherを適用、動的に変更される数値はこの中に定義
CBUFFER_START(UnityPerMaterial)
int _BlurCount;
half _BlurDistance;
CBUFFER_END
struct Attributes
{
half2 uv : TEXCOORD0;
float4 positionOS : POSITION;
};
struct Varyings
{
half2 uv : TEXCOORD0;
float4 positionCS : SV_POSITION;
};
Varyings vert (Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
ENDHLSL
//Pass0: 一番上のPassはuGUIのCanvasRendererから自動的に呼び出される、処理的には下のPass1より後に実行される
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
//y軸にBlurをかける
half4 frag (Varyings input) : SV_Target
{
half4 color = 0;
for (int shift = -_BlurCount; shift <= _BlurCount; shift++)
{
half2 offset = half2(0, 1) * shift * _BlurDistance * _CustomTex_TexelSize;
color += SAMPLE_TEXTURE2D(_CustomTex, sampler_CustomTex, input.uv + offset);
}
//加算した回数分だけ割って平均色を出す
return color / (_BlurCount * 2 + 1);
}
ENDHLSL
}
//Pass1: C#から明示的に呼び出す
Pass
{
Blend One Zero
HLSLPROGRAM
//x軸にBlurをかける
half4 frag (Varyings input) : SV_Target
{
half4 color = 0;
for (int shift = -_BlurCount; shift <= _BlurCount; shift++)
{
half2 offset = half2(1, 0) * shift * _BlurDistance * _CustomTex_TexelSize;
color += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + offset);
}
//加算した回数分だけ割って平均色を出す
return color / (_BlurCount * 2 + 1);
}
ENDHLSL
}
}
}

using UnityEngine;
using UnityEngine.UI;
[ExecuteAlways]
/// <summary>
/// Blurを2Passを使って効率化するためのクラス
/// </summary>
public class BoxBlur2PassController : MonoBehaviour
{
[SerializeField] private Image targetImage;
[SerializeField] private Shader targetShader;
[SerializeField, Range(0, 20)] private int blurCount = 0;
[SerializeField, Range(1, 10)] private float blurDistance = 2;
private Texture2D sourceTexture;
private Material createdMaterial;
private RenderTexture tmpRenderTexture;
//変更があったときだけ更新するためのキャッシュ
private int _lastBlurCount;
private float _lastBlurDistance;
/// <summary>
/// 初期化処理の必要性がある場合trueを返す
/// </summary>
private bool NeedsInitialization => sourceTexture == null || sourceTexture != targetImage.sprite.texture ||
createdMaterial == null || targetImage.material != createdMaterial ||
tmpRenderTexture == null;
/// <summary>
/// インスペクターのパラメータに変化があったかチェック
/// </summary>
private bool CahngedParameter => blurCount != _lastBlurCount || !Mathf.Approximately(blurDistance, _lastBlurDistance);
/// <summary>
/// 初期化処理
/// </summary>
private void Init()
{
Release();
sourceTexture = targetImage.sprite.texture;
createdMaterial = new (targetShader);
createdMaterial.hideFlags = HideFlags.DontSave;
targetImage.material = createdMaterial;
//処理負荷軽減のため、大元の画像サイズから1/2のサイズでtmpRenderTextureを作成
//tmpRenderTextureはぼかし処理のための画像なので、サイズが小さくても見た目に違和感は出ない
tmpRenderTexture = new RenderTexture(sourceTexture.width / 2, sourceTexture.height /2, 0);
//事前にBlitで更新される予定のtmpRenderTextureを、_CustomTexの名前でシェーダー側と紐づけておく
//後はCanvasRender内の処理で自動的にPass0を実行させて、その_CustomTexを画像に反映させる
targetImage.material.SetTexture("_CustomTex", tmpRenderTexture);
//初期化後は問答無用でUpdateTextureを呼ぶ
UpdateTexture();
}
/// <summary>
/// 最初にUnityが1回だけ呼ぶ処理
/// </summary>
private void Start()
{
if (NeedsInitialization)
{
Init();
}
}
/// <summary>
/// Unityから毎フレーム呼ばれる処理
/// </summary>
private void Update()
{
if (targetImage == null || targetImage.sprite == null || targetShader == null)
{
return;
}
if (NeedsInitialization)
{
Init();
}
else if(CahngedParameter)
{
UpdateTexture();
}
}
/// <summary>
/// 対象のシェーダーの2Passを実行させる
/// </summary>
private void UpdateTexture()
{
_lastBlurCount = blurCount;
_lastBlurDistance = blurDistance;
//Blur設定をセット
createdMaterial.SetInt("_BlurCount", blurCount);
createdMaterial.SetFloat("_BlurDistance", blurDistance);
//第1引数の画像を元に、Blitでシェーダー内のPass1を実行させ、結果を第2引数に書き込む
//Blitの仕様上、この書き込み処理は、第3引数のMaterial内の_MainTexを通して行われる
Graphics.Blit(sourceTexture, tmpRenderTexture, createdMaterial, 1);
}
/// <summary>
/// 作成したRenderTextureやMaterialのメモリを解放する
/// </summary>
private void Release()
{
if (tmpRenderTexture != null)
{
tmpRenderTexture.Release();
}
if (createdMaterial != null)
{
DestroyImmediate(createdMaterial);
}
}
/// <summary>
/// このGameObjectを非表示にした時の処理
/// </summary>
private void OnDisable()
{
Release();
}
}


