ボーンキャラメイクの実現方法

今回の主な話題：プロジェクトのボーン仕様が決定した場合にボーンキャラメイク機能を添付する方法、メッシュの再構築をロケートする、ScriptableObject読み込みスピードの分析、ライトマップとマテリアルの組み合わせが追加のDrawCallを引き起こす。

---
###ボーンアニメーション
Q1: プロジェクトのボーン仕様が決まっていて、Character Studioとボーンの総数は60本であります。現在、キャラメイクの方法を調べています。基本的に、面部のボーンを加えてキャラメイクを行うことが決定しました。まず、面部のボーンについての私の理解を説明します。誤りがあれば指摘してください。

追加されたボーンと元のボーンは、同時にエクスポートされてAvatarを作成してボーンアニメーションに使用されますが、ボーンアニメーションはこれらのボーンを制御しません。プレイヤーがキャラメイク後のデータ、つまりボーンのスケーリングと回転は、これらの面部のボーンに直接適用され、面部のカスタマイズが完了します。実際にプロジェクトに適用されると、次の問題があります。

1、既存のプロジェクトでは、インポート時に「Optimize Game Object」オプションがオンになっているため、C#レイヤーでボーン情報を取得できません。Exposeしたボーンは「読み取り専用」であり、キャラメイクデータを実行中のボーンに直接適用する方法はありません。現時点では、「最適化されていない追加のAvatarを使用してターゲットMeshを動的に生成する」方法のみで避けられます。他のより直接的な方法ありませんか？
2、既存のボーン仕様には面部のボーンがありません。追加したい場合は、以前のすべてのアニメーションを再エクスポートする必要があります。

何か既存のボーンを変更しない方法はありますか？

①
>頭と体を分離します。ただし、縫い目がバレしちゃう可能性があります。

>さらに、もう１つの方法を考えしました。「頭のために特別にGPU Skinを作る」ことです。モデルをインポートするときに、前処理を行い、メッシュのuv2とuv3に頭のボーンとウェイトを追加し（コードで頭のボーンであるかどうかを区別し、体の部分を全部０にすればいい）、assetとして名前を付けて保存します。そして、フレームごとにボーン行列をShaderに入力し、vsの中で頂点の座標を変更します。ただし、これは追加の帯域幅を使用します。

②
>前者に接続し、頭と体を分離し、QualitySettings→Blend Weightsを４に変更したら、大部分の継ぎ目のバレ問題を解決できます。実際、現在のゲーム内には、ほとんどの髪の毛にもアニメーションが含まれているため、髪の毛を分離する必要もあります。キャラメイク後のMeshを保存し、ゲーム内に体と接続するときに、保存されたMeshを使用します（ほとんどのゲーム内の顔には動きはありません）。実際のゲームでは、キャラメイクときのボーンは必要ではありません。

③
>オフラインでボーンをサンプリングする時に「Optimize Game Object」をチェックする必要はありません。顔が統合されたら、顔の形を貼り付け戻します。 または、面部のボーンだけを読み書きできると制御します。

---
###UI
Q2: Profilerで、具体的にどのメッシュが再構築されているかを確認できますか？現時点では、VB Uploadsのサイズのみでメッシュ再構築が引き起こされるかどうかを判断できますが、インターフェイスのどの要素がメッシュ再構築を引き起こすのはわかりません。

ここに１つのテストケースがあります。imageを動的canvasに入れることと入れないことを比べて、アップロードされる描画コンテンツは大幅に違いますが、具体的にどちらのものを調べるためにすべてをテストするのは、非常に時間がかかると感じます。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/ff2e9b73-5db3-33db-dae9-dc6d22f2f492.png)
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/70bd1c8a-80c6-8a66-7a7c-b8acd2030dca.png)

>VB Uploadsで再構築を判断するのは直感的ではないかもしれません。SkinnedMeshとパーティクルシステムもこの変化を引き起こせはずです。

>UGUIでは、メッシュの再構築はRebuildとRebatch二つの部分に分けます。その中に、RebuildはUI要素を単位とします。例えば、一枚のImageのColorを変更したら、このImageはRebuild操作を行い、コストはCanvas.SendWillRenderCanvasesに含まれ、UI要素の頂点属性を変更する操作は既にSendWillのコストを引き起こします。具体的にどのUI要素が再構築されたかについては、プロファイラーからは確かに見えません。

>一方、RebatchはCanvasを単位とします。Canvasの任意UI要素が変更したら（位置の変化も含む）、このCanvasはRebatchを行い、コストはCanvas.BuildBatchと子関数のCanvas.SortJob、Canvas.GeometryJobなどの関数にあります。どちらのCanvasにRebatchが発生したかは、Canvas.BuildBatch関数で判別できます。ProfilerでCanvas.BuildBatchを選択し、右側に関するオブジェクトが表示します。リストにあるのは当該フレームでRebatchが発生するCanvasの名前ですが、このRebatchはどちらのUI要素の変更が引き起こしたのは、Profilerでは見えません。

---
###レンダリング
Q3: Unity内のライトマップ割り当て方法にいくつかの問題があることがわかりました。同じマテリアルの多くのオブジェクトは、ライトマップのせいで異なるAtlasに割り当てされましたから、バッチ処理を行えません。

たとえば、次の3つの連続するDrawCallの場合、実際のMeshRendererマテリアルはまったく同じですが、ライトマップが複数の異なるAtlas上にあるため、それらをバッチ処理することができません。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/89c0fcc9-5daf-72c4-781d-e3f98b2bbb35.png)
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/25834dfd-a239-30f7-18ff-a041c0d52274.png)
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/e08f90b4-72b4-1cbd-00bd-9db4dc018982.png)
観察後、この状況のオブジェクトをバッチ処理できなら、シーンのDraw Callコストは1/3以上削減される可能性があります。

したがって、ライトマップの割り当てプロセスを最適化して、同じマテリアルのオブジェクトができるだけ同じAtlasに割り当てられるようにする方法があることを願っています。

>二つの方法があります。

>1）LightmapParametersをカスタマイズし、Bake tagを設定することにより、同じtagを持つオブジェクトが同じLightmapにベイクされます。Terrain_1_1は、新しく作成されたLightmapParametersであり、デフォルトのPamametersを置き換われます。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/c3f60506-12b5-6e2e-4378-72dab9c59047.png)
>2）Rendererのuvを介して、完全なLightmapテクスチャから対応するテクスチャを取得し、これらのテクスチャを合併して新しいLightmapテクスチャを作成します（tinyexrをstbライブラリと組み合わせてexrの読み取りと書き込みを行うことができます）。

---
###メモリ管理
Q4: プロジェクトを最適化していたときに、プロジェクトを最適化していたときに、キャラクターモデルを削除しても、そのマテリアル、メッシュ、Texture2Dがメモリに残っていることがわかりました。 使用される削除方法はDestroyImmediateです。Texture2Dの引用を検索したら、結果は下図のように、
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/06e8555d-4804-b286-3dc4-52556a2a942a.png)
インスタンス化されたMaterialによって引用されていることがわかりました。さらに検索した後、
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/ff39759a-70e7-3fe9-a558-8894c3719828.png)
なぜこれほど多くの引用項目があるのですか？ それとも私は間違った方法で検索していますか？ メモリのこの部分を解放する方法は？ （キャラクターモデルの削除が確認されています。）
>マテリアルとテクスチャをすぐにリリースする場合は、手動でDestroyする必要ながあります。 それ以外に、他の引用がない場合、Resources.UnloadUnusedAssetsをコールしなければなりません。しかし、Resources.UnloadUnusedAssetsがジャムを引き起こす可能性があるため、手動でマテリアルとテクスチャを管理することをお勧めします。

---
###シリアル化
Q5: ScriptableObjectのローディングスピードは、プラグインMessagePack、または一般的なJsonプラグインの逆シリアル化速度と比較したら、どのレベルですか？自分でテストした結果、Unityが「C#で直接シリアル化する」、「テキストをロードしてParseする」、「MessagePackプラグインを使用する」、三つ方法で同じ散々なファイルをロードする時間は大体10:4:3です。

しかし、最速のMessagePackプラグインの読み取り時間でもあまり要求に満たされません。したがって、ScriptableObjectまたはSqliteがMessagePackよりも高速に配置ファイルをロードできますかを知りたいです。

>実際に、Sqliteでデータをロードするのは最速ですが、キーはデータ構造にあります。リフレクションで逆シリアル化を行えますが、MessagePackほど効率的ではありません。個人的には、デザインから問題を解決し、時間をスペースに換え、事前にデータを分析し、メモリを占有し、使用する時に直接にメモリデータを読み取る方が合理的だと思います。

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

UWA公式サイト：https://jp.uwa4d.com
UWA公式ブログ：https://blog.jp.uwa4d.com