0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【小ネタ】UI描画時のシェーダ(UI/Default)を差し替えるためのコンポーネント

0
Last updated at Posted at 2026-04-12

image.png

UIのImageや、RawImage(というかUnityEngine.UI.Graphic)を描画する際に自動的に設定される「UI/Default」シェーダですが、これをまとめて差し替えるにはどうするべきかな?
と調べていて、解決法が分かったので記載しておきます

前提

今回の記事は

  • Unity6000.4.2f1
  • Windows

の環境で記載しています

シェーダを用意

先ずはUnityのUI/Defaultシェーダを自分用にカスタマイズしましょう
Unity公式サイトのDownloadアーカイブから、自分が使用しているUnityバージョンに応じたシェーダを取得します

  • 「全てのバージョン」選択 → 対応するバージョンの「ダウンロード 全ての表示」を選択 → 「Other installs」 の Shaders を選択
  • DLしたzipを解凍(2段階で解凍しなきゃいけないのでちと面倒) → DefaultResourcesExtra/UI フォルダ内にある「UI-Default.shader」を引っ張ってきてリネームしましょう

今回は仮に「UI-Custom」にリネームしたとして、下記の様に記載してみます

UI-Custom.shader
Shader "ScreenPocket/UI/Custom"
{
    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

        [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;
                float4  mask : TEXCOORD2;
                UNITY_VERTEX_OUTPUT_STEREO
            };

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

            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 = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));


                if (_UIVertexColorAlwaysGammaSpace)
                {
                    if(!IsGammaSpace())
                    {
                        v.color.rgb = UIGammaToLinear(v.color.rgb);
                    }
                }

                OUT.color = v.color * _Color;
                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                //Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
                //The incoming alpha could have numerical instability, which makes it very sensible to
                //HDR color transparency blend, when it blends with the world's texture.
                const half alphaPrecision = half(0xff);
                const half invAlphaPrecision = half(1.0/alphaPrecision);
                IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision;

                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;
                
                //この1行を追加 真っ赤にする
                color.rgb = fixed3(1,0,0);

                return color;
            }
        ENDCG
        }
    }
}

ほぼ丸々コピーで、最後の1行 color.rgb = fixed3(1,0,0); だけ足した感じです。
わかりやすいように赤で塗りつぶすだけ。

下記のコンポーネントを使って、UI-Defaultシェーダをこれに置き換えてみましょう

置き換え用コンポーネント

という事で本題のコンポーネントのコードです

UIDefaultShaderChanger.cs
using UnityEngine;

namespace ScreenPocket.UI.Runtime
{
    /// <summary>
    /// UI-Defaultシェーダを置き換える
    /// </summary>
    [DisallowMultipleComponent]
    public sealed class UIDefaultShaderChanger : MonoBehaviour
    {
        /// <summary>
        /// 差し替え用シェーダ
        /// </summary>
        [SerializeField] private Shader _changeShader;
        
        /// <summary>
        /// 復帰用
        /// </summary>
        private Shader _keep;
        
        /// <summary>
        /// シェーダを保持&差し替え
        /// </summary>
        private void Awake()
        {
            var defaultCanvasMaterial = Canvas.GetDefaultCanvasMaterial();
            _keep = defaultCanvasMaterial.shader;

            defaultCanvasMaterial.shader = _changeShader;
        }
        
        /// <summary>
        /// 保持していたシェーダで復帰
        /// </summary>
        private void OnDestroy()
        {
            if (_keep != null)
            {
                Canvas.GetDefaultCanvasMaterial().shader = _keep;
                _keep = null;
            }
        }
    }
}

たったの43行のドシンプルなコンポーネントです。
やっていることは Canvas.GetDefaultCanvasMaterial() の、シェーダを差し替え&復帰しているだけ

「何故わざわざ復帰しているのか?」というと、この差し替えは Unityエディタ上でゲームの実行/停止をしても変更が残留してしまう ようでした。
まぁ一次的に切り替えてテストしたい用途でも使えそうなので、ちゃんと復帰しておくようにしましょう。

使い方

シーンに適当にGameObjectを追加 → ↑のコンポーネントを付与 → _changeShaderに冒頭のシェーダを設定する

だけです。

そうすれば、UIが赤くなることが確認できるでしょう

image.png
↓
image.png

おわりに

という事で、UI-Defaultシェーダの差し替えでした。
「そんな事をしたくなる事があるのか?」と不思議に思う方もいるかもですが、

  • テスト用だったり
  • Linear空間での計算フォローだったり
  • 特定のシーンだけUIの見た目をまるっと変えたり
  • VRS用にUI描画をカスタムしたり(←私はこの用途)

と色々使いどころはあるのではないかな?と思います。

あと「これだけだとTextMeshProUguiのシェーダが書き換えられないじゃないか!」と言われるかもですが、まぁそちらは「FontMaterialのシェーダを直で変えれば良くない?」と思うので、ここでは触れないでおきます。

モバイル実機での動作だけ未検証なので、それは検証出来たら追記します。

追記

モバイルでもちゃんと動きました。一安心

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?