LoginSignup
14
11

More than 3 years have passed since last update.

[Unity]uGUIをBloomを使って光らせ、EmissionColorも設定できるようにする

Last updated at Posted at 2021-05-30

※本記事は[Unity]uGUIをBloomを使わず光らせるのおまけ記事です

3Dモデルを光らせたい!
と思った時、Bloomをかけてエミッションマップ設定したり、イイカンジのシェーダーで便利にしたりという情報は調べるとすぐ出てきてとてもありがたいなと思います。

ただ、個人的にUIをBloomで光らせる方法についての情報は少ないな〜と思ったので簡単に整理しました。

筆者の環境

  • Unity 2020.2.4f1
  • Post Processing 3.0.3
  • MacOS 10.15.7

Bloomを設定し、Canvasにも影響が反映されるようにする

image.png

Package Manager で Post Processing をインストール。

image.png

UIを表示するカメラに Post-process Layer コンポーネントを付与。
Trigger にカメラの参照を与え、Layerにポストエフェクトの影響を与えるレイヤーを設定(今回はUIにでもしておきましょう)。

image.png
UIを表示するCanvasがカメラのポストエフェクト影響下に置かれるように、

  • Render Modeを「Screen Space - Camera」に設定
  • Render Cameraに先程のカメラの参照を設定

image.png

シーンに新しくGameObjectを作成し、Post-process Volumeコンポーネントを付与。

  • さきほどPost-process Layerでポストエフェクトの影響下に置いたレイヤー(UIレイヤー)をオブジェクトに設定
  • Profileの「New」ボタンを押して設定ファイルを追加
  • 「Add effect...」ボタンを押して「Unity > Bloom」を選択

これでBloomが画面に設定され、Canvasもポストエフェクトの影響下に置かれました。
試しにBloomのIntensity(輝度)を15にしてみます。

image.png

光りました。

個別にUIの発光具合、発光色を設定できるようにする

とはいえ画面全体でしか光り具合を調整できないのは困るので、UIにEmissionColor(発光色)を定義して、個別に光り具合を設定できるようにしてみます。

まず下記ページから、自身のUnityバージョン&OSのビルドインシェーダーをダウンロードします。

image.png

ダウンロードしたファイルを解凍し、UIの標準シェーダーであるUI-Default.shaderを探します。
これをコピーして、発光に関する処理を追加します。コピーしたファイル名はUI-Glow.shaderとでもしておきましょう。

スクリーンショット 2021-05-27 22.06.36.png

UI-Glow.shaderを編集

※記事の最後に全文を記載します

UI-Glow.shader
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

Shader "UI/Glow" // ←名前変更
{

シェーダー名を変更

UI-Glow.shader
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255

_ColorMask ("Color Mask", Float) = 15
[HDR] _EmissionColor ("Emission", Color) = (0, 0, 0, 0) // ← パラメータ追加

~~~中略~~~

float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
float3 _EmissionColor; // ← メンバ定義

発光色である_EmissionColorを定義

UI-Glow.shader
OUT.color = v.color * _Color;
OUT.color.rgb += _EmissionColor; // ← _EmissionColorを加算合成
return OUT;

フラグメントシェーダーの記述で、_EmissionColorを加算合成
これでシェーダー完成です。

シェーダーを反映

プロジェクト内に新たにマテリアルを作成。(ひとまず名前は「UI-Glow」としておきます)
image.png

UI-Glowマテリアルを選択し、Inspector上でShaderを「UI > Glow」とすると、シェーダーが設定されます。
(マテリアルファイルにシェーダーファイルをドラッグ&ドロップでも設定可能です)

image.png

このマテリアルをImageオブジェクトに設定します。

image.png

この状態で、UI-Glowの「Emission Color」を変化させると光具合が変化します!
※さっきデフォルトのシェーダーのUIを光らせるために極端に高くしてた、BloomのIntensityを下げておきましょう(1で十分なはず)

色も輝きも自由自在。

こんな感じで、模様として機能させることもできます!
これで完成です。

扱いやすくするコンポーネント作成(おまけ)

発光色はUIオブジェクトごとに設定したいので、マテリアルもその分必要になります。
その度マテリアル作成してたら大変なので、自動的に作成&割当してくれるコンポーネントを作りました。
また、いちいちマテリアルファイルを選択してEmissionColor設定するのも面倒なので、同一オブジェクトのInspector上でImageのカラーも発光色も操作できるようにしています。

ImageGlow.cs
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Image))]
public class ImageGlow : MonoBehaviour
{
    /// <summary> 発光色のシェーダーパラメータ </summary>
    private static readonly int PROPERTY_EMISSION_COLOR = Shader.PropertyToID("_EmissionColor");

    /// <summary> 発光色(HDR) </summary>
    [ColorUsage(false, true)]
    public Color EmissionColor;

    /// <summary> マテリアル </summary>
    private Material _mat;

    /// <summary> マテリアルアクセサ </summary>
    private Material Mat
    {
        get
        {
            if (_mat != null)
            {
                return _mat;
            }
            // マテリアルがない時に呼び出されたら初期化
            Initialize();
            return _mat;
        }
    }

    /// <summary>
    /// 初期化
    /// </summary>
    private void Initialize()
    {
        Image image = GetComponent<Image>();
        // UI/Glowシェーダーでマテリアル生成
        Shader uiGlowShader = Shader.Find("UI/Glow");
        if (image.material == null || image.material.shader != uiGlowShader)
        {
            _mat = new Material(uiGlowShader);
        }
        else
        {
            _mat = new Material(image.material);
        }
        _mat.name = "UI-Glow (Instance)";
        image.material = _mat;
    }

    /// <summary>
    /// 更新
    /// </summary>
    private void Update()
    {
        Mat.SetColor(PROPERTY_EMISSION_COLOR, EmissionColor);
    }

    /// <summary>
    /// Inspector上の値が変更されたときに呼び出し
    /// </summary>
    private void OnValidate()
    {
        Mat.SetColor(PROPERTY_EMISSION_COLOR, EmissionColor);
    }

    /// <summary>
    /// 削除時に呼び出し
    /// </summary>
    private void OnDestroy()
    {
        // 生成したマテリアルがリークしないように明示的に削除
        if(_mat != null)
        {
            if (Application.isPlaying)
            {
                Destroy(_mat);
            }
            else
            {
                DestroyImmediate(_mat);
            }
        }
    }
}

Shader.Findで今回作ったシェーダーを取得する処理が含まれています。
このメソッドでカスタムシェーダーを取得するために、Project SettingsGraphicsで、Always Included Shadersに、今回作った「UI/Glow」を追加してください。

image.png
その上で、このImageGlowコンポーエントをImageオブジェクトにアタッチすると、下記動画のように扱いが楽になります。お好みでお試しください

課題

① マテリアルがとにかく増える
発光するUIごとにマテリアルが必要になる関係上、光るものを増やすとどんどんマテリアル数が増えていきます。
これは単純にメモリを圧迫するということもあるんですが、バッチがいちいち切られてドローコール数が爆増する原因になりかねません。
ImageのColorみたいに1つのマテリアルで各オブジェクトのEmissinColorを変えられればマシになるんですが、どうすればいいんだっけ・・・(ImageのColorで出来てるならできるはずなんだけども)
誰か教えてください!

② Bloomはやっぱり重いよね
それ今言う?って感じなのですが。
実はもともとこの話がしたかったので課題というより本題でもあるんですが、UIにBloomを反映させるのってけっこう乱暴だと思ってて、現実的じゃないという認識です。
3Dモデルを映すカメラにはBloom以外にブラーなどのポストエフェクトをかけることもあるだろうし、そう考えると今回の例みたいに1つのカメラで済まなくなって、3DカメラとUIカメラのそれぞれでBloomをかけないといけないということになってしまいます。それは負荷的にキツい。

なので、ポストエフェクト無しでBloomみたいな効果をつけられないか?という話を、この記事の本体である下記の記事で書いています。
よろしければご覧ください!

今回はここまで!

UI-Glow.shader全文

UI-Glow.shader
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

Shader "UI/Glow"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [HDR] _EmissionColor ("Emission", Color) = (0, 0, 0, 0)

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend One OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile_local _ UNITY_UI_CLIP_RECT
            #pragma multi_compile_local _ UNITY_UI_ALPHACLIP

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                half4  mask : TEXCOORD2;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float4 _MainTex_ST;
            float _UIMaskSoftnessX;
            float _UIMaskSoftnessY;

            float3 _EmissionColor;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                float4 vPosition = UnityObjectToClipPos(v.vertex);
                OUT.worldPosition = v.vertex;
                OUT.vertex = vPosition;

                float2 pixelSize = vPosition.w;
                pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));

                float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
                float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
                OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
                OUT.mask = half4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));

                OUT.color = v.color * _Color;
                OUT.color.rgb += _EmissionColor;
                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = IN.color * (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);

                #ifdef UNITY_UI_CLIP_RECT
                half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
                color.a *= m.x * m.y;
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif

                color.rgb *= color.a;

                return color;
            }
        ENDCG
        }
    }
}
14
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
11