【Unity2D】キャラクターにオーラを纏わせるカスタムシェーダー

Unity

Unityで2Dゲーム開発をしている者です。

キャラクターを描画していると、「もっと存在感を出したい」と思う瞬間があります。

そこで今回は、透過されたキャラクターの周囲にグローエフェクトを加え、オーラを纏わせるカスタムシェーダーを作ってみました。

オーラとは下記のようなキャラクターの周りに発生する光になります。

見た目の印象を簡単に引き上げられる演出なので、コードと合わせて紹介していきます。

コードを紹介

以下が今回作成したカスタムシェーダー「AuraGlow」です。
Unity 2Dのスプライトに適用することで、透過されたキャラクター画像の周囲にオーラを表示できます。
コードはそのままコピペして使えますが、Shader名やパラメータは必要に応じて変更してください。

Shader "Custom/AuraGlow"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _GlowColor ("Glow Color", Color) = (0, 0.5, 1, 1)
        _GlowIntensity ("Glow Intensity", Range(0, 10)) = 2
        _GlowSize ("Glow Size (pixels)", Range(1, 50)) = 10
        _PulseSpeed ("Pulse Speed", Range(0, 10)) = 2
        _PulseAmount ("Pulse Amount", Range(0, 1)) = 0.3
    }
    
    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        LOD 100
        
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite Off
        
        // グローエフェクトをレンダリング
        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;
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;
            float4 _GlowColor;
            float _GlowIntensity;
            float _GlowSize;
            float _PulseSpeed;
            float _PulseAmount;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            
            // グロー計算
            float GetGlowAlpha(float2 uv)
            {
                float alpha = 0;
                float totalWeight = 0;
                
                // サンプリング回数を5x5=25回に固定
                const int sampleRadius = 2; // (5-1)/2 = 2

                // _GlowSizeに応じてサンプリングする間隔を計算
                float2 sampleStep = _GlowSize / sampleRadius * _MainTex_TexelSize.xy;

                [unroll]
                for (int x = -sampleRadius; x <= sampleRadius; x++)
                {
                    [unroll]
                    for (int y = -sampleRadius; y <= sampleRadius; y++)
                    {
                        float2 offset = float2(x, y) * sampleStep;
                        
                        // 重み計算
                        float dist = length(float2(x, y));
                        float weight = exp(-(dist * dist) / (sampleRadius * sampleRadius));
                        
                        // テクスチャをサンプリング
                        float4 sampleColor = tex2D(_MainTex, uv + offset);
                        alpha += sampleColor.a * weight;
                        totalWeight += weight;
                    }
                }
                
                if (totalWeight > 0)
                {
                    return alpha / totalWeight;
                }
                return 0;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // 元のテクスチャサンプリング
                float4 originalColor = tex2D(_MainTex, i.uv);
                
                // グローのアルファ値を計算
                float glowAlpha = GetGlowAlpha(i.uv);
                
                // 元のアルファを引いて、アウトライン部分のみを取得
                float outlineAlpha = max(0, glowAlpha - originalColor.a * 0.9);
                
                // パルスアニメーション
                float pulse = 1 + sin(_Time.y * _PulseSpeed) * _PulseAmount;
                
                // グローカラーを設定
                float4 glowColor = _GlowColor;
                glowColor.rgb *= _GlowIntensity * pulse;
                glowColor.a = outlineAlpha;
                
                // 元の画像とグローを合成
                float4 finalColor = originalColor;
                finalColor.rgb = lerp(glowColor.rgb, originalColor.rgb, originalColor.a);
                finalColor.a = max(originalColor.a, outlineAlpha * glowColor.a);
                
                return finalColor;
            }
            ENDCG
        }
    }
    
    FallBack "Sprites/Default"
}

コード冒頭のプロパティについて、役割を簡潔に説明します。

_MainTex … キャラクター画像のテクスチャ。透過PNGなどを想定。

_GlowColor … オーラの色。RGBAで指定可能。

_GlowIntensity … 発光の強さ。数値が大きいほど光が強くなる。

_GlowSize … オーラの広がり(ピクセル単位)。

_PulseSpeed … 光の脈動(パルス)の速さ。

_PulseAmount … 脈動の強さ。0にすると常時一定光量になる。

使用例

_GlowColorを紫に変更する。

_GlowIntensityを弱くする

_GlowSizeを増やす

こんな感じでパラメータを調整しながらオーラの表現を変更できます。

使い方

まず上記で紹介したシェーダーを自身のプロジェクトに配置します。

Unityが自動的にコンパイルし、インスペクターから参照できるようになります。

(例:Assets\Resources\Materials\AuraGlow.shader)

次に、このシェーダーを使うためのマテリアルを作ります。

プロジェクトウィンドウで右クリック → Create → Material を選択。

名前をわかりやすく「AuraGlow.mat」などに変更。

インスペクターのShader欄から、Custom/AuraGlowを選択。

この時点でマテリアルのInspectorに、Glow Color / Glow Intensity / Glow Sizeなどのパラメータが表示されます。

最後に、このマテリアルを透過PNGのキャラクター画像に適用します。
ImageコンポーネントのMaterial欄で先ほど作成したマテリアルを選択します。

これで、透過されたキャラクター画像の周囲に光のオーラが表示されます。
色や光の強さ、広がり、パルス速度などはマテリアルのInspectorからリアルタイムに調整できます。

スクリプトからパラメータ変更

シェーダーを使ったオーラ演出は、マテリアルのパラメータを変えるだけで色や強さを簡単に調整できます。
ですが、UnityのInspector上で手動変更する方法だと、キャラクターごとに異なるオーラ設定を作るのは少し面倒です。
そこで、スクリプトから直接パラメータを変更できる仕組みを用意しておくと、イベントや状況に応じてリアルタイムに演出を切り替えられて便利です。
私のゲームで実際に使っているパラメータ変更用のクラスを紹介します。

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

// ImageにAuraGlowマテリアルを適用して各種エフェクトを制御するクラス
public class AuraGlowEffect : MonoBehaviour
{
    // 対象のImage
    public Image targetImage;
    // AuraGlowマテリアルのテンプレート
    public Material auraGlowMaterialTemplate;
    private Material auraGlowMaterialInstance;

    private bool isInitialized = false;

    // シェーダープロパティID(パフォーマンス向上のため)
    static readonly int ID_GlowColor = Shader.PropertyToID("_GlowColor");
    static readonly int ID_GlowIntensity = Shader.PropertyToID("_GlowIntensity");
    static readonly int ID_GlowSize = Shader.PropertyToID("_GlowSize");
    static readonly int ID_PulseSpeed = Shader.PropertyToID("_PulseSpeed");
    static readonly int ID_PulseAmount = Shader.PropertyToID("_PulseAmount");

    void Start()
    {
        // インスペクター経由で設定された場合のために、Startでも初期化を試みる
        Initialize();
    }

    // 初期化処理
    private void Initialize()
    {
        // 既に初期化済みか、テンプレートがなければ何もしない
        if (isInitialized || auraGlowMaterialTemplate == null)
        {
            return;
        }

        // マテリアルのインスタンスを作成(他UIと共有しない)
        auraGlowMaterialInstance = new Material(auraGlowMaterialTemplate);
        // Imageにインスタンス化したマテリアルを適用
        targetImage.material = auraGlowMaterialInstance;

        isInitialized = true;
    }

    void OnDestroy()
    {
        if (auraGlowMaterialInstance != null)
        {
            Destroy(auraGlowMaterialInstance);
        }
    }

    // 手動初期化メソッド(外部から初期化したい場合)
    public void SetupAuraGlow(Image image, Material template = null)
    {
        targetImage = image;
        if (template != null)
        {
            auraGlowMaterialTemplate = template;
        }
        else if (auraGlowMaterialTemplate == null)
        {
            // デフォルトのAuraGlowマテリアルを読み込む
            auraGlowMaterialTemplate = Resources.Load<Material>("Materials/AuraGlow");
        }
        Initialize();
    }

    // グローの色を設定
    public void SetGlowColor(Color color)
    {
        Initialize();
        if (!isInitialized) return;
        auraGlowMaterialInstance.SetColor(ID_GlowColor, color);
    }

    // グローの強度を設定(0~10)
    public void SetGlowIntensity(float intensity)
    {
        Initialize();
        if (!isInitialized) return;
        auraGlowMaterialInstance.SetFloat(ID_GlowIntensity, Mathf.Clamp(intensity, 0f, 10f));
    }

    // グローのサイズを設定(1~50ピクセル)
    public void SetGlowSize(float size)
    {
        Initialize();
        if (!isInitialized) return;
        auraGlowMaterialInstance.SetFloat(ID_GlowSize, Mathf.Clamp(size, 1f, 50f));
    }

    // パルスの速度を設定(0~10)
    public void SetPulseSpeed(float speed)
    {
        Initialize();
        if (!isInitialized) return;
        auraGlowMaterialInstance.SetFloat(ID_PulseSpeed, Mathf.Clamp(speed, 0f, 10f));
    }

    // パルスの量を設定(0~1)
    public void SetPulseAmount(float amount)
    {
        Initialize();
        if (!isInitialized) return;
        auraGlowMaterialInstance.SetFloat(ID_PulseAmount, Mathf.Clamp01(amount));
    }

    // すべてのパラメータを一度に設定
    public void SetAuraGlowParameters(Color glowColor, float intensity, float size, float pulseSpeed, float pulseAmount)
    {
        Initialize();
        if (!isInitialized) return;

        auraGlowMaterialInstance.SetColor(ID_GlowColor, glowColor);
        auraGlowMaterialInstance.SetFloat(ID_GlowIntensity, Mathf.Clamp(intensity, 0f, 10f));
        auraGlowMaterialInstance.SetFloat(ID_GlowSize, Mathf.Clamp(size, 1f, 50f));
        auraGlowMaterialInstance.SetFloat(ID_PulseSpeed, Mathf.Clamp(pulseSpeed, 0f, 10f));
        auraGlowMaterialInstance.SetFloat(ID_PulseAmount, Mathf.Clamp01(pulseAmount));
    }

    // フェードイン効果(グローの強度を徐々に上げる)
    public void RunGlowFadeIn(float duration = 1f, float targetIntensity = 2f)
    {
        Initialize();
        if (!isInitialized) return;
        StartCoroutine(GlowFadeIn(duration, targetIntensity));
    }

    private IEnumerator GlowFadeIn(float duration, float targetIntensity)
    {
        float startIntensity = 0f;
        auraGlowMaterialInstance.SetFloat(ID_GlowIntensity, startIntensity);

        float t = 0f;
        while (t < duration)
        {
            float progress = t / duration;
            float currentIntensity = Mathf.Lerp(startIntensity, targetIntensity, progress);
            auraGlowMaterialInstance.SetFloat(ID_GlowIntensity, currentIntensity);
            t += Time.deltaTime;
            yield return null;
        }
        auraGlowMaterialInstance.SetFloat(ID_GlowIntensity, targetIntensity);
    }

    // フェードアウト効果(グローの強度を徐々に下げる)
    public void RunGlowFadeOut(float duration = 1f)
    {
        Initialize();
        if (!isInitialized) return;
        StartCoroutine(GlowFadeOut(duration));
    }

    private IEnumerator GlowFadeOut(float duration)
    {
        float startIntensity = auraGlowMaterialInstance.GetFloat(ID_GlowIntensity);

        float t = 0f;
        while (t < duration)
        {
            float progress = t / duration;
            float currentIntensity = Mathf.Lerp(startIntensity, 0f, progress);
            auraGlowMaterialInstance.SetFloat(ID_GlowIntensity, currentIntensity);
            t += Time.deltaTime;
            yield return null;
        }
        auraGlowMaterialInstance.SetFloat(ID_GlowIntensity, 0f);
    }

    // 色の変化アニメーション
    public void RunColorTransition(Color targetColor, float duration = 1f)
    {
        Initialize();
        if (!isInitialized) return;
        StartCoroutine(ColorTransition(targetColor, duration));
    }

    private IEnumerator ColorTransition(Color targetColor, float duration)
    {
        Color startColor = auraGlowMaterialInstance.GetColor(ID_GlowColor);

        float t = 0f;
        while (t < duration)
        {
            float progress = t / duration;
            Color currentColor = Color.Lerp(startColor, targetColor, progress);
            auraGlowMaterialInstance.SetColor(ID_GlowColor, currentColor);
            t += Time.deltaTime;
            yield return null;
        }
        auraGlowMaterialInstance.SetColor(ID_GlowColor, targetColor);
    }

    // エフェクトをリセット
    public void ResetAuraGlow()
    {
        if (!isInitialized) return;

        // デフォルト値に戻す
        auraGlowMaterialInstance.SetColor(ID_GlowColor, new Color(0f, 0.5f, 1f, 1f));
        auraGlowMaterialInstance.SetFloat(ID_GlowIntensity, 2f);
        auraGlowMaterialInstance.SetFloat(ID_GlowSize, 10f);
        auraGlowMaterialInstance.SetFloat(ID_PulseSpeed, 2f);
        auraGlowMaterialInstance.SetFloat(ID_PulseAmount, 0.3f);
    }
}

コード中の以下の部分が、ResourcesフォルダからAuraGlowマテリアルを読み込む処理です。

auraGlowMaterialTemplate = Resources.Load("Materials/AuraGlow");

Assets/Resources/Materials/AuraGlow.matを指します。
もし異なる場所に置く場合は、この文字列パスを変更してください。
Inspectorで直接割り当ててもOKです。
auraGlowMaterialTemplate に直接マテリアルを設定すれば、Resources.Loadは使われません。

スクリプトからの呼び出し例

auraGlowEffect.SetGlowColor(Color.red); // 色を赤に変更
auraGlowEffect.SetGlowIntensity(3f); // 光の強さを変更
auraGlowEffect.RunGlowFadeIn(1f, 4f); // 1秒かけて強度4までフェードイン
auraGlowEffect.RunColorTransition(Color.blue, 2f); // 2秒かけて青に変化

コメント

タイトルとURLをコピーしました