問題
Canvas.GetDefaultCanvasMaterial
で取得できるマテリアルのシェーダーを置き換えることで、uGUIにカスタムしたシェーダーを使うことができます。
Canvas.GetDefaultCanvasMaterial().shader = customShader;
一部シーンでSRPを使っている関係で、ゲーム中に動的にGetDefaultCanvasMaterial
のシェーダーを置き換えていたのですが、Mask
コンポーネントでマスクされたuGUIのシェーダーが置き換え前の初期uGUIシェーダーになってしまい、表示が破綻してしまいました。
解決方法
上記のコードでシェーダーを置き換えた後、下記処理を入れることで解決しました。
StencilMaterial.ClearAll();
foreach (var canvas in FindObjectsOfType<Canvas>().Where(x => x.isActiveAndEnabled))
{
canvas.enabled = false;
canvas.enabled = true;
}
解説
マスクできるuGUIは全てMaskableGraphic
を継承しています。
MaskableGraphic
は、マスクされたときにGetModifiedMaterial
メソッドが呼ばれ、その中で現在のuGUIのマテリアルを基にして、マスクされた時用のマテリアルをStencilMaterial.Add
で生成しています。
GetDefaultCanvasMaterial
を置き換える前にマスク用のマテリアルが作られてしまうと、GetDefaultCanvasMaterial
を置き換えた後もそのマテリアルが使われるため、これが原因で表題のようなことが起きていました。
対策として入れたコードは、UIのデフォルトマテリアルのシェーダー置き換え前のものが残ってしまっているところを、クリア&全部Graphic.SetMaterialDirty
呼ぶことで解決しているものです。
社内の有識者に教えてもらったんですが、コンナノワカラナイヨ…!
余談:Unityの標準コンポーネントのコードってどこで読むの?
今回だと、MaskableGraphic.cs
を読むことで原因が分かったんですが、標準コンポーネントのソースコードってどこで読むのが正道なんですかね…?
URPをインポートしてるプロジェクトでMaskableGraphic.cs
を検索にかけたら Librarly フォルダ内の下記の場所に格納されてて読めたんですが、こういう調べ方がセオリーだとは思わないので、次似たようなことが起きたときのために参照すべき場所を知っておきたい…。
Libraly/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/
参考:MaskableGraphic.GetModifiedMaterial
/// <summary>
/// See IMaterialModifier.GetModifiedMaterial
/// </summary>
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
var toUse = baseMaterial;
if (m_ShouldRecalculateStencil)
{
if (maskable)
{
var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
m_StencilValue = MaskUtilities.GetStencilDepth(transform, rootCanvas);
}
else
m_StencilValue = 0;
m_ShouldRecalculateStencil = false;
}
// if we have a enabled Mask component then it will
// generate the mask material. This is an optimization
// it adds some coupling between components though :(
if (m_StencilValue > 0 && !isMaskingGraphic)
{
var maskMat = StencilMaterial.Add(toUse, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMat;
toUse = m_MaskMaterial;
}
return toUse;
}