はじめに
こんにちは。
サイバーエージェントゲームエンターテイメント事業部・SGEコア技術本部(以下、コアテク)のグラフィックスチーム所属の畳です。
コアテクでは、多機能なパーティクルシェーダーであるNova ShaderをOSSとして公開しています。
前回はエディタバージョンの互換性を保ちつつRenderGraphに対応する方法についてご紹介しました。
今回は、バージョン 2.6.0にて新たに追加した「不要なマテリアルパラメーターの削除ツール」についてご紹介します。
マテリアルに不要なパラメーターがあることによる弊害
Unity で使用されるマテリアルファイル(.mat)は、シェーダーに関するパラメーター情報などがYAML形式で記述されています。
たとえば、Create -> Rendering -> Material
により新しいマテリアルを作成すると、初期状態ではUniversal Render Pipeline/Lit
のシェーダーが割り当てられ、マテリアルファイルにLitシェーダーのパラメーターが記述されます。
その後、別のシェーダーに切り替えた場合でも、もとのLit シェーダーに属するパラメーターがマテリアルファイル内に残ったままとなります。
以下の図は、Universal Render Pipeline/Lit
からUniversal Render Pipeline/Unlit
に切り替えた際のマテリアルファイルの状態を示しています。
赤くなっている領域が、Unlit では不要となった Lit 側のパラメーターです。
そのため、不要なプロパティを残さないようにすることが望ましく、エディター上ではマテリアルのインスペクターウィンドウ内にあるReset機能を利用することで、現在選択中のシェーダーのパラメーターのみを残すことが可能です。
しかし、この方法ではすべてのパラメーターが初期状態にリセットされてしまうため、既存の設定が失われてしまうという課題があります。
選択されているシェーダー以外のパラメーターを削除する
この問題はスクリプトを用いて解決することができます。
下記は、マテリアルのうち現在選択されているシェーダーのパラメーターだけを残し、それ以外を削除するコードです。
private static void RefreshMaterial(Material material) { if (material == null) return; var tmpMaterial = new Material(material.shader); tmpMaterial.CopyMatchingPropertiesFromMaterial(material); material.CopyPropertiesFromMaterial(tmpMaterial); Object.DestroyImmediate(tmpMaterial); }
まず、対象のシェーダーから一時的なマテリアルを作成します。
その後、一時マテリアルに対して Material.CopyMatchingPropertiesFromMaterialメソッドを用い、元のマテリアルから対応するプロパティの値をコピーします。
一時マテリアルは、元のシェーダーに関連したパラメーターのみを持つため、不要なパラメーターを含みません。
最後にMaterial.CopyPropertiesFromMaterialメソッドを使用して、一時マテリアルのプロパティを元のマテリアルに上書きします。
シンプルな仕組みですが、この一連の処理により、現在選択されていないシェーダーに関連するパラメーターを安全に削除することができます。
自作シェーダーのパラメーターから不要な参照を削除する
前述のケースは、過去に使用していたシェーダーのパラメーターが残るという例でしたが、他にも不要なパラメーターが残ってしまうケースがあります。
それは、自作シェーダーやカスタムエディターを使用している場合です。
特に、パラメーター値によって実際に使用されるテクスチャを切り替えるような実装を行っていると、使われていないテクスチャの参照がマテリアルに残ることがあります。
例として、NOVA ShaderではBase MapにTexture2D、Texture2DArray、Texture3Dの3種類のテクスチャを設定することができます。
それぞれのテクスチャは異なる変数としてシェーダー内に定義されていますが、実際にシェーダーで参照するのはユーザーが選択したモードに対応するテクスチャのみです。
不要なテクスチャをインスペクターに表示してしまうと煩雑になるため、カスタムエディター側で非表示にしています。
ここに落とし穴があり、エフェクト作成中に設定したが使わなくなったテクスチャを見落とす問題が発生しやすくなります。
そのため、残ってしまった不要なテクスチャ参照を探す際には、インスペクターをくまなく確認せざるを得ない状況になります。
NOVA Shaderは規模が大きいため、こういった問題を解消する専用ツールをバージョン 2.6.0で追加しました。
コード例
private static void ClearTexture(Material material, string propertyName) { var id = Shader.PropertyToID(propertyName); if (material.GetTexture(id) == null) return; material.SetTexture(id, null); Debug.Log($"{material.name}: Removed unused texture from property: {propertyName}"); } private static void FixBaseMap(Material material) { var mode = (BaseMapMode)material.GetFloat(MaterialPropertyNames.BaseMapMode); switch (mode) { case BaseMapMode.SingleTexture: ClearTexture(material, MaterialPropertyNames.BaseMap2DArray); ClearTexture(material, MaterialPropertyNames.BaseMap3D); break; case BaseMapMode.FlipBook: ClearTexture(material, MaterialPropertyNames.BaseMap); ClearTexture(material, MaterialPropertyNames.BaseMap3D); break; case BaseMapMode.FlipBookBlending: ClearTexture(material, MaterialPropertyNames.BaseMap); ClearTexture(material, MaterialPropertyNames.BaseMap2DArray); break; default: throw new ArgumentOutOfRangeException(); } }
このコードは、現在選択されているテクスチャモードに応じて、不要なテクスチャパラメーターを削除します。
どのモードにおいてどのパラメーターが不要になるかは、各シェーダーの実装に依存するため、個別に条件分岐を記述する必要があります。
ClearTexture
メソッドでは、指定されたテクスチャプロパティが設定されている場合、その参照をnullにすることで除去します。
FixBaseMap
メソッドでは、現在選択されているモード以外のテクスチャを ClearTexture
メソッドを用いて個別に削除しています。
このように、各プロパティごとに処理を用意することで、マテリアル全体のリフレッシュを実現しています。
ツールの実行タイミング
ここまでで、不要なパラメーターを削除する処理は整いましたが、他に考慮するべき点に実行タイミングがあります。
たとえば、メニューから手動でツールを実行する形式にすると、以下のような課題が発生します。
- ツールの存在を知っている人しか利用できない
- 実行するまで不要な参照が残ったままになる
- 実行後、再編集すれば再び不要なパラメーターが生まれる可能性がある
つまり、ヒューマンエラーが発生しやすい構造となってしまいます。
そこで本来であれば、プロパティ変更を検知して自動実行する仕組みが望ましいと考えていました。
しかし、社内のアーティストからは「エフェクト作成中は頻繁にプロパティを変更するため、そのたびにテクスチャが None に変更されるのは不便」というフィードバックがありました。
そのため、NOVA Shaderでは使い勝手を優先し、任意のタイミングでメニューから実行する形式を採用しています。
おわりに
以上、Nova Shader バージョン 2.6.0で追加された、不要なマテリアルパラメーターの削除機能についてご紹介しました。
マテリアルを整理しておくことは、将来的なトラブルの防止につながります。
本記事の内容が、皆さまのご参考となりましたら幸いです。