Skip to main content

ちょっと詳しい Mutable

Tags:

この記事では、Mutable の基本的な概念とCustomizable Object の作成方法...はすっ飛ばして、コンパイル、デバッグ、クック、パッケージングについて解説します。

Mutable とは

基本的な Mutable の説明や使い方は、公式ドキュメントがとても丁寧に解説してくれています。 なので、ここでは超ざっくりとしたことを書いておきます。

Mutable は、実行時に動的にスケルタルメッシュやマテリアル、テクスチャを生成する Unreal Engine のプラグインです。 UE5.5から正式にサポートされました。 主に、カスタマイズ可能なキャラクターやアイテムを作成するために使用されます。 それらを作成する際にMutable を使用することで、メモリの使用量を抑えたり、ドローコールを減らしたりと、より効率的で柔軟なキャラクターやアイテムのカスタマイズが可能になります。

Customizable Object とリソースのマージ

Customizable Object は、 Mutable で扱う主なアセットです。 オブジェクトに適用できるすべての要素が含まれており、実行時に制御可能なパラメータと、そのパラメータがオブジェクトにどのように適用されるかを定義します。

もっともシンプルな Customizable Object

Customizable Object のもっともシンプルな構成は、以下のようになります。
image 画像の例では、JacketA の SK_MeshComponent を持つオブジェクトを定義しています。 定義する過程で特に操作をおこなっていないため、元の SK_MeshComponent と同じ見た目になります。

マテリアルとメッシュのマージ

Mutable では、アセットを組み合わせることで新たなスケルタルメッシュを生成できます。
ノードグラフを使用してスケルタルメッシュの生成を制御できます。 一見同じ見た目のメッシュが生成されていても、グラフの組み方によって含まれるマテリアルやスケルタルメッシュの内訳が異なることがあります。

例として、マテリアルやスケルタルメッシュのマージを見てみます。作成したいオブジェクトを以下の画像のような「 BaseBody が JacketA と Pants を着用しているキャラクター」としましょう。
image

マージをおこなわない場合

まず、メッシュのマージをおこなわない場合は、以下のようなノードグラフになります。
image
この場合、このオブジェクトは BaseBody(Head) とBaseBody(Body) と JacketA と Pants の3つのスケルタルメッシュを持つことになります。

スケルタルメッシュのマージをおこなう場合

次に、メッシュのマージをおこなう場合は、以下のようなノードグラフになります。
image
同じ Mesh Componentに Mesh Section を追加するか、 Add To Mesh Component ノードを使用して、 Mesh Component を追加することで、メッシュのマージをおこないます。
結果として、このオブジェクトは BaseBody と JacketA と Pants の3つのメッシュをマージしたスケルタルメッシュを持つことになります。

マテリアルのマージをおこなう場合

最後に、マテリアルのマージをおこなう場合は、以下のようなノードグラフになります。
image
マテリアルのマージは親マテリアルが共通である場合に限られます。
ここで使用しているスケルタルメッシュは、BaseBody と洋服のマテリアルが異なります。
そのため、BaseBody(Head) と BaseBody(Body) をひとつの Mesh Section にまとめ、 JacketA と Pants をひとつの Mesh Section にまとめました。
結果として、このオブジェクトは2つのスケルタルメッシュと、それぞれのマテリアルを持つことになります。

注意として、マテリアルをマージするとき、マテリアルが持っていたパラメータはベースのマテリアルのものが全体に適用されます。そのため、仮に個別にカラーの調整などを行っていた場合、マージ後の領域で個別の調整が引き継がれることはありません。

ただし、テクスチャをマテリアルのパラメータとして使用する場合は例外です。マテリアルをマージするとき、ほかのパラメータと同じくマージ後はひとつしか残らないのは同じですが、同時にテクスチャのマージとメッシュ UV の再レイアウトが行われます。そのため、異なるテクスチャを持つマテリアルをマージしても、それぞれのテクスチャが正しく適用されます。
テクスチャをマージとメッシュ UV の再レイアウトを行うと、以下のようなテクスチャが生成されます。
これは、JacketA と Pants のテクスチャをマージしたものです。
image
image
UV の再レイアウトは、こまやかな設定が可能であるため各テクスチャに要求する内容に合わせて調整できます。

Customizable Object の分割

前節ではひとつの CO に書きましたが、CO を分割することもできます。
分割をすることで、作業が分かれるため、複数の人が同時に作業を進めることができます。
一方で、実は CO を分割しても、パッケージングすると一つのアセットにまとまってしまいます。この点について、詳しく後述します。

Customizable Object のコンパイル

CO のエディタには、「コンパイル」というボタンがあります。コンパイルとはいうものの、具体的には何をするボタンなのでしょうか?

ノードが扱うデータのおさらい

Mutable のコンパイルを知るために、ノードが扱っているデータの種類をおさらいしましょう。

メッシュセクション

image
メッシュセクションは、ひとつのマテリアルで描画されるメッシュの領域です。メッシュセクションには、メッシュとそれをレンダリングするためのマテリアルが含まれます。

メッシュコンポーネント

image
メッシュコンポーネントは、複数のメッシュセクションの集合体です。複数のメッシュセクションを組み合わせることで、生成するスケルタルメッシュを表します。 LODごとに異なるメッシュセクションを含めることで、LODの切り替えに対応できます。

オブジェクト

image
オブジェクトは、Customizable Object のルートを表します。ここには、複数のメッシュコンポーネントや、それらを変更するための情報が含まれます。

コンパイルとは?

CO のコンパイルでは、以下の処理が行われます。

  1. 利用している UE アセットを収集し、Mutable 形式へ変換する
  2. Customizable Object 処理のバイトコード化

どちらの処理も、オブジェクトノードを起点とします。ノードの依存関係をたどることで CO のノードグラフ全体を解析することでおこなわれます。
image

この際、CO 内で呼ばれているほかの子 CO も解析されます。そのため、子 CO を持つ場合、親となる CO から順に解析されます。 子 CO も含めたすべての CO のノードグラフが解析された後、コンパイルが完了します。

Mutable は UE 上で動作するプラグインですが、利用するアセットも CO の処理も、どちらも Mutable 独自の形式に変換して利用します。 変換されたアセットや処理は、CO アセットに埋め込まれます。

アセットの収集と変換

CO で参照した UE のアセットはノードの依存関係を辿って収集されるので、オブジェクト、メッシュセクション、メッシュコンポーネントの順に収集されます。その過程でテクスチャなど、他のアセットがあればそれも収集されます。

収集されたリソースは Mutable の独自形式に変換されます。 変換されたデータは、C++上では Mutable のリソースクラスに格納されます。例えば、メッシュやマテリアルは、mu::Meshクラス、テクスチャは mu::Image クラスに格納されます。

Customizable Object 処理のバイトコード化

CO のグラフは、実行のたびにゼロから解釈されるわけではありません。コンパイル時に、グラフの処理をバイトコード化します。 バイトコードとは、機械にとってわかりやすい命令の列に変換されたプログラムのことです。

アセットの収集とおなじく、ノードの依存関係に従ってバイトコード化されます。こちらの場合、依存関係のないノードから処理が実行できるようにバイトコードに変換されていきます。

Mutable のバイトコードには、たとえば以下のような命令が含まれます。グラフ上のノードは、こうしたより小さな命令に変換されていきます。

Mutable Bytecode
// mu::Image をリサイズ
IM_RESIZE
// mu::Mesh をマージ
ME_MERGE
// Curve からスカラーの値を取得
SC_CURVE

CO から生成されるバイトコードは、実行時に Mutable のランタイムモジュールによって実行されます。実行のときに必要なのは、このバイトコードであるため、パッケージング後には CO のグラフなど、エディタ上での情報は削除されます。 C++ 上では、mu::FProgram クラスの中に格納されます。mu::FProgram はさらに、mu::Model クラスに格納されており、このクラスがプログラムを実行するためのインターフェースを提供します。

生成されたバイトコードやリソースは、デバッグ機能を使って確認することができます。詳しくは後述します。

実行時の動作

実行時には、CO から生成されたバイトコードが Mutable の独自形式のデータを参照する形で実行されます。

Customizable Object のデバッグ機能

Mutable には、CO のデバッグ機能があります。デバッグ機能を使うことで、CO のコンパイル結果を確認できます。

デバッグタブ

CO アセットを右クリック → コンテキストメニューから「デバッグ」を選択すると、デバッグタブが開きます。 image

  1. バイトコードの情報をツリー形式で表示します。ツリーを展開することで、バイトコードの構造を確認することができます。 併記された値は実行順をしめしており、小さい値から順に実行されます。 image
  2. 最終的に生成されたリソースの情報を表示します。 image
  3. 1で選択したプログラムが実行されたタイミングのリソースの情報を表示します。 image

Customizable Object のクックとパッケージング

クックは、プロジェクトをパッケージングする際にアセットに対して行われる処理です。 CO のクックでは、上で述べたコンパイル処理が行われます。そのため、CO 内で参照している UE のアセットは独自形式に変換され、処理はバイトコード化されます。

このように、CO が利用するアセットは自動的に収集されて埋め込まれます。そのため、パッケージング後には CO が参照したアセット自体は不要となり、他の箇所で利用されていなければパッケージに含まれることはありません。

一方で、CO は利用するアセットをクック時に全て収集してしまうため、DLC などでカスタマイズ用のアイテムを追加する場合、アイテムアセットの追加だけでは対応できません。この仕様は現在の Mutable の制約であり、プロジェクトにとって制限となる場合があるため、注意が必要です。

Customizable Object Instance

COI は内部でプログラムを保持しているのではなく、親となる CO への参照と、その CO に対して適用するパラメータを保持します。

アプリケーションで、実際にキャラクターコンポーネントにアタッチするのはこの COI です。 COI はあくまでパラメータを保持しているにすぎないため軽量であり、アプリケーションの容量に負担を与えません。

また、CO との大きな違いは、CO はコンパイル時に親や子の COとマージされてひとつのアセットになるのに対し、COI はマージされることなくそのままアプリケーションに組み込まれます。

知っておくと便利なこと

Console Variables

私が個人的に便利そうだなと感じたコンソールコマンドをいくつか紹介します。

  • mutable.WorkingMemory
    • キャラクター作成時に、作業メモリとして使用するメモリサイズを制限します。
    • デフォルト: PCは50MB、そのほかは10MB
  • mutable.MaxTextureSizeToGenerate
    • 生成するテクスチャの最大サイズを指定します。
    • デフォルト: 制限なし

モバイルなどのリソース制限のある環境で使用するときに便利だなと思いました。いいねぇ。👌

DataTable を使ったアセットの一括投入

DataTable を使用すると、CO に大量のカスタマイズ用アセットを一括で投入することができ、処理の再利用が容易になります。

しかし、DataTable は実行時に読み込まれるわけではなく、コンパイル時にその中身をすべて読み込んで、バイトコードと変換済みアセットを生成しています。つまり、ノードグラフ上での表記はすっきりまとまって見えますが、実際には CO に大量のノードを連ねたオブジェクトを作成しているのと変わりありません。 加えて、実行時に読み込まれるデータではないため、DataTableを変更した場合は再コンパイルが必要です。

おわりに

Mutable よさそうでしょ。楽しいよ。👗