gracetory’s blog

東池袋にある合同会社グレストリのエンジニアブログです

MeshBakerでランタイムのメッシュ結合機能を使う!

こんにちは、yamauraです。 みなさんお盆はいかがお過ごしでしたでしょうか。

私の方はというと、弊社はお盆を含む夏期休暇で9連休と長いお休みだったのですが、このコロナ禍ではなかなか遠出をすることもできず、 趣味の登山を諦めて近所の川で15年ぶりの釣りに挑戦してきました。 3日で1匹というひどい釣果でしたがおかげで陽の光をたっぷり浴びれたのは良かったかなと思います。

リモートワーク推奨で引きもこりがちなこの時期、近場での釣りを外に出る口実としてオススメしたいです。



MeshBakerを使う

techblog.gracetory.co.jp

さて、今回は前回のUnityでのレンダリングの仕組みに関連して実際に「ドローコール・セットパスコール」を削減します。

使用するのはMeshBakerというアセット。 Unityを使っていればほぼ確実に聞くことになる超有名アセットですね。

f:id:gre_yamaura:20200817135510p:plain

簡単に説明すると異なるゲームオブジェクト同士のテクスチャ・マテリアル・メッシュを結合して1つのオブジェクトにすることで、レンダリングのパフォーマンスを向上させることができるというものですが、人物モデルなどに必要なSkinnedMeshRendererや表情アニメーション用のブレンドシェイプも結合できるということで、最近ではVtube用のアバターを軽くする用途で使われることが増えてきたように思います。

ただMeshBakerは有名アセットですので前述のVtubeアバターのパーツ結合や、もともと使われていた町並みや複数の建物を結合するような使い方の記事がすでに沢山あります。 同じ内容を紹介してもあまり意味がありませんので、今回はランタイムでメッシュ結合を行う手法を紹介したいと思います。

ランタイムでの使用について

digitalopus.ca

ランタイムでMeshBakerを使う場合、公式マニュアルでは事前にテクスチャを結合してそれに対応するマテリアルを作成。 プログラム実行中にメッシュ部分を結合する。という工程が紹介されています。 テクスチャの結合は軽いものでも数十秒から数分かかってしまうため、この部分はコルーチンを使ってバックグラウンドで用意するかエディタ上で事前に準備をしておくということのようです。

テクスチャの結合(事前準備)

では実際にランタイムでメッシュ結合ができるように、事前にテクスチャの結合を行っていきます。

1.MeshBakerのインポート

assetstore.unity.com

まずはアセットストアでMeshBakerをダウンロードし、Unityのプロジェクトにインポートします。 無料版と有料版がありますが、無料版では結合できるポリゴン数の制限・結合できるシェーダーの制限・プレハブへの出力制限があるようです。 今回は有料版を使用していますが、ランタイムのメッシュ結合については記載がありませんので無料版でも使用できるかと思います。

2.結合用のオブジェクト配置

f:id:gre_yamaura:20200817135540p:plain

インポートが完了すると上部メニューの「GameObject -> Create Other」に「MeshBaker」の項目が追加されます。 そこから何を結合するかでオブジェクトを選択するのですが、今回はテクスチャを結合するので「TextureBaker」を選択。 するとシーンにTextureBaker(0)というオブジェクトが配置されます。

3.結合したいオブジェクトをまとめる

f:id:gre_yamaura:20200817135556j:plain

CreateEmptyで空のオブジェクト(CombineGroup)をシーンに配置し、そこに結合に使用する元のオブジェクトを子としてまとめます。 ※位置や回転が違う全く同じオブジェクト(複製)は対象に含めなくて大丈夫です。

4.結合対象を選択

f:id:gre_yamaura:20200817135624p:plain f:id:gre_yamaura:20200817135633p:plain

TextureBaker(0)のインスペクターから「Open Tools For Adding Objects」をクリックすると専用のウインドウが開きます。 このウインドウが開いた状態でヒエラルキーから先程オブジェクトをまとめた親のオブジェクトを選択状態にして、ウインドウ内の「Search For Meshed To Add」をクリック。

f:id:gre_yamaura:20200817135642p:plain

ウインドウのタブが切り替わるので「Exclude meshes with out-of-bounds UVs」のチェックを外して、「Add Selected Meshes To Target」をクリック。

f:id:gre_yamaura:20200817135649p:plain

TextureBaker(0)のインスペクターに戻るとヒエラルキーで選択したオブジェクトにまとめられていた子要素が「Objects To Be Combined」というパラメータに自動で設定されます。 結合対象から外したかったり追加したいオブジェクトがある場合はこのタイミングでパラメータを手動で変更します。

5.テクスチャの結合

f:id:gre_yamaura:20200817135659p:plain

TextureBaker(0)のインスペクターを下に送り、「Create Empty Assets For Combined Material」で結合されたテクスチャとそれに対応して作成されるマテリアルの出力先を指定。 さらにインスペクターを下に送って「Bake Materials Into Combined Material」をクリック。 しばらく待つと先程指定した出力先に結合されたテクスチャとそれに対応するマテリアルが出力されます。

f:id:gre_yamaura:20200817135707p:plain

それと同時に上記SSだと「CombineItems」という名前でMB2_TextureBakeResultsアセットが生成されますが、 これは結合されたテクスチャと元になったオブジェクトのメッシュのUVマップを関連付けるファイルになります。

メッシュの結合(ランタイム)

テクスチャとマテリアルの準備ができましたので、次はランタイムでメッシュを結合するための準備をします。

1.メッシュ結合用のスクリプトを作成

〜
public class CombineTest : MonoBehaviour
{
    public MB3_MeshBaker meshbaker;
    public GameObject mCombineObjl;
    private GameObject[] mObjArray;

    private void Start()
    {
        meshbaker = GetComponent<MB3_MeshBaker>();

        // メッシュを結合するゲームオブジェクトを取得
        int length = mCombineObj.transform.childCount;
        mObjArray = new GameObject[length];
        for(int i=0; i<length; i++)
        {
            mObjArray[i] = mCombineObj.transform.GetChild(i).gameObject;
        }

        // MeshBakerに登録
        meshbaker.AddDeleteGameObjects(mObjArray, null, false);

        // 登録されているゲームオブジェクトのメッシュを結合し、シーン内に出力
        meshbaker.Apply();

        // 結合元となるゲームオブジェクトを削除する
        Destroy(mCombineObj);
    }
}
〜

CombineTestスクリプトでは結合するオブジェクトを子としてまとめた親のGameObject参照を取得し、そこから子のGameObjectをMeshBakerへ登録。 登録されたGameObjectのメッシュを1つにまとめて出力。 出力後に結合前のオブジェクトを削除することで、シーン上には結合済のオブジェクトのみが残っている状態にする。ということを行っています。

今回は簡易的に予め用意したオブジェクトグループを結合対象としていますが、 ローグライクなゲームでダンジョンを自動生成する場合やRPGツクールでおなじみのマップパターンをファイルから読み込んでマップを生成するような場合は それぞれのパーツをインスタンス化した後に親のGameObjectにまとめて、その親の参照をmCombineObjに設定するような形で対応できます。

2.メッシュ結合用のオブジェクトを配置

f:id:gre_yamaura:20200817135913p:plain

シーン上に「CombineTest」という名前のオブジェクトを配置し、そこにスクリプトの「CombineTest」をアタッチ。 それと「MB3_Mesh Baker」をアタッチし、Texture Bake Resultに先程テクスチャやマテリアルと一緒に生成されたMB2_TextureBakeResultsアセットを設定します。

3.実行と結果

準備ができたらプロジェクトを実行。 するとシーン上に結合された1つのオブジェクトが生成されました。

f:id:gre_yamaura:20200817135753j:plain f:id:gre_yamaura:20200817142634j:plain

左が結合を行っていない状態のStatsで、右が結合した状態のStatsです。 Batchesが約1/10になりSetPassCallsも1/3になってます。 劇的な効果ですね!

終わりに

いかがだったでしょうか。 あらかじめメッシュ結合したいオブジェクトを1つずつまとめてテクスチャとマテリアル、MB2_TextureBakeResultsを生成するという手間はありますが、 それさえ準備しておけばランタイムでメッシュ結合を簡単に実現できるかと思います。

1つだけこれはランタイムに限らずですがメッシュ結合を行った結果、本来は一部しか描画されないオブジェクトグループだったのに1つのオブジェクトとして認識されたため見えていない部分を含めてレンダリングの計算が行われて逆に負荷が上がった。なんていう事例もあるようです。

使用についてはケースバイケースということになりますが、まだMeshBakerを使ったことがない。なんて方はぜひこの機会に一度触れてみてほしいです!

※MeshBakerでのランタイムのメッシュ結合機能を実践するにあたって参考になる日本語の記事がまったく見つからず、公式マニュアルのみを参考にしています。 英語が得意なほうではないので、誤りなどありましたらご指摘いただけますと幸いです。