Mesh OptimizerでLODモデルの作成

# 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2658418/32b53b50-8a40-0862-619c-f6d6b8304ca9.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

また、オプションでキャラモデルの顔だけはくずれたくない！
という場合は、特定のノードだけや特定のメッシュだけ
最適化させないということもできるので、とっても便利です。

# 結果を動画でどうぞ
<iframe width="560" height="315" src="https://www.youtube.com/embed/clX72p9OwvI" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

Youtubeチャンネルもやってます。
良ければチャンネル登録してください。