【Unity】スクリプトからShaderを変更する

2019/08/17DEVELOP

実行中にスクリプトからShaderを変更するには二通りの方法があります。Materialに割り当てられているShaderファイルを入れ替えるか、Materialごと入れ替える必要があります

確認バージョン

2019.1.9f1

MaterialのShaderファイルを入れ替える

最もシンプルなShaderの入れ替え方は以下の様に書きます。

gameObject.GetComponent<Renderer>().material.shader = Shader.Find("Legacy Shaders/Diffuse");

予めビルドインシェーダーとして含まれているシェーダーであれば、Shader.Findが使えます。

Androidなど出力すると正常に表示されない(ピンク色現象)

Shader.Findできるシェーダーファイルは、ビルトインシェーダーに登録されているもののみです。そのためビルトインシェーダーに登録されていないと、アプリをビルドした際にシェーダーファイルを見つけることが出来ず表示がおかしくなってしまいます。

ビルトインシェーダーの場所

メニューの「Edit」->[Project Settings..]からGraphicsメニューのAlways Included Shadersから確認できます。任意のシェーダーファイルをこちらに追加するとShader.Findで取得できる様になります。

ビルトインシェーダーに含めない場合

予めビルトインシェーダー( Always Included Shaders )に含めない場合はResourcesにShaderファイルを含め、Resources.Loadで読み込んでしまっても良いです。

Shader shader = Shader.Find("Shaderファイルへのパス");
gameObject.GetComponent<Renderer>().material.shader = shader;

Materialごと入れ替える

MaterialにはShaderが設定されていて、ファイルとして作成できます。となればMaterialごと入れ替えてしまっても良いです。経験としてはこちらの実装が多いです。

Material mat = Resources.Load<Material>( "Materialファイルへのパス" );
gameObject.GetComponent<Renderer>().material = new Material(mat); // コピーを使う。

Materialはコピーしたものを使用します。コピーしておかないと、複数のGameObjectに同じMaterial割り当てた場合、プロパティの値を共有してしまいます。

Material mat = Resources.Load<Material>( "Materialファイルへのパス" );
// クラスAのGameObject
gameObject.GetComponent<Renderer>().material = mat;
// クラスBのGameObject
gameObject.GetComponent<Renderer>().material = mat;
gameObject.GetComponent<Renderer>().material.SetFloat("_Color", Color.red)

サンプルの様にコピーせずに使用した場合、クラスBの_Colorプロパティの値を変更したいだけなのにクラスAの_Colorプロパティの値まで変わってしまいます。これはResources.LoadしたMaterialファイルがコピーではなく実態を伴っているためです。そのためコピーせず書き換えてUnityEditorを閉じたりするとファイル自体が書き換わってしまうので超危険です。必ずコピーしたものを使う様にしましょう。

後片付けは忘れずに

Materialに限らずnewしてインスタンスをクローンしたものは破棄する癖をつけましょう。メモリリークします。C#はメモリリークしない言語というわけではありません。時々勘違いする人がいますが、そこまでお世話してくれません。

private Material m_material;
void OnDestroy()
{
    GameObkect.Destroy(m_material);
}
void Start()
{
    var mat = Resources.Load<Material>( "Materialファイルへのパス" );
    m_material = new Material(mat);
    gameObject.GetComponent<Renderer>().material = m_material;
}

Posted by kazupon