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」にリネームしたとして、下記の様に記載してみます
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シェーダをこれに置き換えてみましょう
置き換え用コンポーネント
という事で本題のコンポーネントのコードです
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が赤くなることが確認できるでしょう
おわりに
という事で、UI-Defaultシェーダの差し替えでした。
「そんな事をしたくなる事があるのか?」と不思議に思う方もいるかもですが、
- テスト用だったり
- Linear空間での計算フォローだったり
- 特定のシーンだけUIの見た目をまるっと変えたり
- VRS用にUI描画をカスタムしたり(←私はこの用途)
と色々使いどころはあるのではないかな?と思います。
あと「これだけだとTextMeshProUguiのシェーダが書き換えられないじゃないか!」と言われるかもですが、まぁそちらは「FontMaterialのシェーダを直で変えれば良くない?」と思うので、ここでは触れないでおきます。
モバイル実機での動作だけ未検証なので、それは検証出来たら追記します。
追記
モバイルでもちゃんと動きました。一安心

