【Unity】沢山のメッシュをCombineMeshesで結合すると変な風になるので対応する

【Unity】沢山のメッシュをCombineMeshesで結合すると変な風になるので対応する

別の記事で、CombineMeshesを使ってメッシュを結合し、バッチ数を減らすサンプルを紹介しました。

この記事の中で使っている地形に関してはパーリンノイズを使ってオブジェクトの高さを決めていて、滑らかに変化する地形を自動的に作成できます。ただ、ひとつひとつオブジェクトとしてメッシュを描画しているため、バッチ数がエライコッチャになって地獄めいた処理負荷になったのでした。

そこでメッシュを結合してバッチ数を減らしたものの、結合するオブジェクトが多くなると意図した通りに結合できなかったので、「これメッシュの頂点で制限あるな?」とやっと思い至りました。Unityだとデフォルトではメッシュのインデックスバッファは16bitなので、これで引っかかっていたみたいです。

 

 

環境

macOS 11.1 Big Sur

Unity2019.4.4f1

 

このページの前提となるもの

パーリンノイズを使って地形の高さを調整するサンプルコードを以下の記事で紹介しています。こちらのコードを使って動的に地形となるオブジェクトを生成します。

 

続いて以下の記事でメッシュの結合を扱いました。複数のメッシュを結合することでCPUのオーバーヘッドが減り、処理がだいぶ軽くなります。

 

このメッシュの結合の際に、対象のメッシュ数が多くなると以下のように意図しない形で結合されてしまうため、これを解消することがこのページの目的です。

結合したはずが不思議な形に……
結合したはずが不思議な形に……

 

沢山のメッシュをCombineMeshesで結合すると変な風になる

割とゆるいタイトルになりましたが、上で紹介した結合前、結合後の画像を見ていただくと、「あ、変な風になってるね」と共感していただけるかと思います。本来であれば、結合前のメッシュと結合後のメッシュは同じ見た目になっていて欲しかったのですが、以下のように一部だけが結合されて、他のオブジェクトは虚無の世界に旅立ちました。

本当はもっとあるはずなのに……
本当はもっとあるはずなのに……

 

これはUnityのメッシュのインデックス数が関係しています。Unityのメッシュでは、デフォルトでは16bit分のインデックスバッファが用意されていて、最大で65535個の頂点データを保持するようになっています。

 

 

試しに50*50のメッシュを結合した時の頂点数を確認してみると、以下のようになっています。

頂点数を表示する
頂点数を表示する

 

上の「meshFilterList.Count」は、結合しようとしているオブジェクトが持っているMeshFilterコンポーネントの数です。50*50で2500個のメッシュが結合の対象になっています。

下の「VertexCount」は頂点数を出力しました。MeshクラスにはvertexCountのフィールドがあり、このフィールドから頂点数を取得することができます。

ここで結合しようとしているメッシュはデフォルトのCubeなので頂点数は8……かと思いきや、60000 / 2500 で1メッシュあたり24個のインデックスを使っています。

面を表現する際には三角形で表し、例えば正方形であれば2つの三角形を作ります。この時、4つの頂点の情報があれば三角形を2つ作ることができます。Cubeは正方形が6つあるので、4頂点*6面で24個の頂点があれば表現できることになります。

 

Cubeの頂点数を確認

念の為Cubeの頂点数を確認してみましょう。

以下のスクリプトをCubeオブジェクトにアタッチしてゲームを実行すると、頂点数と頂点の位置をコンソールに出力してくれます。

Meshクラスのverticesのプロパティでは頂点の位置をVector3の配列として保持しています。この配列を取得し、長さを確認した上で中身をコンソールに出力しています。

出力結果は以下のようになります。Cubeの頂点の数はちゃんと24個でした。2500個のCubeを結合した結果、頂点数が60000になるのは計算通りですね(ご満悦)

頂点はちゃんと24個だった
頂点はちゃんと24個だった

 

インデックスバッファのサイズについて

さてここで本題に戻ると、メッシュを結合した結果、インデックスバッファの最大値である65535個の頂点を超えてしまっていたことで意図した通りにメッシュが結合されないのでした。

対策としては、インデックスバッファのサイズを32bitに変更するのが良さそうです。

MeshクラスのindexFormatフィールドではインデックスバッファのサイズを変更することができます。デフォルトでは符号なし16bitで最大65535の頂点をサポートしていて、符号なし32bitに変更すると約40億の頂点をサポートできるようになります。

バッファのサイズが大きくなると、その分メモリ使用量も大きくなるのでプラットフォームやパフォーマンスと相談ですね。

また、プラットフォームのGPUによっては32bitのインデックスバッファに対応していないことがあるそうで、リリースする端末についても確認しておいた方が良さそうです。Android向けにリリースするなら、結構気をつけておいた方がいいかもしれません。特定の端末だけメッシュが表示されない! という報告が来た場合は再現に困る可能性もありますからね。

ただ、スクリプトリファレンスに記載されているMali-400のGPUは2008年あたりのGPUで、2015年からはMali-470というメジャーアップデートのバージョンが提供されているので、近年のGPUなら大体動きそうな気もします。(太鼓判は押せませんが)

 

インデックスバッファのサイズを変更するサンプル

インデックスバッファのサイズを変更する場合は、MeshクラスのindexFormatのフィールドに値をセットします。

メッシュの結合のサンプルコードについては以下のページで紹介しているものを使います。

このコードを修正してインデックスバッファを変えてみます。といっても、とてもシンプルな変更で、CombineMesh()のメソッド内で最終的にメッシュを生成する時にindexFormatの値を変更します。

 

indexFormatではRendering.IndexFormatのEnumを使用します。インデックスバッファのサイズはUInt16だと符号なし16bit、UInt32だと符号なし32bitになります。

スクリプトを保存してゲームを実行し、結合を実施してみると以下のようにちゃんと想定通りの結合になっています。よしよし。

ちゃんと結合された
ちゃんと結合された

 

まとめ

メッシュ数が多い場合でも無事に結合することができました。バッチ数を増やさなくて済む上に1行追加するだけというお手軽解決。知っていればすぐできる点ですが、知らないとスクリプトリファレンスを彷徨うことになるかもしれません(1敗)

無事に結合できたのであとはマテリアル問題も解決していきましょうかね。こちらもそのうちやりたいと思います。

 

そのうちやりました。

 

パーリンノイズ関係やそれに関連するメッシュ関係の記事は以下のページでまとめています。

     

ゲーム開発の攻略チャートを作りました!

CTA-IMAGE

「ゲームを作ってみたいけど、何から手を付けていいか分からない!」


そんなお悩みをお持ちの方向けに、todoがアプリをリリースした経験を中心に、ゲーム作りの手順や考慮すべき点をまとめたe-bookを作成しました。ゲーム作りはそれ自体がゲームのように楽しいプロセスなので、「攻略チャート」と名付けています。


ゲームを作り始めた時にぶつかる壁である「何をしたら良いのか分からない」という悩みを吹き飛ばしましょう!