-->

【Unity Shader】UI日食シェーダー

【Unity Shader】UI日食シェーダー
 ※コードが古くなっていたのでURP用にコードを改修しました。(2026/08/13) 


前から作りたかった、色反転とともに周囲が光りだすシェーダーです。
前ページの 発光シェーダーをベースに 色反転シェーダーと組み合わせて作成しました。

日食シェーダー、コード全文

 Transition 
のプロパティのスライダーを弄ると、徐々に日食に近づいていきます。


応用すると、阿部寛を使って魔王降臨のような演出を作ることも出来ます。


実装としては マルチパスを使っているため、シェーダー側の実装の他にC#側のRenderTextureの実装も入っています。
このためやや処理が複雑です。

シェーダー側の実装

下がシェーダー側のコード全文になります。
適当な.shaderファイルを作成し、中に全てコピペしてください。
コードの意味は理解しなくても大丈夫ですが、 UIシェーダー基礎 マルチパス記事を見ると理解しやすいです。
Copy Code

Shader "UI/Eclipse3Pass"
{
    Properties
    {
        [HideInInspector] _MainTex("-", 2D) = "white"{} //uGUI及びGraphics.Blitが標準で扱うテクスチャ
        [HideInInspector] _CustomTex("-", 2D) = "white"{}

        [HideInInspector] _Transition ("Transition", Range(0.0, 1.0)) = 0.0
        [HideInInspector] _BlurColor("Blur Color", Color) = (1.0, 1.0, 1.0, 1.0)
        [HideInInspector] _BlurCount("Blur Count", float) = 5
        [HideInInspector] _BlurDistance("Blur Distance", float) = 3
    }

    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "Queue" = "Transparent"
        }
        LOD 100
        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)
                half _Transition;
                half4 _BlurColor;
                int _BlurCount;
                half _BlurDistance;
            CBUFFER_END

            static const half MaxTransition = 1;
            static const half HalfTransition = MaxTransition / 2;
            static const half MaxBlurPower = 10;

            struct Attributes
            {
                half2 uv : TEXCOORD0;
                float4 positionOS : POSITION;
                half4 color : COLOR;
            };

            struct Varyings
            {
                half2 uv : TEXCOORD0;
                float4 positionCS : SV_POSITION;
                half4 color : COLOR;
            };

            Varyings vert (Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = input.uv;
                output.color = input.color;
                return output;
            }

            //第二引数で指定した方向に画像をぼかす
            half4 executeBlur(half2 uv, half2 direction)
            {
                //_BlurDistanceに関しては_Transitionの値も影響させる
                //_Transitionの値が大きいほど、Blurの距離もどんどん伸びる
                half clampValue = clamp(_Transition, 0, MaxTransition) / MaxTransition;
                half fixBlurDistance = _BlurDistance * clampValue;

                //アルファ以外の値は_BlurColorを参照させる
                half4 color = half4(_BlurColor.r, _BlurColor.g, _BlurColor.b, 0);

                //上下左右、隣接するピクセル色のalphaを合算する
                for (int shift = -_BlurCount; shift <= _BlurCount; shift++)
                {
                    half2 offset = direction * shift * fixBlurDistance * _CustomTex_TexelSize;
                    half4 samplerColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv + offset);
                    color.a += samplerColor.a;
                }
                //加算した回数分だけ割って平均alphaを出す
                color.a /= (_BlurCount * 2 + 1);
                //画像の際部分を強く光らせる(画像の際から離れるほどalphaを急激に下げる)
                color.a = color.a * color.a * 2;
                return color;
            }

        ENDHLSL

        //Pass0: 一番上のPassはuGUIのCanvasRendererから自動的に呼び出される、処理的には下のPass1,2より後に実行される
        Pass
        {
            //一番最後は背景とブレンドして透過処理を行う
            Blend SrcAlpha OneMinusSrcAlpha

            HLSLPROGRAM
                //オリジナルの画像を上に被せた上で、全てのピクセル色の輝度を上げる
                half4 frag (Varyings input) : SV_Target
                {
                    //CanvasRendererから呼ばれた際は、_MainTexにはPass1, 2実行前の純粋な元の画像が代入されている
                    //この_MainTexから指定したUV座標のピクセル色を取得
                    half4 original = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                    //Image ComponentのColorを反映
                    original *= input.color;

                    //Pass1,2でBlur処理を通した画像から、指定したUV座標のピクセル色を取得
                    half4 blurred = SAMPLE_TEXTURE2D(_CustomTex, sampler_CustomTex, input.uv);

                    //originalの方の色相を反転
                    half reversalR = abs(original.r - _Transition);
                    half reversalG = abs(original.g - _Transition);
                    half reversalB = abs(original.b - _Transition);

                    //最後に到達する色を黒にするための値
                    half darkness = 1 - _Transition;
                    //黒変の変化を直線ではなくカーブにするための値を算出
                    half ease = darkness * darkness * darkness;

                    //色相の変化にカーブをかける(後半に急激に黒く変色する)
                    original.r = reversalR * ease;
                    original.g = reversalG * ease;
                    original.b = reversalB * ease;

                    //Blur画像の上に元の画像を重ねる
                    //例えば指定UV座標のalphaが1なら、完全に元の画像のピクセルを描画、0なら完全なBlur画像を描画
                    //0.5なら両者の中間のピクセル色が描画される
                    half4 combine = lerp(blurred, original, original.a);
                    return combine;
                }
            ENDHLSL
        }

        //Pass1: C#から明示的に呼び出す
        Pass
        {
            //背景は考慮せず画像単体で描画処理
            Blend One Zero

            HLSLPROGRAM
                //x軸にBlurをかける
                half4 frag (Varyings input) : SV_Target
                {
                    return executeBlur(input.uv, half2(1, 0));
                }
            ENDHLSL
        }

        //Pass2: C#から明示的に呼び出す
        Pass
        {
            //背景は考慮せず画像単体で描画処理
            Blend One Zero

            HLSLPROGRAM
                //y軸にBlurをかける
                half4 frag (Varyings input) : SV_Target
                {
                    return executeBlur(input.uv, half2(0, 1));
                }
            ENDHLSL
        }
    }
}

本来、シェーダーは直接uGUI Imageにアタッチできないので、マテリアルを作成する必要があります。
しかし今回の場合、後述のC#側のコードが自動でそれをやってくれます。

C#側の実装

下がC#側のコード全文です。
このC#のコードから、Blitを使ってさきほどのシェーダーのPassを呼び出します。
Copy Code

using System;
using UnityEngine;
using UnityEngine.UI;

[ExecuteAlways]
/// <summary>
/// RenderTextureをシェーダーに流し込んで日食演出を行う
/// </summary>
public class Eclipse3PassController : MonoBehaviour
{
    [Serializable]
    /// <summary>
    /// シェーダーに渡すパラメータはこちらで管理
    /// </summary>
    private class Parameters
    {
        [SerializeField, Range(0, 1)] private float _transition = 0;
        public float Transition => _transition;
        [SerializeField] private Color _blurColor = Color.white;
        public Color BlurColor => _blurColor;
        [SerializeField, Range(0, 10)] private int _blurCount = 5;
        public int BlurCount => _blurCount;
        [SerializeField, Range(0, 10)] private float _blurDistance = 3;
        public float BlurDistance => _blurDistance;

        // 変更があったときだけ更新するためのキャッシュ
        private float _lastLuminance;
        private Color _lastBlurColor;
        private int _lastBlurCount;
        private float _lastBlurDistance;

        /// <summary>
        /// インスペクターのパラメータに変化があった場合trueを返す
        /// </summary>
        public bool CahngedParameter => !Mathf.Approximately(_transition, _lastLuminance) ||
                                        _blurColor != _lastBlurColor || _blurCount != _lastBlurCount ||
                                        !Mathf.Approximately(_blurDistance, _lastBlurDistance);

        /// <summary>
        /// キャッシュに最新のパラメーター値を反映させる
        /// </summary>
        public void UpdateParameter()
        {
            _lastLuminance = _transition;
            _lastBlurColor = _blurColor;
            _lastBlurCount = _blurCount;
            _lastBlurDistance = _blurDistance;
        }
    }

    [SerializeField] private Image _targetImage;
    [SerializeField] private Shader _targetShader;
    [SerializeField] private Parameters _parameters;

    private Texture2D _sourceTexture;
    private Material _createdMaterial;
    private RenderTexture _tmpRenderTexture1;
    private RenderTexture _tmpRenderTexture2;

    /// <summary>
    /// 初期化処理の必要性がある場合trueを返す
    /// </summary>
    private bool NeedsInitialization => _sourceTexture == null || _sourceTexture != _targetImage.sprite.texture ||
                                        _createdMaterial == null || _targetImage.material != _createdMaterial ||
                                        _tmpRenderTexture1 == null || _tmpRenderTexture2 == null;

    /// <summary>
    /// 初期化処理
    /// </summary>
    private void Init()
    {
        Release();

        _sourceTexture = _targetImage.sprite.texture;
        _createdMaterial = new (_targetShader);
        _createdMaterial.hideFlags = HideFlags.DontSave;
        _targetImage.material = _createdMaterial;

        //処理負荷軽減のため、大元の画像サイズから1/2のサイズでtmpRenderTexture1, 2を作成
        //tmpRenderTexture1, 2は両方ともぼかし処理のための画像なので、サイズが小さくても見た目に違和感は出ない
        _tmpRenderTexture1 = new RenderTexture(_sourceTexture.width / 2, _sourceTexture.height /2, 0);
        _tmpRenderTexture2 = new RenderTexture(_sourceTexture.width / 2, _sourceTexture.height / 2, 0);

        //事前にBlitで更新される予定のtmpRenderTexture2を、_CustomTexの名前でシェーダー側と紐づけておく
        //後はCanvasRender内の処理で自動的にPass0を実行させて、その_CustomTexを画像に反映させる
        _targetImage.material.SetTexture("_CustomTex", _tmpRenderTexture2);

        //初期化後は問答無用で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(_parameters.CahngedParameter)
        {
            UpdateTexture();
        }
    }

    /// <summary>
    /// 対象のシェーダーのPass1, 2を実行させる
    /// </summary>
    private void UpdateTexture()
    {
        _parameters.UpdateParameter();

        //輝度をセット
        _createdMaterial.SetFloat("_Transition", _parameters.Transition);
        //Blur設定をセット
        _createdMaterial.SetColor("_BlurColor", _parameters.BlurColor);
        _createdMaterial.SetInt("_BlurCount", _parameters.BlurCount);
        _createdMaterial.SetFloat("_BlurDistance", _parameters.BlurDistance);

        //第1引数の画像を元に、Blitでシェーダー内のPass1を実行させ、結果を第2引数に書き込む
        //Blitの仕様上、この書き込み処理は、第3引数のMaterial内の_MainTexを通して行われる
        Graphics.Blit(_sourceTexture, _tmpRenderTexture1, _createdMaterial, 1);
        //Pass1の結果を元にPass2を実行
        Graphics.Blit(_tmpRenderTexture1, _tmpRenderTexture2, _createdMaterial, 2);
    }

    /// <summary>
    /// 作成したRenderTextureやMaterialのメモリを解放する
    /// </summary>
    private void Release()
    {
        if (_tmpRenderTexture1 != null)
        {
            _tmpRenderTexture1.Release();
            _tmpRenderTexture2.Release();
        }
        if (_createdMaterial != null)
        {
            DestroyImmediate(_createdMaterial);
        }
    }

    /// <summary>
    /// このGameObjectを非表示にした時の処理
    /// </summary>
    private void OnDisable()
    {
        Release();
    }
}

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


これで実装は完了です。
0
0