Device.Present時間コストの最適化

今回の主な話題:「Device.Present時間コストの最適化」、「PCでアート特殊効果を検査する」、「LuaJitバイトコードのプラットフォーム相関性」、「Shaderを使用して特定色相の変換を実現する」。


パフォーマンス

Q1:Androidモバイルゲームプロジェクトが実機でProfilerを使ってパフォーマンスを分析する時、CPU消費におけるDevice.Presentインターフェイスの割合が非常に高いため、レンダリング時間が長くなり、レンダリング時間の曲線がグリッチ形状を示している。

一部の情報を確認したところ、垂直同期をオンにしたことが原因との声もあった。 このオプションはオフにすると、この現象は少し改善されましたが、まだ存在している。ある人も全画面UIが原因であると認めたが、 このプロジェクトには存在しない。これの原因と対処方法を誰かが知っている人がいるか。 どうもありがとうございました!

問題主が言った二つ共にDevice.Presentの高い時間コストを導く可能性がある。モバイルデバイス自体が垂直同期をオンにするため、実機で垂直同期をオフにしても実際には役に立たない。

このアイテムは2つの理由で高すぎ:

(1)当該フレームのCPUコストが33ms以下の場合、Device.Presentは基本的にハードウェアの垂直同期が原因だ。

(2)当該フレームのCPUコストが33msより高い場合、高すぎるDevice.Presentは、 GPUプレッシャーが高すぎるか、ある子スレッドのCPUコストが大きすぎることを示す。

問題主は自分の具体的な要求次第で確認できる。


制作

Q2:PCでアート特殊効果のパフォーマンスを確認するに良い方法はないの?以前には、不細工な方法を使う経験があった。それはシーンに特殊効果をたくさん入れてフレーム数をチェックすることだったが、効果はあまりよくなかった。

評価式を使っている。つまり、特定のルールに従って特殊効果全体をトラバースし、節点の属性に従ってパフォーマンススコアを計算している。アセットをスキャンしてExcelに出力してチェックできる。 コードを再利用して エディターに直接入力すると、アーティストはこのスコアを参照できる。精度上の要求は必要ではなく、この方法で90%の問題をすぐに発見できる。仕様を事前に指定し、特殊効果シーンと共に、同時に出現する特殊効果の数とパフォーマンススコアが標準を超えるかどうかを推定することもできる。

特殊効果がパフォーマンスに要求高いのはOverDrawの原因だ。PCで確認せず、実機で確認した。

PCでの検査はQian LaiKangさんが提供した、一つのOverDraw監視スクリプトを使った。アーティストは特殊効果を作る時にOverDrawを見ながら、状況に応じて調整できる。

次に、実機パッケージをビルドする時に特殊効果リストを作成し、Major Cityに入り、チャットプログラムでGMコマンドを入力し、特殊効果をトラバースする。各特殊効果は1回から無限回に再生でき、AdvancedFPSCounterプラグインを使って当時の FPS値をファイルに記録される。フレームレートが低い場合の特殊効果を特定し、最適化する。

例えばこのコード:

if (cmd.ToLower().StartsWith("-gm texiaoplay3")) { Toast.ShowTip("アーティストが作った特殊効果をテストします " + cmd); > prEffectTest(3); return true; }
private static void prEffectTest(int efCount)
    {
        if (BilinCamera.Instance == null || BilinCamera.Instance.player == null)
        {
            Debug.LogError("ヒーローは見つからないので、特殊効果はテストできません");
            return;
        }
        TextAsset conf = Resources.Load<TextAsset>("effeclist");
        string[] lines = conf.text.Split('\n');
        //先に一回同期ロードします
        for (int i = 0; i < lines.Length; ++i)
        {
            string prefabName = lines[i];
            if (string.IsNullOrEmpty(prefabName))
            {
                continue;
            }
        }
        BLDebug.isLog2FileEnable = true;//ファイルを書き始めます
        GameManager.Instance.StartCoroutine(prEffectTestAsync(lines, efCount));
        GameManager.Instance.StartCoroutine(logUsedFPS(efCount));
    }


private static IEnumerator logUsedFPS(int efCount)
    {
        float startTime = Time.realtimeSinceStartup;
        float crtTime = Time.realtimeSinceStartup;
        while (crtEffectName.Length > 0 && (crtTime - startTime) < 6000.0f)
        {//100分以内
            crtTime = Time.realtimeSinceStartup;
            CodeStage.AdvancedFPSCounter.AFPSCounter aFPSCounter =
                CodeStage.AdvancedFPSCounter.AFPSCounter.AddToScene();
            BLDebug.Log2File(TimeUtil.currentSqlTimestamp4LongAdv() + "\tlogUsedFPS" + efCount + "\t" + crtEffectName + "\t" + aFPSCounter.fpsCounter.LastValue + "\t" + aFPSCounter.fpsCounter.LastAverageValue + "\t" + Time.frameCount + "\t" + Time.realtimeSinceStartup);
            yield return new WaitForSeconds(0.1f);// 0.1秒ごとにFPSをサンプリングします
        }
        yield return null;

    }
    private static string crtEffectName = "notbegin";
    private static IEnumerator prEffectTestAsync(string[] lines, int efCount)
    {
        float periodTime = 5.0f;
        //特殊効果をを1つずつ再生します
        for (int i = 0; i < lines.Length; ++i)
        {
            string prefabName = lines[i];
            if (string.IsNullOrEmpty(prefabName))
            {
                continue;
            }
            crtEffectName = prefabName;
            GameObject[] effectGoArr = new GameObject[efCount];
            for (int k = 0; k < effectGoArr.Length; k++)
            {
                GameObject effectGo = PrefabUtil.loadPrefabToGameObject(prefabName, parentGo);
                effectGoArr[k] = effectGo;
                if (effectGo != null)
                {//はUI特殊効果
                    GameObject.Destroy(effectGo, periodTime);
                }
                else {
                    Debug.LogError("ロード失敗しました???" + prefabName);
                }
            }
            yield return new WaitForSeconds(periodTime);
        }
        crtEffectName = "";
        BLDebug.UploadTodayLogFile();//その日のログをサーバーに送信します
        yield return null;
    }

ログをExcelにインポートして分析すればいい。

私たちは2つの部分に分けました:

1.特殊効果のリリースが全体的なパフォーマンスへの影響。これは結論に対する統計である。実機でリリース後のフレームレートの変動を統計する。現在のバージョンでは、シーンにフレームレートの記録を入力する機能だけがあり、主な機能は特殊効果がゲームへの最終的な影響を評価することだ。

2.特殊効果のドローコール、面の数、およびOverdraw影響。現在、この部分の統計データはPCでしか取得できないため、スタンドアロンバージョンではスキルの特殊効果を1つずつ再生するツールがある。上記の3つの指標の評価を統計して、標準を超える部分が毎週の会議にレポートで提出する。

個人的な感覚はアセットの検査はツールで定期的にやる必要があり、継続的な観察は最も効果的な最適化方法だ。


ToLua

Q3:ToLuaフレームワークでコンパイルされたLuaJit32とLuajit64を使用してLuaコードをバイトコードに変更するので、すべてのプラットフォームで2セットのバイトコードを入力する必要があるの?それとも、Androidでは32を使ったら大丈夫?PCとiOSは32と64を使用し、実行時に異なるプラットフォームを区別してから、異なる方法でロードするの?実行時にはIntPtr.Size == 4で区別されるの?

iOSでは32 + 64でなければならない、そうしないと32ビットの古いマシンをサポートできない。エディターでは通常64(ただし、私たちが使っているのはLuaファイルを直接読み取る)、PCスタンドアロンでは32を採用する(ユーザー以外の場合は64でもかまわない)、Androidでは 32のみ。

とにかく、対応関係は

x86 arm→ 32

x64 arm64→ 64

IntPtr.Size == 4が信頼できるかどうかについては、詳細なテストはまだやっていない。自分が作ったプラットフォームに対する判断コードを使っている。(実際には、上記の状況に従ったらiOSのみ判断する必要がある)


制作

Q4:画像の特定の選択範囲の色変換を実現したいと考えている。PSでは、 画像選択でエリアを簡単に選択し、元画像の色相を変更できる。ただし、Unityでも似たようなソリューションを実現したが、実際の効果はPSとかなり異なる。全体が暗くなり、個々の領域が露出オーバーになった。誰かが似たようなShaderソリューションがあったら、参照させていただけませんか。

Color Lookup Tableソリューションを使用することをお勧め。 簡単に言うと、一つのテクスチャをルックアップテーブルとして使う。このテクスチャに、各色が変更された後の 色を記録する。この方法は、問題主によって述べられた要求を満たすだけでなく、多くの色補正要求(さまざまなフィルター)も達成できる。

cocos2d時代に使ったから、パフォーマンス上には納得できることがわかった、大きな問題は存在しない。AssetStoreにLUTをキーワードとしてのプラグインリを検索して参考になれる。

Color Lookup Tableの作成とカスタマイズの方法については、説明するのは複雑なので、Googleするとお勧め:P。一つの簡単な方法は、Color Lookup Tableには一つの固定された標準色の元画像がある。この画像をPhotoShopにドラッグし、変更したい色相で変更したら、 変更後の各色相を保存できる。

Unity自身のColorCorrectionLookupを参照することもできるが、これは3Dテクスチャなので、2Dに適用するように変更する必要がある。https://docs.unity3d.com/550/Documentation/Manual/script-ColorCorrectionLookup.html

他のソリューション(Shaderに計算コードを直接作成するなど)については、できないではないが、パフォーマンスはあまり高くない。

理論的には、問題主が実現した色変更アルゴリズムはCPUでもGPUでも、アルゴリズムとコードが正しいなら、画像の色変更結果はPSと同じすべきだ。

問題主が言った「全体が暗くなり、個々の領域が露出オーバーになった。」ということは、ゲーム内にライトがある場合に見たことか?それとも、変更された画像を保存して比較された結果なの?ゲーム内の場合は、個別に染色された画像に問題があるかどうかを確認してください。ある可能性はアーティストがdiffuse画像にaoなどの別の情報を書いて、後でシェーディングした画像に問題を導く。ただ保存した画像に問題がある場合には、アルゴリズムを検索し、目的と比較してください、理論的には問題はないはずだ。染色効果を実現したい場合は、 一つのUnityプラグインをお勧め、色々なアルゴリズムが含んである:Texture Adjustments


管理

Q5:ベイクする時にはnormalとtangentをインポートする必要があるが、ベイクが完全した後にはインポートする必要はなくなって、メッシュアセットを最適化できる。しかし、アーティストが再びベイクしたい時には、もう一度開いてnormalとtangentの設定をインポートするのは非常に面倒だ。これに対して適切な処理法がないのか?私が考えられるのはパッケージする時にこのアセットたちを再インポートし、normalとtangentをインポートするオプションを削除して、パッケージして元に戻ることだけだ。

Player SettingのOther Settingsにある「Optimize Mesh Data」オプションをオンにしてみてください。オンにすると、エンジンはリリースまたはAssetBundleパッケージング中にすべてのメッシュデータをトラバースして、過剰なデータを削除する。注意すべきのは、こちらの「過剰な」データとは、レンダリング時にシェーダーが要らないデータはメッシュデータに含まれていること。つまり、メッシュデータにposition、uv、normal、color、tangentなどの頂点データが含まれているが、レンダリングに使わせるシェーダーが必要なのはposition、uv、normal三つだけ。colorとtangentは「過剰な」データとして、エンジンはゲームをリリースするまたはAssetBundleを作成する時、これらのデータを自動的に削除する。ただし、Runtime状況にシェーダーを置換する必要のあるメッシュに、開発チームに特に注意すると提案する。もしRuntimeの時、あるGameObjectにより複雑や多くの頂点属性にアクセスする必要のあるシェーダーを置き換える必要がある場合、実行時に頂点属性のmissingで問題を起こさないために、先にこれらのシェーダーを対応するプレハブにマウントしてリリースすると提案する。


UWA公式サイト:https://jp.uwa4d.com

UWA公式ブログ:https://blog.jp.uwa4d.com

UWA公式Q&Aコミュニティ(中国語注意)https://answer.uwa4d.com