【Unity2020】material.HasPropertyの挙動について
はじめに
material.HasProperty
を使用すると、マテリアルがプロパティを持っているかどうかを取得することができます。
このメソッドの振る舞いについて、直感に反するものがあったため記事としてまとめてみることにします。
環境
- Unity2020.3.32f1
- Built-in RP
使用したコード
シェーダー
今回は二つのシェーダーを検証に使用します。
- プロパティ
_A
を持つシェーダーShader1_HasPropA.shader
- プロパティ
_A
を持たないシェーダーShader2_NoPropA.shader
これらのシェーダーは、Resourcesフォルダ直下に配置し、Resources.Loadで読み込めるようにしておきます。
Shader1_HasProp.shader
Shader "Unlit/Shader1_HasPropA"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_A ("A", Float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
Shader2_NoPropA.shader
Shader "Unlit/Shader2_NoPropA"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
C#コード
今回の検証にあたって以下のC#メソッドを使用して、material.HasPropertyの振る舞いを検証することとします。
static void CheckProperty(Material mat)
{
var propertyName = "_A";
int propertyID = Shader.PropertyToID(propertyName);
bool hasProperty = mat.HasProperty(propertyID);
Debug.Log($"material.HasProperty({propertyName}) -> {hasProperty}");
}
このメソッドを実行すると、HasPropertyの結果がConsole上へ出力されます。
検証1 : シェーダーから動的に作成
マテリアルをシェーダーから動的に作成した場合、以下のような結果となりました
- シェーダーが
_A
を持つ場合、HasProperty
はtrue
を返す - シェーダーが
_A
を持たない場合、HasProperty
はfalse
を返す
var s1 = Resources.Load<Shader>("Shader1_HasPropA");
var m1 = new Material(s1);
CheckProperty(m1); // true
var s2 = Resources.Load<Shader>("Shader2_NoPropA");
var m2 = new Material(s2);
CheckProperty(m2); // false
検証2 : マテリアルを複製した場合
マテリアルをシェーダーから複製しても、HasProperty
が返す結果に変化はありませんでした。
var s1 = Resources.Load<Shader>("Shader1_HasPropA");
var s2 = Resources.Load<Shader>("Shader2_NoPropA");
var m1 = new Material(s1);
CheckProperty(m1);// true
CheckProperty(new Material(m1));// true
var m2 = new Material(s2);
CheckProperty(m2);// false
CheckProperty(new Material(m2));// false
検証3 : material.SetFloatでプロパティを書き換えた場合
プロパティ _A
を持たないシェーダーのマテリアルに対し、
SetFloat
でプロパティ_A
の値を設定するとHasProperty
はtrue
を返すようになります。
このマテリアルを複製すると、HasProperty
はfalse
を返すようになります。
var s2 = Resources.Load<Shader>("Shader2_NoPropA");
var m2 = new Material(s2);
CheckProperty(m2); // false
m2.SetFloat("_A", 100);
CheckProperty(m2); // true
var m2_copy = new Material(m2);
CheckProperty(m2_copy); // false
シェーダーがプロパティ_A
を持っている場合は、マテリアルを複製してもHasPropertyはtrue
を返します。
var s1 = Resources.Load<Shader>("Shader1_HasPropA");
var m1 = new Material(s1);
CheckProperty(m1); // true
var m1_copy = new Material(m1);
CheckProperty(m1_copy); // true
マテリアルを複製した場合、マテリアルのプロパティ情報は
シェーダー由来の状態にリセットされる、というふるまいをするようです。
検証4 : シェーダーを動的にさしかえ
マテリアルのシェーダーを直接変更した場合、マテリアルを複製した時と同じく、
HasProperty
の結果はシェーダー由来の状態に戻るようです。
var s1 = Resources.Load<Shader>("Shader1_HasPropA");
var s2 = Resources.Load<Shader>("Shader2_NoPropA");
var m1 = new Material(s1);
CheckProperty(m1); // true
m1.shader = s2;
CheckProperty(m1); // false
var s1 = Resources.Load<Shader>("Shader1_HasPropA");
var s2 = Resources.Load<Shader>("Shader2_NoPropA");
var m2 = new Material(s2);
CheckProperty(m2); // false
m2.shader = s1;
CheckProperty(m2); // true
マテリアルが持っているシェーダーと同じシェーダーを設定しても、プロパティの状態は元に戻ります。
var s2 = Resources.Load<Shader>("Shader2_NoPropA");
var m2 = new Material(s2);
CheckProperty(m2);// false
m2.SetFloat("_A", 100);
CheckProperty(m2); // true
m2.shader = s2;
CheckProperty(new Material(m2)); // false
余談 : Unity上で直接プロパティを追加してもHasPropertyに影響はない
UnityのInspectorをDebugモードにすると、マテリアル内部のプロパティ配列を直接いじることができます。
ここでプロパティ_A
を登録したとしても、HasProperty
はfalse
を返すようです。
var m2 = Resources.Load<Material>("Unlit_Shader2_NoPropA");
CheckProperty(m2);
まとめ
Unity2020.3.32f1においては、以下のような振る舞いをするようです。
-
material.HasProperty
はマテリアル自身がプロパティを持っているかどうかを返す - シェーダーのPropertiesブロックで定義されてないプロパティに対しても、
material.Set
系のメソッドでプロパティの値を設定できる - シェーダーが指定のプロパティを持っているかどうかの厳密な判定は
material.HasProperty
で行うことはできない-
material.Set
系のメソッドが呼ばれていた場合、プロパティが定義されていなかったとしてもmaterial.HasProperty
はtrue
を返す
-
Discussion