※本記事は**「[Unity]uGUIをBloomを使わず光らせる」**のおまけ記事です
3Dモデルを光らせたい!
と思った時、Bloomをかけてエミッションマップ設定したり、イイカンジのシェーダーで便利にしたりという情報は調べるとすぐ出てきてとてもありがたいなと思います。
ただ、個人的にUIをBloomで光らせる方法についての情報は少ないな〜と思ったので簡単に整理しました。
筆者の環境
- Unity 2020.2.4f1
- Post Processing 3.0.3
- MacOS 10.15.7
Bloomを設定し、Canvasにも影響が反映されるようにする
Package Manager で Post Processing
をインストール。
UIを表示するカメラに Post-process Layer
コンポーネントを付与。
Trigger
にカメラの参照を与え、Layer
にポストエフェクトの影響を与えるレイヤーを設定(今回はUIにでもしておきましょう)。
UIを表示するCanvasがカメラのポストエフェクト影響下に置かれるように、
-
Render Mode
を「Screen Space - Camera」に設定 -
Render Camera
に先程のカメラの参照を設定
シーンに新しくGameObjectを作成し、Post-process Volume
コンポーネントを付与。
- さきほど
Post-process Layer
でポストエフェクトの影響下に置いたレイヤー(UIレイヤー)をオブジェクトに設定 -
Profile
の「New」ボタンを押して設定ファイルを追加 - 「Add effect...」ボタンを押して「Unity > Bloom」を選択
これでBloomが画面に設定され、Canvasもポストエフェクトの影響下に置かれました。
試しにBloomのIntensity
(輝度)を15
にしてみます。
光りました。
個別にUIの発光具合、発光色を設定できるようにする
とはいえ画面全体でしか光り具合を調整できないのは困るので、UIにEmissionColor
(発光色)を定義して、個別に光り具合を設定できるようにしてみます。
まず下記ページから、自身のUnityバージョン&OSのビルドインシェーダーをダウンロードします。
ダウンロードしたファイルを解凍し、UIの標準シェーダーであるUI-Default.shader
を探します。
これをコピーして、発光に関する処理を追加します。コピーしたファイル名はUI-Glow.shader
とでもしておきましょう。
UI-Glow.shaderを編集
※記事の最後に全文を記載します
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
Shader "UI/Glow" // ←名前変更
{
シェーダー名を変更
_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
を定義
OUT.color = v.color * _Color;
OUT.color.rgb += _EmissionColor; // ← _EmissionColorを加算合成
return OUT;
フラグメントシェーダーの記述で、_EmissionColorを加算合成
これでシェーダー完成です。
シェーダーを反映
プロジェクト内に新たにマテリアルを作成。(ひとまず名前は「UI-Glow」としておきます)
UI-Glowマテリアルを選択し、Inspector上でShaderを「UI > Glow」とすると、シェーダーが設定されます。
(マテリアルファイルにシェーダーファイルをドラッグ&ドロップでも設定可能です)
このマテリアルをImageオブジェクトに設定します。
この状態で、UI-Glowの「Emission Color」を変化させると光具合が変化します!
※さっきデフォルトのシェーダーのUIを光らせるために極端に高くしてた、BloomのIntensityを下げておきましょう(1
で十分なはず)
色も輝きも自由自在。
こんな感じで、模様として機能させることもできます!
これで完成です。
扱いやすくするコンポーネント作成(おまけ)
発光色はUIオブジェクトごとに設定したいので、マテリアルもその分必要になります。
その度マテリアル作成してたら大変なので、自動的に作成&割当してくれるコンポーネントを作りました。
また、いちいちマテリアルファイルを選択してEmissionColor設定するのも面倒なので、同一オブジェクトのInspector上でImageのカラーも発光色も操作できるようにしています。
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 Settings
のGraphics
で、Always Included Shaders
に、今回作った「UI/Glow」を追加してください。
その上で、このImageGlow
コンポーエントをImageオブジェクトにアタッチすると、下記動画のように扱いが楽になります。お好みでお試しください
課題
① マテリアルがとにかく増える
発光するUIごとにマテリアルが必要になる関係上、光るものを増やすとどんどんマテリアル数が増えていきます。
これは単純にメモリを圧迫するということもあるんですが、バッチがいちいち切られてドローコール数が爆増する原因になりかねません。
ImageのColorみたいに1つのマテリアルで各オブジェクトのEmissinColorを変えられればマシになるんですが、どうすればいいんだっけ・・・(ImageのColorで出来てるならできるはずなんだけども)
誰か教えてください!
② Bloomはやっぱり重いよね
それ今言う?って感じなのですが。
実はもともとこの話がしたかったので課題というより本題でもあるんですが、UIにBloomを反映させるのってけっこう乱暴だと思ってて、現実的じゃないという認識です。
3Dモデルを映すカメラにはBloom以外にブラーなどのポストエフェクトをかけることもあるだろうし、そう考えると今回の例みたいに1つのカメラで済まなくなって、3DカメラとUIカメラのそれぞれでBloomをかけないといけないということになってしまいます。それは負荷的にキツい。
なので、ポストエフェクト無しでBloomみたいな効果をつけられないか?という話を、この記事の本体である下記の記事で書いています。
よろしければご覧ください!
今回はここまで!
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
}
}
}