【Unity】マテリアルごとにメッシュを結合する簡単なサンプル
UnityではMeshクラスのCombineMeshesメソッドを使ってメッシュを結合することができます。そのまま結合すると元々のメッシュにあったマテリアルが適用されないので、同じマテリアルを持っているメッシュをグループ分けして結合する簡単なサンプルを作ってみました。
結合するオブジェクトのMeshRendererではマテリアルがひとつだけ設定されていると想定してコードを書いています。もし複数のマテリアルがある場合はマテリアルを配列なりリストなりで保持するように書き換えてもらうといいかもしれません。
ここではCombineMeshesメソッドの機能の紹介のような位置付けなので、実際に使うならMeshBakerなどのアセットを使った方が簡単に実現できると思います。車輪を再発明するのではなく、車輪の仕組みを学びたい人向けの記事です。
環境
macOS 11.1 Big Sur
Unity2019.4.4f1
このページの前提となるもの
パーリンノイズを使って地形の高さを調整するサンプルコードを以下の記事で紹介しています。こちらのコードを使って動的に地形となるオブジェクトを生成します。
続いて以下の記事でメッシュの結合を扱いました。複数のメッシュを結合することでCPUのオーバーヘッドが減り、処理がだいぶ軽くなります。
このメッシュの結合の際に、対象のメッシュ数が多くなると以下のように意図しない形で結合されてしまうため、インデックスバッファの値を変更して結合したのが以下の記事です。
これらのメッシュの結合を行う際に、結合後にひとつのマテリアルを設定するようにしていましたが、実際に使うことを考えると全体でひとつではなく、同じマテリアル単位で結合して、結合後もマテリアルを維持するようにしてみます。
マテリアルごとにメッシュを結合する
ここでは、あるオブジェクトの子オブジェクトとして作成されている地形オブジェクトを結合します。この時、GetComponentsInChildrenを使って地形オブジェクトのMeshFilterとMeshRendererをそれぞれ取得します。
取得したMeshRendererの配列から、中身のマテリアルを確認していきます。マテリアル名をキーとして、マテリアルへの参照を保持する辞書、マテリアルに対応するMeshFilterのリストを保持する辞書を作成します。
マテリアル名でグループごとに分けることができたら、『このページの前提となるもの』で提示したページのソースコードのようにメッシュを結合していきます。適用するマテリアルについては、キーとなるマテリアル名を使って簡単に取得できるのでそれをセットすればOKです。
これらを考慮して作成したサンプルが以下のものになります。(プロジェクトでは『MeshMaterialCombiner.cs』というファイルを追加しています)
ちょっと長いので部分ごとに解説を。
クラスとフィールドの宣言部分です。地形オブジェクトの親オブジェクトをフィールドとして用意し、ここにインスペクターウィンドウからセットするようにします。
OnPressedCombineMaterialButton()のメソッドはシーン内のUIから呼び出します。
メッシュを結合するメソッドCombineMeshWithMaterial()の準備部分です。
このメソッドでは結合する地形オブジェクトのMeshFilterとMeshRendererを取得します。フィールドで指定したオブジェクトの子オブジェクトが持っているコンポーネントをGetComponentsInChildrenで取得しています。この時、それぞれの配列の数が合っていない場合は何か問題が起きている可能性があるので、処理を行わずに抜けています。
マテリアルごとにグループ分けする部分ではマテリアルの情報を保持するための辞書を作成します。マテリアル名をキーとして、マテリアルを値に持つ辞書、そのマテリアルを持っているMeshFilterのリストの辞書をそれぞれ作成しています。辞書の値としてリストを使えるのがC#の柔軟さかもしれません。
MeshFilterの配列に対して中身を確認していき、辞書にキーがあればMeshFilterのリストに追加、なければキーと値のペアを追加しています。
マテリアルでグループ分けしたメッシュを結合していきます。結合したメッシュを保持するためのオブジェクトを作成し、SetAsFirstSibling()のメソッドでオブジェクトの並び順を一番上に変更しています。作成したオブジェクトに対してはMeshFilterとMeshRendererのコンポーネントをアタッチしています。ここで使用しているCheckComponentのメソッドはスクリプト内の後半で作成しているメソッドです。
ここからの処理は以前の記事で紹介したものと同じ流れになっています。(インデックスバッファについては32bitに変更しています)
CreateMeshObj()は結合したメッシュを保持するためのゲームオブジェクトを作成するメソッドです。ここでは地形オブジェクトの親オブジェクトの下に作成するようにしていますが、もし任意のゲームオブジェクトの子オブジェクトにする場合はSetParentの部分で別のオブジェクトを指定するようにしてください。
CheckComponent()はコンポーネントをアタッチするためのメソッドです。GetComponentした結果、なければ新しいコンポーネントをアタッチします。対象のコンポーネントについては型パラメータで指定するようにしています。
ランダムなマテリアルを持ったオブジェクトの生成
地形オブジェクトを生成する際に作成したスクリプトを変更し、オブジェクトの生成時にランダムなマテリアルをアタッチする処理を追加します。ベースとなるスクリプトの『FieldGenerator.cs』は以下の記事でサンプルを紹介しています。
フィールドとしてマテリアルをセットするリストを追加します。
ゲームオブジェクトをインスタンス化するInstantiateFieldParts()のメソッドの中で、マテリアルをランダムに追加する処理を追加します。Random.Rangeの引数をint型で指定した場合は、最大値となる値を含まないのでリストの大きさをそのまま指定しています。
マテリアルのリストにある値を取得し、生成したゲームオブジェクトのMeshRendererにセットしています。なお、ここで生成しているゲームオブジェクトのPrefabにはすでにMeshFilterとMeshRendererのコンポーネントがアタッチされています。
動作確認
『MeshMaterialCombiner.cs』のスクリプトをアタッチするゲームオブジェクトを作成し、地形オブジェクトの親オブジェクトを『Field Parent』のフィールドにアサインします。
地形オブジェクトの作成には『FieldGenerator』のスクリプトを使っているので、このスクリプトで任意のマテリアルをアサインします。
また、任意のボタンを作成して『OnPressedCombineMaterialButton()』のメソッドを呼び出せるようにします。
ゲームを実行してオブジェクトを生成します。オブジェクト数は横50*縦50の計2500個です。バッチ数(Batches)の数が10000にもなっているので単純にそれぞれのメッシュを表示させる場合は結構な負荷になります。これを結合してメッシュをまとめることで、21まで数を減らすことができました。
試しにヒエラルキーウィンドウから特定のマテリアル(ここでは水色)のメッシュだけ表示させてみます。マゼンタや黄色のオブジェクトが表示されておらず、マテリアル単位でメッシュを結合できたことがわかります。
また、テクスチャのあるマテリアルを使った場合でも結合してみます。左が結合前、右が結合後の画像で、結合後の画像でも元のマテリアルがそのまま同じように表示されているのが分かります。メッシュ全体で引き伸ばされた形になるのではなく、個々のオブジェクトのuvなどはそのまま残っているようです。となると、うまくメッシュ結合の恩恵を受けられているようですね。
まとめ
このページではメッシュを結合する際にマテリアル単位でメッシュをグループ化して結合するサンプルを紹介しました。
地形に関してはそれぞれで草原だったり砂漠だったりと異なるマテリアルを使っていると思うので、マテリアル単位で結合できると情報が失われることもありません。
異なるテクスチャを持つマテリアルを結合して……とまでやり始めるなら、おそらく自力で実装するよりもMeshBakerなどのアセットを使った方が簡単なので、こちらも検討してみると良いでしょう。
無料版もあるのでこちらで試すのもいいかも。
パーリンノイズ関係やそれに関連するメッシュ関係の記事は以下のページでまとめています。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【Unity】沢山のメッシュをCombineMeshesで結合すると変な風になるので対応する 2021.01.02
-
次の記事
インゲームとアウトゲームの用語を知らなかったので調べた【ゲーム開発】 2021.01.04
コメントを書く