Unity 6 の UI Toolkit でリッチな UI エフェクトを実装する

Unity 6 UI Toolkit エフェクトサンプル集

目次

はじめに

Unity 6.3 で UI Toolkit にグラフィックス系のアップデートが入りました。 ShaderGraphVisualElement に適用したり、Filter 機能で描画結果全体にエフェクトをかけたりできます。 この新機能を活かして UI Toolkit でどこまでリッチな表現を組めるか、ShaderGraph と Filter の 2 経路で体系化しました。

UI Toolkit + UIEffect の総合デモ

検証環境

  • Unity Editor: 6000.4.6f1
  • Render Pipeline: URP 17.4.0
  • Shader Graph: 17.4.0

UI Toolkit における UIEffect の選択肢

UI Toolkit のエフェクト表現は2種類の方法が考えられます。
以下の表にまとめました。

経路 適用方法 強み 弱み
ShaderGraph (Material) USS の Material で Material を差し替え ノードベースで色変換系を直感的に組める / 描画前の Color/Alpha を直接いじれる 単一描画 (1 つの VisualElement) にしか掛からない
USS Filter USS の filter プロパティで指定。Custom Filter も組める 親要素にかけると配下がまとめてエフェクト処理される / Custom Filter で任意の shader 効果を組める Custom Filter は shader を自作する手間がかかる

使い分けの軸は、エフェクトを掛けたい対象が 1 つの VisualElement か、複数の VisualElement かという点です。 単一なら ShaderGraph、複数をまとめて掛けたいなら Filter、という整理で使用しています。 同じ見た目のエフェクトでも、用途に応じて両方の経路で実装することがあります。

ShaderGraph 経路

まず簡単にですが、UI Toolkit で ShaderGraph を使用する設定手順を説明していきます。

Project以下で右クリックをしてCreate/ShaderGraph/URP/UI Shader Graphを選択して生成します。

UI Shader Graph の生成メニュー

ShaderGraph実装

ここからは今回表現したShaderGraphのエフェクトについて詳しく見ていきます。 今回のShaderGraphでは下記の画像を元画像(MainTex)として使用しています。

ShaderGraph に渡している加工前の元画像(MainTex)

各エフェクトのShaderGraphの中身は以下の通りです。

Tone: Grayscale (彩度を 0 にして白黒化)

Grayscale ShaderGraph と適用結果

Tone: Sepia (Sepia 行列で茶色調)

Sepia ShaderGraph と適用結果

Tone: Negative (1 - color で反転)

Negative ShaderGraph と適用結果

ColorMode: Multiply (TintColor を乗算合成)

Multiply ShaderGraph と適用結果

ColorMode: Add (AdditiveColor を加算合成)

Add ShaderGraph と適用結果

装飾: Shiny (光のラインが走る)

Shiny ShaderGraph と適用結果

装飾: Gradient (UV ベースのグラデーション)

Gradient ShaderGraph と適用結果

統合 ShaderGraph (SubGraph + Keyword でまとめる)

各エフェクトは SubGraph として独立して作成しました。SubGraph 化することで、エフェクトを 1 つの親 ShaderGraph に集約し、Boolean Keyword と Branch ノードで動的に切り替えられる構成にしています。

7 つの SubGraph を直列に並べ、Grayscale → Sepia → Negative → Multiply → Add → Shiny → Gradient こちらの順序で描画処理していきます。

各段の出力を Boolean Keyword Branch ノードで「Keyword ON ならエフェクト適用後、Keyword OFF なら前段の値」と切り替え、最終結果を Fragment.BaseColor に渡します。

_TONE_GRAYSCALE_ON_SHINY_ON などの Keyword を Material 側でトグルするだけで、任意のエフェクトを任意の組み合わせで適用できます。

統合 ShaderGraph (UIEffectsKeyword) の全体像

UI Toolkit で ShaderGraph の適用

作成したShaderGraphは、UIBuilder上で対象のUXMLを開き各要素ごとに設定していくことでMaterial(ShaderGraph)の設定ができます。

UI Builder で VisualElement に Material を割り当てる画面

Filter 経路

Filter機能についても設定方法から説明していきます。 Filterの設定方法は、対象UXMLをUI Builder上で開き、対象要素のInlined Styles/Filterから対象のエフェクトを選択するだけです。

UI Builder の Inlined Styles から Filter を選択する画面

Filter機能にはデフォルトですでにいくつかの機能があります(USS Filter 公式ドキュメント)。今回は Custom Filter の実装について紹介していきます。

Custom Filter実装

まず、シェーダー(.shader)を書きます。 全てエフェクトを紹介してしまうと長くなってしまうので、今回は色収差について紹介していきます。

Shader "UI/Filters/ChromaticAberration"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Strength ("Strength (UV)", Range(0.0, 0.1)) = 0.025
        _Angle ("Angle (deg)", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "RenderType"="Transparent"
            "Queue"="Transparent"
            "RenderPipeline"="UniversalPipeline"
        }

        Cull Off
        ZWrite Off
        ZTest Always
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _ UIE_OUTPUT_LINEAR

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct appdata
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 positionCS : SV_POSITION;
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            float4 _MainTex_TexelSize;

            // _Strength: 0=ズレなし、大きいほど色チャネル分離が強い (UV 単位)
            // _Angle  : 色ズレの方向 (degrees)。R/B を逆方向にずらす軸を制御する
            float _Strength;
            float _Angle;

            v2f vert(appdata v)
            {
                v2f o;
                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
                o.uv = v.uv;
                return o;
            }

            half4 frag(v2f i) : SV_Target
            {
                float angleRad = _Angle * 0.01745329; // deg → rad
                float2 axis = float2(cos(angleRad), sin(angleRad));
                float2 offset = axis * _Strength;

                // R は +方向、B は -方向、G は中心。古典的な色ズレ手法。
                float2 uvR = i.uv + offset;
                float2 uvG = i.uv;
                float2 uvB = i.uv - offset;

                // 範囲外サンプリングを抑えるため境界クランプ
                uvR = clamp(uvR, _MainTex_TexelSize.xy * 0.5, 1.0 - _MainTex_TexelSize.xy * 0.5);
                uvG = clamp(uvG, _MainTex_TexelSize.xy * 0.5, 1.0 - _MainTex_TexelSize.xy * 0.5);
                uvB = clamp(uvB, _MainTex_TexelSize.xy * 0.5, 1.0 - _MainTex_TexelSize.xy * 0.5);

                half r = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uvR).r;
                half4 g4 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uvG);
                half b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uvB).b;

                // UIToolkit用 Gammaに変換処理
                #ifdef UIE_OUTPUT_LINEAR
                    col.rgb = GammaToLinearSpace(col.rgb);
                #endif

                half4 col = half4(r, g4.g, b, g4.a);
                return col;
            }
            ENDHLSL
        }
    }
}

.shaderができましたら次に、FilterFunctionDefinition アセットを作成します。

こちらも作成方法は簡単で、Project以下で右クリックし、CreateUI ToolkitFilter Function Definition を選択します。

FilterFunctionDefinition アセットの生成メニュー

生成されたFilterFunctionDefinitionアセットに今回は「ChromaticAberrationFilter」と名前をつけて、下記画像の通りInspector上で設定します。

ChromaticAberrationFilter の Inspector 設定

設定のポイントは以下の3つです。

  • Filter Name: USS から参照する際の識別子(chromaticAberration など)
  • Parameters: USS で引数として渡せるパラメータの順序(strength, angle)とデフォルト値
  • Passes: 実際に走らせる Material(先ほど作成した shader を割り当てた .mat)と、shader プロパティへのバインド(_Strength ← parameter[0]、_Angle ← parameter[1])

最後にUIBuilder上からVisualElementに追加していきます。 Inlined Styles/Filter内の「+」ボタンを押して、Customを選択します。 その後、Definitionの参照を上で作ったchromaticAberrationのDefinitionに設定し、strengthangleに値を入れます。

UI Builder で Custom Filter を VisualElement に追加する画面

これでCustomFilterの作成と適用は完了です。

応用 1:RenderTexture で背景エフェクト UI

ゲーム開発をしているとポーズ画面やダイアログなどを表示する際に背景に対してブラーをかけて表示したいということがあると思います。 そのような演出を UI Toolkit で行う場合どのように実装していくのか紹介していきます。

RenderTexture を経由した背景エフェクトのデモ

実装の選択肢

UI 側に RenderTexture を表示するには、いくつかの実装方法があります。今回は構成がシンプルになるよう、事前に RenderTexture を用意して専用カメラから書き込む実装にしました。

専用カメラから RenderTexture へ書き出す

BackgroundCamera という名前で 3D シーン撮影用の Camera を1つ追加します。Inspector の Output Texture(= targetTexture)には、事前に用意した RenderTexture アセットを割り当てます。

RenderTexture は次のような構成にしています。

  • Size: UI 表示サイズに合わせる(本記事のサンプルでは 512×288)
  • Depth Stencil Format: D32_SFloat_S8_UInt(Stencil を使う Filter にも対応できるよう 8bit Stencil 付き)
  • Color Format: R8G8B8A8_UNorm

BackgroundCamera の Inspector で Output Texture に RenderTexture を割り当てる

VisualElement に RenderTexture を流し込む

VisualElement に背景画像として RenderTexture を流すには、style.backgroundImageBackground.FromRenderTexture(rt) を渡します。USS では RenderTexture を直接 background-image に指定できないので、C# 側で実行時にバインドします。

using UnityEngine;
using UnityEngine.UIElements;

public class RenderTextureUIBinder : MonoBehaviour
{
    [SerializeField] UIDocument uiDocument;
    [SerializeField] RenderTexture renderTexture;
    [SerializeField] string[] targetElementNames;

    // rootVisualElement は Awake / OnEnable では未生成のことがあるため、Start と OnEnable 両方から BindAll を呼ぶ。
    void Start() => BindAll();
    void OnEnable() => BindAll();

    void BindAll()
    {
        // rootVisualElement が未生成のフレームでは何もせず、後続の Start / OnEnable 呼び出しで再試行する。
        if (uiDocument == null || uiDocument.rootVisualElement == null || renderTexture == null) return;
        if (targetElementNames == null) return;

        // RenderTexture は USS から直接指定できないため、Background.FromRenderTexture + StyleBackground で C# から流し込む。
        var background = new StyleBackground(Background.FromRenderTexture(renderTexture));
        foreach (var name in targetElementNames)
        {
            // UI Toolkit の VisualElement ツリーは UQuery(.Q<T>(name))で目的の要素を取得する。
            var element = uiDocument.rootVisualElement.Q<VisualElement>(name);
            if (element != null)
            {
                element.style.backgroundImage = background;
            }
        }
    }
}

Inspectorの設定内容は以下の通りです。

RenderTextureUIBinder の Inspector 設定

ここまで設定したら、その VisualElement に対して USS の Material で ShaderGraph の Material を割り当てたり、filter: filter(url(...)) で Custom Filter を適用したりできます。RenderTexture の中身にエフェクトが掛かった状態で表示可能です。

「3D シーン → RenderTexture」へ書き出した時点で、それ以降は通常の UI 画像と同じ扱いになるので、ここまでに紹介した ShaderGraph 系・Filter 系のエフェクトがそのまま使えるようになります。

ポーズ画面で「背景の 3D だけブラー、UI は素」のように見せたい場合は、UI を写さない専用カメラから RT に書き出し、filter: blur(8px) を適用した VisualElement の背景として表示するだけで実現できます。

応用 2:親要素 Filter で Text + Image をまとめて PostEffect

ゲーム開発をしていると動的に変更されるTextとTextに合わせた背景Imageに対してエフェクトを掛けたいというケースもよくあると思います。 UI Toolkit でこの表現を実装する方法を紹介します。

親要素 Filter で Text + Image をまとめて PostEffect するデモ

UXML 構造

各UIをまとめて表示構成する UXML は次のような階層になります。親のVisualElementに対して filter を指定することで、配下の Label(Score 等)と VisualElement(アイコン画像)と背景がまとめて 1 パスで PostEffect されます。

parent                ← この要素に filter: filter(url("ChromaticAberrationFilter.asset")) を指定
├── title             (Label: "ChromaticAberration")
├── icon              (VisualElement: 背景画像)
├── score-label       (Label: "Score: 12,345" 動的更新)
└── caption           (Label: "HIGH SCORE")

UI Builder 上の設定はこのようになります。

UI Builder で親 VisualElement に Filter を適用する設定

これで parent を含む配下のラスタライズ結果が 1 枚のテンポラリ RT に書き込まれ、それに対して ChromaticAberrationFilter が 1 回だけ走るという挙動になります。

uGUI 時代の UI Effect との比較

uGUI 時代にリッチな UI エフェクトを実装する場合、BaseMeshEffect や独自シェーダーを各要素に貼り付ける方式が一般的でした。 今回 UI Toolkit で実装してきた内容を、この旧来手法と並べて整理してみます。

比較項目 uGUI 時代 (旧来) UI Toolkit (今回)
エフェクトの記述場所 コンポーネント (C#) に集約 ShaderGraph・USS Filter・USS に分散できる
適用単位 Image / Text 個別に貼り付け 親 VisualElement にまとめて指定可能
多重がけ コンポーネントを多段に重ねる filter: blur(4px) filter(url(...)) で USS だけでエフェクトを重ねられる
デザイナーとの分業 C# 改修が必要なことが多い 見た目の調整を USS 側に寄せやすい
メッシュ加工系の自由度 頂点単位で細かく操作できる 現状は ShaderGraph / Filter の範囲が中心

たとえば「動的なテキストと背景画像にブラーを掛けたい」「ダイアログ全体に色収差を掛けたい」といった要件は、uGUI 時代には各要素を CanvasGroup で囲んだり、RenderTexture 経由で組んだりと、それなりの作業量が必要でした。UI Toolkit の Filter なら親要素に設定するだけで配下をまとめて処理してくれるため、こうしたケースの記述量を大幅に抑えられます。 このように、描画前の色変換や全体への PostEffect といった用途では、UI Toolkit のほうが宣言的に組めるようになりました。一方、頂点を直接いじるような細かいメッシュ加工は、まだ uGUI 側に分があるケースもあります。

おわりに

今回の追加により、よりリッチな表現が UI Toolkit で実装できるようになりました。 これによってランタイムで UI Toolkit が導入されるプロジェクトも増えてくると考えています。 とはいえ、まだ行えないことも多いと思いますので、今後のアップデートにも期待が高まります。

関連リンク


財津 大睦

株式会社サムザップ グラフィックスエンジニア
開発推進室のグラフィックスを担当