LoginSignup
2
1

More than 1 year has passed since last update.

Mesh OptimizerでLODモデルの作成

Last updated at Posted at 2022-05-30

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モデルを
描画できるということです。

lod_dragon.png

index生成の準備

生成するためにはいくつか情報を用意する必要があります。

頂点情報構造体

モデルデータをロードした際、頂点にまつわる情報として

  • 頂点
  • 法線
  • UV
  • ウェイト
  • ボーンインデックス

などなどがあるかと思います。
mesh optimizerではこれらを頂点構造体として扱います。
なので、これらの情報が入る構造体を用意する必要があります。
頂点、法線などをそれぞれ配列として持っているようであれば
この様な頂点情報構造体を作成して配列にデータをセットし直す必要があります。
すでにこの様な構造体のデータの配列にモデルをロードしているのであれば
そのままで問題ありません。

mesh_optimize.cpp
struct sOptVertData{
  Vector3 position;
  Vector3 normal;
  Vector2 texcoord;
};

// こちらの配列にデータをセットし直す。
std::vector<sOptVertData>  OptVertDatas;

頂点インデックス

こちらは普通に頂点インデックスの数値が入った
配列であればOKです。

mesh_optimize.cpp
std::vector<unsigned int>   Indices;

remap情報の作成

頂点情報構造体の配列(OptVerDatas)と、頂点配列(Indices)の準備ができたら
LOD生成に使用するremap情報を作成します。
これらの情報がLODの前準備のようです。
他の頂点最適化処理などにも使用するので、
これらは必須のようです。

mesh_optimize.cpp
   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を作成します。

mesh_optimize.cpp
    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チャンネルもやってます。
良ければチャンネル登録してください。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1