Mesh OptimizerでLODモデルの作成
モデルの高精細化は、最近のゲームでは加速する一方です。
これらの負荷を軽減する為にLODモデルを自動で生成する
MeshOptimizerの使い方を説明してみたいと思います。
LOD(Level Of Detail)とMesh Optimizerとは
いくら高精細なモデルでもカメラから遠く離れれば、
細かな部分は見えなくなります。
それを利用して、高精細なモデルからいくつかの頂点を使わずに
モデルの見た目を保ったインデックス情報を利用して
描画を行うことで、描画負荷を下げつつ
たくさんのモデルを描画することができるようになります。
その頂点を端折ったindex情報を作ってくれるのがMesh Optimizerです。
MeshOptimizerにはLOD作成以外にも様々な機能がありますが
ここではLOD作成のみをまず説明したいと思います。
環境の準備
https://github.com/zeux/meshoptimizer
こちらからgit cloneします。
cmakeを使用して、slnを作成してからビルドすることで
meshoptimizer.libを作成します。
プロジェクトからmeshoptimizer.libをリンクして、
meshoptimizer/src/meshoptimizer.hをインクルードしたら
処理の準備は完了です。
MeshOptimizerがやってくれること
Mesh Optimizerが生成するLODの原理としては、
頂点情報を新たに作成し直すといった事ではなく、
現状の頂点をそのままにして、index情報だけを新たに作成して
メッシュとして見た目を保つようにしてくれます。
つまり、vertex bufferはそのままシェーダーに渡し、
index bufferだけ生成した物を渡すことで、LODモデルを
描画できるということです。
index生成の準備
生成するためにはいくつか情報を用意する必要があります。
頂点情報構造体
モデルデータをロードした際、頂点にまつわる情報として
- 頂点
- 法線
- UV
- ウェイト
- ボーンインデックス
などなどがあるかと思います。
mesh optimizerではこれらを頂点構造体として扱います。
なので、これらの情報が入る構造体を用意する必要があります。
頂点、法線などをそれぞれ配列として持っているようであれば
この様な頂点情報構造体を作成して配列にデータをセットし直す必要があります。
すでにこの様な構造体のデータの配列にモデルをロードしているのであれば
そのままで問題ありません。
struct sOptVertData{
Vector3 position;
Vector3 normal;
Vector2 texcoord;
};
// こちらの配列にデータをセットし直す。
std::vector<sOptVertData> OptVertDatas;
頂点インデックス
こちらは普通に頂点インデックスの数値が入った
配列であればOKです。
std::vector<unsigned int> Indices;
remap情報の作成
頂点情報構造体の配列(OptVerDatas)と、頂点配列(Indices)の準備ができたら
LOD生成に使用するremap情報を作成します。
これらの情報がLODの前準備のようです。
他の頂点最適化処理などにも使用するので、
これらは必須のようです。
std::vector<unsigned int> remap(Indices.size());
// remapしたインデックス
std::vector<unsigned int> remappedIndices(Indices.size());
// remapした頂点情報配列
std::vector<sOptVertData> remappedVertices(OptVertDatas.size());
const size_t vertexCount = meshopt_generateVertexRemap(
remap.data(),
Indices.data(),
Indices.size(),
OptVertDatas.data(),
OptVertDatas.size(),
sizeof(sOptVertData));
meshopt_remapIndexBuffer(remappedIndices.data(),
Indices.data(), Indices.size(), remap.data());
meshopt_remapVertexBuffer(remappedVertices.data(),
OptVertDatas.data(), OptVertDatas.size(),
sizeof(sOptVertData), remap.data());
LODインデックスの作成
ここまで準備できたら、お待ちかねのLODを作成します。
const float target_error = 1e-2f;
// ここにLODのindexを格納
std::vector<unsigned int> IndicesLod(Indices.size());
// これが削減する度合い。
// ここでは0.5fなので半分のインデックス数を使ったモデルが作成されます。
const size_t target_index_count = size_t((float)remappedIndices.size() * 0.5f);
IndicesLod.resize(meshopt_simplify(&IndicesLod[0],
remappedIndices.data(),
remappedIndices.size(),
&remappedVertices[0].position.x,
vertexCount,
sizeof(sOptVertData),
target_index_count1,
target_error));
出来上がったデータで描画
注意したいのが、インデックス配列はIndicesLodを使うのはわかると思いますが、
頂点バッファにも、remappedVerticesを使う必要があります。
つまり元のデータを描画したいなら、
remappedVerticesとremappedIndicesを使って描画すれば
頂点削減する前のモデルが描画できます。
remappedVerticesとIndicesLodを使って描画すれば
インデックス数を半分にしたモデルが描画されます。
gltfpackを使った変換
ここまで作業しておいて最後に出さなくても・・となりそうですが、
事前に変換したデータとして用意するならば、わざわざコードを書かなくても
meshoptimizerのリポジトリに含まれているgltfpackというコマンドライン
ツールを使えば、LODで削減したgltfモデルを作成することも可能です。
gltfpack -i scene.gltf -o scene.glb -si 0.5
また、オプションでキャラモデルの顔だけはくずれたくない!
という場合は、特定のノードだけや特定のメッシュだけ
最適化させないということもできるので、とっても便利です。
結果を動画でどうぞ
Youtubeチャンネルもやってます。
良ければチャンネル登録してください。