目次
環境
- Unity 2022.1.0f1
対象読者
Unityを利用して開発を行い、Materialを利用したことがある方、またMaterialの管理についてよりよい方法を探している方。
はじめに
Material VariantはUnity 2022.1から実装された新機能です。Variantと聞くと、Unity 2018.3から導入されたPrefab Variantを思い浮かべる方もいるかもしれません。実はイメージはPrefab Variantと同じです。Prefab VariantではベースとなるPrefabを作成し、その差分をPrefab Variant側に保存することで共通部分を効率よく管理、更新することができました。これをマテリアルにも実装したのが今回のMaterial Variantになります。
Prefab Variantを利用したことがある人はもちろんですが、利用したことがない人でもMaterial Variantはさほどむずかしいものではないのですぐに利用できるようになると思います。
ここでは、Material Variantとはなにか、その生成・利用方法、そして活用方法についてご紹介します。
Material Variantとは
Material Variantはマテリアルを効率よく管理することを目的として実装されたUnity2022.1からの新機能です。Unityで開発しているとたびたび遭遇するのが、基本のマテリアルは共通だがいくつかの点で異なるマテリアルがほしくなる、というシーンです。(例えば色違いのモンスターを作成するなど)
今までであればそうしたマテリアルを作成したい場合、いくつかの方法で実現できました。
ひとつめは、シンプルにマテリアルを複製する方法。ファイルとしても存在するようになるため、微調整がしやすく、Gitなどの履歴管理ツールを導入している場合はその履歴を保存しておくこともできます。
ふたつめは実行時にマテリアルの値を書き換える方法。こちらはC#からマテリアルを書き換えて差分を作る方法です。プログラムから操作する場合はその設定をどこかで保持しておく必要があったり、不用意にたくさんのマテリアルを書き換えてしまうとBuilt-inパイプラインでは描画負荷(マテリアルのバッチング問題)につながったりとデメリットも目につきます。
みっつめはMaterialPropertyBlockを利用した方法。これは前述のC#からの方法と似ていますが、マテリアルのコピーを作成せずに個別の値を設定できる機能で、共通の設定を持つオブジェクトが多数ある場合は、ふたつめの方法に比べてSetPassコールが減るため有用ですが、ふたつめ同様、その値をどこに保持しておくか、という問題は共通です。
どれも一長一短があり、どれが正解という話ではないですが、上記3つのうちふたつはどうしてもプログラマーの力が必要になってしまいます。しかし、絵作りという観点で見た場合、後半ふたつはあまり最適解とは言えません。となると最初の方法が一番いいことになりますが、これにも難点があります。例えば、複製した瞬間から元のマテリアルとはまったく別のものになってしまい、もしベースとなるマテリアルが変更された場合かつその変更を複製先のマテリアルにも適用しないとならないとなると、プロジェクト規模によってはかなりのコストになってしまいます。
こうした問題を解決するために導入されたのがMaterial Variantという機能です。
Material Variantの利用方法
さっそく利用方法について見ていきましょう。まずはベースとなるマテリアルを用意します。このマテリアルは標準のシェーダを利用したものでもいいですし、独自のシェーダでも問題ありません。基本的にVariant分に関するデータや機能はUnityエディタ側で制御してくれるため、シェーダを自作する場合でもMaterial Variantを意識した作りをしなくても動作します。
Universal Render Pipeline(以後、URP)の標準のLitシェーダを適用したマテリアルを作成しました。作成方法は今までと違いはありません。もし独自のシェーダを利用したい場合は、使用するシェーダを変更してください。
Material Variantの作成
先ほど作ったSampleマテリアルからMaterial Variantを作成します。作成するにはベースとなるマテリアルを選択し、右クリックでコンテキストメニューを開き「Create > Material Variant」を選択します。
すると、Sample Variantという名前で新しくマテリアルが生成されます。
よく見るとアイコンが少し変わっているのが確認できます。Prefab Variantと似た見た目になっていますね。しかし、インスペクタに表示されている内容は元となったマテリアルとまったく違いがないことも分かります。違う見方をすれば、Material Variantであっても通常のマテリアルと同様の操作が行えるということです。現時点では違いはありませんが、ここからMaterial Variantがどういう振る舞いをするのかを詳しく見ていきましょう。
Material Variantで違いを表現する
ベースとなるマテリアルは全体のルックを表現するために用います。つまり、例えば色違いのモンスターを作る場合などで、質感や反射の表現など全体として統一しておきたい設定を保持します。一方、Material Variantは色違いモンスターの色や、あるいはテクスチャだけなど「少しだけ異なる部分」の設定を保持します。
今回は色を変えてみましょう。Material Variant側の色だけを変えたものをShpereにアタッチしてみた図が以下です。
左側がベースとなったマテリアル、右側がMaterial Variantです。
現在の設定を見比べてみると以下です。Material Variant側の色が青に変更されているのが分かります。ただ、これだけではマテリアルを複製しただけのものと違いはありません。
前述したように、ベースマテリアルは全体のルックを調整するのに用います。ということで、色以外の部分を調整して別の見た目を作ってみましょう。設定を変更するのはベースマテリアルのみです。
MetallicとSmoothnessの値を調整し、金属っぽい見た目に変更してみました。ベースマテリアルだけを変更していますが、同様にしてMaterial Variant側の設定も変化しているのが分かります。
今回の例ではMaterial Variantがひとつのため、そこまで恩恵は感じられないかもしれません。しかしMaterial Variantが10、20と増えていくとその機能の恩恵を強く感じられるようになると思います。
そして実はMaterial Variantは、Material Variantからもさらに派生させることができます。つまり親、子、孫というように継承構造を作ることができるわけです。
差分を持つことができると言っても、細かい差分が膨大になることは想像に難くありません。例えば、龍のモンスターを作成しているとして、ベースはあるものの、龍種が複数グループあり、さらにそのグループ内でも個別の違いを表現したい、という場合、場合によっては龍種単位で違いを持っておき、さらに詳細を個別に定義したい、と思うのは当然のことでしょう。
このような場合は「龍ベース > 龍種 > 個別龍」というように定義を分けることができます。
それを踏まえて、上記の設定からさらに派生させてみたのが以下の画像です。
一番左は前述のベースマテリアルです。そして中央がその子にあたるMaterial Variantで、それぞれ法線マップを変えたものにしています。そして一番右が孫にあたるMaterial Variantで、それぞれ色を変更したものになっています。
以下の動画は、中央下の「子」のMaterial Variantの質感を変更しているところです。下側の3つだけが変化しているのが確認できますね。当然、大本のベースマテリアルを変更すればすべての状態を変更することができます。
プロパティの扱いについて
実際の利便イメージが湧いてきたでしょうか。ここからは、実際に制作を開始するにあたって、どういう機能があるのかを紹介していきます。チーム開発などでより力を発揮する機能などもあるので、フローを考える際の参考にしてみてください。
プロパティの変更をロックする
今回のサンプルのような規模であればあまり重宝しないかもしれませんが、規模が大きくなってくると変更されてしまうと問題になるようなケースも出てくると思います。その場合に使えるのがこのロック機能です。
親の側でプロパティをロックすると、その子や孫以降で変更ができなくなる機能です。ロックするのは簡単で、インスペクタ上に表示されるロックボタンを押してロックするだけです。すると子や孫以降で該当のプロパティの変更ができなくなります。
変更を親のマテリアルに適用する
開発中、マテリアルの変更を繰り返しているとその設定を親に適用したい、つまり全体の調整結果としたい場合があります。PrefabやPrefab Variantにもあるように、Material Variantにも変更を親に適用する方法があります。
変更を適用したいプロパティを右クリックし、Apply as Override in Variant ‘<MATERIAL_NAME>’を選択することで親に変更を適用することができます。
以下の画像はSmoothnessの変更を親に適用している例です。
実際に適用する様子の動画が以下です。最初はMaterial Variantが適用されたオブジェクトのみの見た目が変化していますが、それを親に適用すると、親に加えてそれを継承する他のMaterial Variantにも変更が適用されているのが確認できます。
変更を取り消す
変更の適用とは逆に、変更を取り消すことも可能です。色々試行錯誤してみたものの、元の値に戻したい場合に利用できます。また、親の変更を再反映させたい場合にもこの取消を行う必要があります。
Material Variant側で変更を行った箇所の左側に白いラインが入ります。この状態では、たとえ親とまったく同じ値が設定されていたとしても「Override状態」となり、親の変更が適用されなくなります。これを取り消す場合にも利用できます。
変更の取り消しは、適用と同じように該当のプロパティを右クリックし、Revertを選択することで可能です。
実行すると左側の白いラインが消え、値も親と同じ状態になります。
既存マテリアルのMaterial Variant化
前段までは新規作成の方法を見てきました。しかしすでにある程度制作が進んでいるプロジェクトでこれをやろうとすると骨が折れるかもしれません。しかし安心してください。既存マテリアルをMaerial Variant化する方法が用意されています。
まず、Material Variant化したいマテリアルを選択します。するとインスペクタに段落を模したようなアイコンが表示されています。これをクリックするとMaterial Variant関連のメニューが開きます。
初期設定では「Material」となっておりVariant化されていないことが分かります。
これをまず「Material Variant」に変更します。するとインスペクタに、親となるマテリアルを設定する項目が現れるので親にしたいマテリアルをドラッグ&ドロップで設定します。
この「Parent」のところに親となるマテリアルを設定します。
すると以後は、前述のMaterial Variant同様に、親の情報が伝搬されるようになります。
既存の値の継承
Material Variantの関係にないマテリアルを変更した場合、元々設定してあった値はどうなるのでしょうか。結論から言うと、同じシェーダを使っている場合は設定されている値がそのまま引き継がれます。Material Variant化したい場合は基本的に同じシェーダを利用するはずなのでここは問題にならないでしょう。
ただひとつだけ注意点があります。それは、親の側でロックを掛けたプロパティがある場合です。この場合はロックを掛けたことと矛盾が起きないように、すでに変更がされていた場合でも強制的に親の値で上書きされます。
実際に変更すると以下のような挙動になります。(色の値やSmoothnessの値は変更前と同じですが、ロックされているMetallic Mapの値が親と同じ値に変更されています)
Material Variant向けのEditor拡張を利用する際の注意事項
Material Variant向けにもEditor拡張を利用して機能を拡張することができます。しかし、いくつかの注意点があります。
Editor拡張を適切に実装しないとロックを無視できてしまう
前述したプロパティロックの機能は、あくまでインスペクタ上の機能に過ぎません。そのため、それを考慮しないEditor拡張を実装してしまうと意図せずロック対象のプロパティを上書きできてしまい、想定と違う結果になってしまう可能性があります。
例えばColorプロパティを変更する項目を表示するコードは以下のようになります。
// 入力内容が変更されたかチェック EditorGUI.BeginChangeCheck (); // Colorプロパティの変更を取得 Color color = EditorGUILayout.ColorField("color", Material.GetColor("_Color")); // 更新されたら内容を反映 if (EditorGUI.EndChangeCheck ()) { Material.SetColor ("_Color", color); } |
このEditor拡張を適用すると、例えMaterial VariantであってもColorプロパティを上書きできてしまいます。
Material Variantを意識したEditor拡張の実装
Material Variantを意識したEditor拡張を実装する場合、BeginPropertyメソッドとEndPropertyメソッドを利用することでこれを達成することができます。具体的なサンプルコードは以下のようになります。
using UnityEditor; using UnityEngine; public class SampleMaterialEditor : MaterialEditor { public override void OnInspectorGUI() { Material mat = target as Material; EditorGUI.BeginChangeCheck(); // For the color. MaterialProperty colorProp = GetMaterialProperty(new Object[] { mat }, "_Color"); BeginProperty(colorProp); ColorProperty(colorProp, "Color"); EndProperty(); // For the texture. MaterialProperty texProp = GetMaterialProperty(new Object[] { mat }, "_MainTex"); BeginProperty(texProp); TextureProperty(texProp, "MainTex"); EndProperty(); if (EditorGUI.EndChangeCheck()) { PropertiesChanged(); } } } |
上記サンプルはColorとMainTexというふたつのプロパティを持つシェーダに対するEditor拡張です。これを実装し、マテリアルのインスペクタの状態を見ると以下のようになります。
Colorのところにマウスオーバーした際に、ロックアイコンが表示されているのが確認できます。これにより、ロックのオン・オフや、親マテリアルでロックされたプロパティの操作を防ぐなど、標準のMaterial Variantと同じ振る舞いを実装することができます。
親マテリアルでロックした場合は鍵アイコンが表示され、編集ができなくなっている(プロパティ名がグレーアウトされている)のが確認できます。
SetPassコール・バッチングについて
以上でMaterial Variantの紹介は終了です。最後に、描画負荷の観点からどういう違いがあるかを紹介します。
Built-inパイプラインでは異なるマテリアルとして扱われる
Built-inパイプラインの場合は、Material Variantは異なるマテリアル扱いとなりバッチングされません。その他、MaterialPropretyBlockなどを用いても同様で、基本的に通常のマテリアルと同じ扱いとなります。
URPの場合はSRP Batcherは有効
Unityの公式ブログの記事でも言及があるように、Material Variantは通常のマテリアルアセットです。違いはオーバーライドされたパラメータのリストに加えて親への参照が保存されています。逆を返せば、それ以外は通常のマテリアルと同様の振る舞いをするということでもあります。
そのため、SRP Batcherのバッチング条件を満たしていればMaterial Variantを利用しても正常にバッチングされます。
最後に
Material Variantはデザイナーにとっても有用で、幅広い活用が期待できる機能です。グループ化を適切にすることで今までにはなかった効率的な制作フローを構築することができるでしょう。描画負荷的にも、URPであれば問題なく利用することができるので積極的に導入していきたい機能です。これを気に導入のきっかけになれば幸いです。
--
筆者紹介:えど

ARスタートアップのMESONでUnityエンジニアとして従事。もとはカヤック時代にWebエンジニアとしてリーダーを務め、その後VRと出会いコロプラに転職。コロプラでは仮想現実チームにてXRコンテンツ開発に携わる。Daydream向けゲーム「Nyoro The Sanke & Seven islands」をリリース。またプライベートでもAR/VRの開発をしており、インディー部門でTGSに出展など公私関わらずAR/VRコンテンツ制作に精を出す。プライベートな時間でも開発しているように、新しいことを学ぶ事が趣味で、最近は英語を学んでいる。 Twitter:https://twitter.com/edo_m18