【Unity】MaterialのRenderQueueが2000に戻る罠
はじめに
Transparentシェーダー("Queue"="Transparent"
)を使用したマテリアルの
RenderQueue
が2000になるという不可思議な現象に遭遇したので
こちらについてまとめてみようと思います。
int renderQueue = material.renderQueue;
material.shader = shader;
material.renderQueue = renderQueue; // 2000になることがある
結論を先に述べると、以下の条件を満たすときにmaterial.renderQueue
が2000になります。
-
material.shader
がエラーを起こしている - マテリアルの
CustomRenderQueue
が -1 である
マテリアルのRenderQueue
マテリアルのRenderQueueは、マテリアル内部でCustomRenderQueue
という値として保存されています。
.matファイルをコードエディタで開いてみると、CustomRenderQueue
を確認できます。
m_CustomRenderQueue: -1
CustomRenderQueue
の挙動は以下の通りです
-
CustomRenderQueue
が -1 の場合、Shaderで定義されたRenderQueueが使用される - -1 以外の値の場合、マテリアルのRenderQueueとして使用される
Shader の RenderQueue
Shader上では、Queue
というシェーダータグを指定することで、RenderQueueを定義できます。
Tags { "RenderType"="Opaque" "Queue"="Overlay" }
参考 : https://docs.unity3d.com/ja/2019.4/Manual/SL-SubShaderTags.html
マテリアルのInspector上でFrom Shaderを選択した場合に、Shader上で定義されたRenderQueueが使用されます。
RenderQueueを直接入力
マテリアルのInspector上で、Render Queueの値を入力した場合、その数値がRenderQueueとして使用されます。
.matファイルをコードエディタで開いてみると、CustomRenderQueueに数値が格納されていることを確認できます。
m_CustomRenderQueue: 3000
ハマりポイント1 : Shaderを変更するとRenderQueueはリセットされる
C#コード側で、マテリアルのShaderを差し替えた時、CustomRenderQueue は -1 にリセットされます。
By default materials use render queue of the shader it uses. You can override the render queue used using this variable. Note that if a shader on the material is changed, the render queue resets to that of the shader itself.
_material.renderQueue = 3500;
Debug.Log(_material.renderQueue); // 3500 (CustomRenderQueue = 3500)
_material.shader = _shader;
Debug.Log(_material.renderQueue); // 3000 (CustomRenderQueue = -1)
シェーダーを差し替えたときに、元のRenderQueueの値を保持させたい場合、
Shader差し替え前のRenderQueueを保持しておいて、Shader差し替え後に復元するような実装をすると良さそうです。
void ReplaceShader(Material material, Shader shader)
{
// マテリアルに元から保持されているRenderQueueを保持
int renderQueue = material.renderQueue;
// マテリアルのシェーダーを差し替える (CustomRenderQueue = -1 にリセットされる)
material.shader = shader;
// RenderQueueを元に戻す
material.renderQueue = renderQueue;
}
しかし、この実装を行った場合、特定のケースでRenderQueueが意図しない値になることがあります。その理由は後述します。
ハマりポイント2 : FallbackErrorシェーダーはRenderQueue=2000
マテリアルが参照するシェーダーがエラーになっている場合、
レンダリングに InternalErrorShader
シェーダーが使用されます。
そして、このシェーダーのRenderQueue は 2000 です。
先ほど、RenderQueueがリセットされる現象を回避するための実装を紹介しました。
void ReplaceShader(Material material, Shader shader)
{
// マテリアルに元から保持されているRenderQueueを保持
int renderQueue = material.renderQueue;
// マテリアルのシェーダーを差し替える (CustomRenderQueue = -1 にリセットされる)
material.shader = shader;
// RenderQueueを元に戻す
material.renderQueue = renderQueue;
}
この実装は、以下のケースで問題が起きます。
-
material.shader
がエラーを起こしている - エラーを起こす前の
material.shader
のRenderQueueが2000以外 - マテリアルのCustomRenderQueue = -1
- マテリアルに最初に設定されていたRenderQueueを、shader差し替え後に適用したい場合
C#コードのコメントに補足を加えると、以下のような感じでしょうか。
void ReplaceShader(Material material, Shader shader)
{
// 2000 (CustomRenderQueue=-1、かつシェーダーがエラーとなっている場合)
int renderQueue = material.renderQueue;
// マテリアルのシェーダーを差し替える (CustomRenderQueue = -1 にリセットされる)
material.shader = shader;
// renderQueueが2000になる (元のmaterial.shaderのRenderQueueが2000以外でも問答無用で2000になる)
material.renderQueue = renderQueue;
}
マテリアルに最初に適用されていたshaderのRenderQueueが2000以外でも、
上記のメソッドを実行した結果RenderQueueが2000に戻るという現象が発生します。
こちらの問題を回避するためには、material.rawRenderQueue
を使用すると良いでしょう。
rawRenderQueue
material.rawRenderQueue
を使用すると、マテリアルの CustomRenderQueue
の値を直接取り出すことができます。
Unity6000
Unity6000では、rawRenderQueueはpublicなAPIとして公開されているため、簡単に利用できます。
void ReplaceShader(Material material, Shader shader)
{
// CustomRenderQueueを保持しておく
int rawRenderQueue = material.rawRenderQueue;
// マテリアルのシェーダーを差し替える (CustomRenderQueue = -1 にリセットされる)
material.shader = shader;
// RenderQueueを復元
material.renderQueue = (rawRenderQueue == -1)
? shader.renderQueue
: rawRenderQueue;
}
Unity6000以前
Unity6000以前ではmaterial.rawRenderQueue
はinternalなAPIとして定義されているため、
利用するためにはリフレクションを使用する必要があります。
internal extern int rawRenderQueue { [NativeName("GetCustomRenderQueue"), MethodImpl(MethodImplOptions.InternalCall)] get; }
void ReplaceShader(Material material, Shader shader)
{
// CustomRenderQueueを保持しておく
int rawRenderQueue = (int)typeof(Material)
.GetProperty("rawRenderQueue", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(material);
// マテリアルのシェーダーを差し替える (CustomRenderQueue = -1 にリセットされる)
material.shader = shader;
// RenderQueueを復元
material.renderQueue = (rawRenderQueue == -1)
? shader.renderQueue
: rawRenderQueue;
}
関連
Discussion