LuaとC＃間のコール問題

今回の主な話題：Luaコール回数統計、Unity人形の手首の反転アニメーションの具体的な制作方法、Find（）で要素を検索するときに生成されるGC、パーティクルシステムの最適化、アニメーションクリップの数がパフォーマンスコストへの影響。

---
###スクリプト
Q1: LuaとC＃間のコール頻度はLuaのパフォーマンスに影響を与える非常に重要な要素ですが、最近、私たちのプロジェクトでもこのような問題を分析しています。LuaとC＃間のコール回数を数える方法は？挿入すべきコードの量がたくさんあると感じしています。warpコードをたくさん作成しましたが、それぞれ追加するのは少し非現実的で、warpコードは自動的に生成されます。 何か良い提案はありますか？
私たちのプロジェクトはToLuaを使用しており、Unityのバージョンは5.6.1p1です。

①
>Luaはdebug.sethookでC＃をコールします。
Yunfengさんがこの方法に基づいて一つのツールcloudwu / luaprofilerを作成しましたが、直接使用することはできず、自分でライブラリをコンパイルする必要があります。
これ以外、Luaで直接ハングすることも可能ですが、パフォーマンスに影響があります。
C＃はLuaFunctionでLuaをコールします。

②
>LuaとC＃間のコール回数をカウントするためのツールは、ToLuaがwarpインターフェースをエクスポートする場所を変更し、コードを再生成し、マクロまたは条件付きコンパイルのコントロールを追加するだけで、簡単にエディターモードで回数統計を行いながら、デバイスへの公開プロセスに影響を与えないこともできます。
統計のために生成されたコードにタグを追加して、例えば私たちプロジェクトの一つのコールコード：

>```ruby:code
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int GetBattery(IntPtr L)
    {
        try
        {
            ToLua.CheckArgsCount(L, 0);
#if STATISTICS_LUACALLOUNT
            CallMethodInfo cpf = new CallMethodInfo();
            cpf.FuncName = "UnityEngine.HardwareInformation.GetBattery";
            if(CallCountPerFrameDataManager._instance!=null)
                CallCountPerFrameDataManager._instance.AddNewMethodNameDic(cpf);
#endif
            int o = UnityEngine.HardwareInformation.GetBattery();
            LuaDLL.lua_pushinteger(L, o);
            return 1;
        }
        catch(Exception e)
        {
            return LuaDLL.toluaL_exception(L, e);
        }
    }
```
UWAがUWA DAYで紹介した方法はより簡単で、関数に直接カプセル化され、[Conditional（ “XXX”）]によって制御されます。
C＃がLuaを逆にコールする場所を数えたい場合、LuaFunctionのいくつかのCall関数がキーポイントです。

---
###アニメーション
Q2: アニメーションを再利用し、アセットの重複問題を軽減するために、ヒューマノイドアニメーション形式を使用して制作しています。一人称視点があるため、手のひらと手首は常に見えます、何か欠点があればすぐバレでしまいます。手首の歪みを避けるや問題を解決するために、ボーンを追加しました。ボーンの位置と構造は次のとおりです。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/fbab0b3b-3c02-6f6f-f32e-0303d2d8001d.png)
Unityに追加ボーンの手首回転効果を添付していません場合：
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/093f022b-7259-7167-64ed-ce0a214ff4b9.png)
Unityに前腕追加ボーンの手首回転効果を添付しています場合：

問題１：手首と腕の接合部に転位があります
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/24abb6de-3559-9d2d-3726-cc39418c4955.png)
Maxに前腕追加ボーンの手首回転効果を添付しています場合（手首の歪みは完全に解決されていることがわかりますが、Unityでの効果は理想的ではありません）。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/59fe4bb3-2704-595b-6f38-9a40887fb5e8.png)
問題２：ヒューマノイドアニメーション形式の親指動画は再現できません、いつも伸ばしています。ボーンマッチング問題を確認しましたが、下図のように正常であります、親指のみ曲げることはできません。
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/4ce23032-89e1-fe94-6dbb-df2232f723b6.png)
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/55fa03b9-8c93-e1ba-19ec-e143213769df.png)

>Unityのヒューマノイドボーンをここで使用すると、小さな問題がたくさん存在しています。公式ドキュメントも多くの場所が非常に不明瞭です。私たちのプロジェクトはヒューマノイドボーンを使用しており、Retargetingを使用する際のさまざまなアクションに小さな問題があります。

>先に私たちが得たいくつかの結論を教えます。

>●キャラクター自身の動画に自分のAvatarを使用したら、ヒューマノイドボーンはMaxおよびGenericのアニメーションとまったく同じ効果を達成できるはずです。
●他のボーンの場合、高さ、肩の幅などのボディシェイプに違いがあります。Retargetingエフェクトの後、全体的な効果は正しくなりますが、累積された誤差により、足や指などのルートボーンから遠く離れたボーンは小さな差がある可能性があります。例えば、足と地面の接触距離が足りない、足のidle動きにわずかな揺れがあるなど。

>Retargetingの原理について私が[「アニメーションリダイレクト技術の分析とUnityでの応用」（中国語注意）](https://blog.uwa4d.com/archives/AnimationRetargeting.html)と言う記事が書いてありましたが、興味があればご覧ください。ここのその後の自分が使用した時に発見したいくつかのポイントを述べます。

>●Avatarファイル、いわゆるボーンファイルは、Retargetingに使用される基本的なT-pos情報を記録されています。Unityに定義されたのは一つの大きなTポーズですが、Maxで一般的に使用されるのは小さなTポーズであります。だからこちらに少し調整の必要があります。詳細は後で話します。
●アニメーションファイル、アニメーションの差分情報を記録します。インポートされたアニメーションファイルがヒューマノイドボーンを使用する場合、対応するファイルへのAvatarを使用する必要があります。Avatarが変更された場合、アニメーションファイルをUpdateする必要があり、これは、差異を再計算するためのプロセスです。このアニメーションファイルの内容は、アニメーションファイルにインポートされたMaxファイルのT-posと強い相関があることがわかりました。

>前の記事を読んだ方がわかりやすいでしょう。 AとBの2つのスケルトンセットがあります。BのアクションをAに適用する必要があります。これには4つのデータが必要です。

>●AのT-pos情報。
●B のT-pos情報。
●Bのアニメ情報。
●Aのアニメ情報＝Bのアニメ情報- B のT-pos情報+ AのT-pos情報。

>ですから、上記のアニメーションファイルの情報は、実際にはBのアニメーションファイルとBのT-posとの違いとして理解されるべきです。

>**だから我がプロジェクトがアーティストに伝えた提案は、AvatarをエクスポートするT-posとアニメーションをエクスポートするMaxファイルにあるT-pos情報は出来る限り一貫にします。そうすればアクションを可能な限り復元できます。**（同じMaxファイルを使用してエクスポートされたアニメーションとAvatarは、UnityとMaxで同じかどうかをテストすることをお勧めします。）

>原理についてたくさん話しました。つまり上記の問題のいくつかは、問題主自分が原理に基づいてテストして解決する必要はあります。

>もちろん、これら2つの問題に対しての私の考えも述べます。

>一つ目の「手首と腕接合部の転位」問題は、正直私も遭いましたはずですが、遠く昔の話で、具体的なディテールもう忘れてしまいました。下記の方面を確認することを提案します。

>●非ヒューマノイドのボーンにもこの問題がありますか？
●スキンの問題であり可能性はありませんか？それともスキンを修正することで解決できますか？
●ヒューマノイドボーンAvatarの手首のボーンマッピング関係に問題はありますか？Avatarの姿勢を調整することでこの問題を緩和することは可能ですか？

>二つ目の「指が動かない」問題は、Avatarでの指の姿勢が予想と異なるかどうかを確認する必要があるかもしれません。この状況では、動作中の指に一つの非常に誇張した姿勢を与えてテストできます。完全に行動がない場合、動作のmaskの設定に何か遺漏があるかどうかを確認することを提案します。
アクション方面はエンジニアとアーティストの協力でテストし続き、一つのバランス点を見つけることは必要です。私自身ではMaxによく知らないので、問題を見るときはアーティストと一緒にする必要があります。

---
###アニメーション
Q3: アニメーションクリップの数はパフォーマンスのコストに影響しますか？ もしそうなら、同じアニメーションクリップに対して、異なるレベルでの多重化再利用も影響がありますか？（アニメーションコントローラゲーム中はアクティブ化と隠す操作はありませんが、なぜアニメーションクリップデータのサンプリングと読み取りのコストが高い理由は明らかではありません。）

①
>簡単に言えば、アニメーションはただ移動、回転、スケーリングなど簡単な変換であります。各フレームは各頂点に変換行列を乗算し、この部分は純粋なCPU計算です。
じゃあ、パフォーマンスコストに関するのは頂点の数、ボーンの数、レ​​イヤーの数に集めています。

>もう1つのはBoneWeightで、0以外のweight（n）の数が多いほど、各フレーム頂点の変換計算が多くなります。

>では、最適化できるポイントは：

>●面を減らすこと；
●骨を減らすこと；
●層を減らすこと；
●骨格blendの量を減らすこと；

>また、アニメーションのメモリ占有の最適化に関して、この記事[「Unity動画ファイル最適化について」](https://qiita.com/UWATechnology/items/d47a490acff36bb90260)を参照できます。

②
>process animation以外に、アニメーションクリップの数もanimatorcontorllerのメンテナンスにコストを導きます。（writejobに反映されます）。"When using the Animator, all the properties of all the clips currently connected are written, whether the clips are playing or not. "

---
###パーティクルシステム
Q4: シーンに沢山の環境パーティクルシステムがあり、パーティクルシステムに裁断最適化を行いましたが、カメラから遠く離れているパーティクルシステムはどうやって正確にスリープできますか？パーティクルシステムをstopにすればいいですか？レンダリングやCPUに余分なコストを与えませんか？または、パーティクルシステムのルートノードのgameobjectのactiveをFalseに設定しなければならないですか？

>ActiveとDeactiveで制御することをお勧めします。DeactiveしていないParticle Systemも、レンダリングモジュールのCullingなどの計算に参加します。同時に、パーティクルシステムに対して、Active/Deactiveの処理は一般的に時間がかかりません。

---
###コード
Q5: Deep Profileでコードパフォーマンスをチェックしますとき、一つのリスト検索Find（）は毎回行うと2.5KB GC Allocを生成しますことを発見しました。そして書き方を変更し、Find()を使わずにforループを直接たどって、GC Allocがなくなっていることを発見しました。時間コストもほぼ同じです。その後、Find（）のILを探しに行きましたが、GCがどこに生成されるのか理解していませんでした。ぜひ、教えてください！
>
```ruby:code
Profiler.BeginSample("===========Find2======");
for (int i = 0; i < 2000; ++i)
{
    result = lst.Find(t => t != null && t.Num < 200);
}
Profiler.EndSample();
```
>![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/2d5f76e2-732c-3507-d321-8f137b3377be.png)
>
```ruby:code
Profiler.BeginSample("===========Find1======");
for (int i = 0; i < 2000; ++i)
{
    result = lst.Find(t => t != null && t.Num < i);
}
Profiler.EndSample();
```
>![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/d14d2a0a-16ca-63db-2ce0-946bd6c7347b.png)
問題主のコードは次のロジックに似ていると推測します。一つのループにFind操作が複数回実行され、Findのパラメーター（クロージャー関数含む）は、毎回ループごとに変化するnon-local変数を使用するため、より多くのGCが生成されました。この原因は、毎回ループすると一つの委託対象をnewしますことです。


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

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