Unity UI分割ぼかしシェーダー

Unity UI分割ぼかしシェーダー

[Unity URP] UI分割ぼかしシェーダー(UI Separable Blur Shader)。
マルチパスで画像をブレさせる処理。

関連ページ
前のページ で画像をぼかすBlurシェーダーを実装しましたが、処理負荷という意味では課題がありました。
このページで紹介する
Separable Blur
は、マルチパスを使ってこの問題を解決します。

この分割ぼかしを応用した 発光シェーダー もあるのでそちらもどうぞ。

分割ぼかしシェーダー概要

インスペクターから
BlurCount
を上げると段々と画像がぼけていきます。
C#から
RenderTexture
を流し込んでマルチパスを管理しているので、シェーダーコードの他にC#側の実装も必要になります。
シェーダー効果自体は前ページの1Passぼかしシェーダーと全く一緒です。

BlurCountを最大値の20にした場合、前ページの1Passぼかしでは1ピクセルにつき1681回画像を読み込みんでいました。
このページの2Passぼかし処理であれば、1ピクセルにつき82回までに処理を圧縮できます。

シェーダー側の実装

下がシェーダー側のコード全文です。適当な.shaderファイルを作成し、中に全てコピペしてください。
コードの中身は UIシェーダー基礎 マルチパス 記事を見るとより理解しやすいです。
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
        }        
    }
}

C#側の実装

下がC#側のコード全文です。
このC#のコードから、Blitを使ってさきほどのシェーダーのPassを呼び出します。
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();
    }
}

作成した
BoxBlur2PassController
を何かしらのGameObjectにアタッチしてください。
そこから、インスペクターの
TargetImage
に対してシェーダーを適用させたい画像をアタッチしてください。
また
TargetrSahder
には上で追加したシェーダーコードのファイルをアタッチします。

パラメータの解説

用意してあるパラメータは2つのみです。C#側から操作します。

BlurCount

冒頭で紹介した通り、値を増やすと画像のぼかし回数が増えます。

BlurDistance

値を増やすと、サンプリングするピクセル色の座標が遠くにズレていきます。
結果的にどんどん拡張したアウトラインが広がり、その代わり形が不正確で曖昧になっていきます。

0
0