リソースファイルが多すぎでインストールが遅くなさせる状況を最適化するにどうすれば良いですか？

今回の主な話題：Androidプラットフォームでのリソースファイルが多すぎでインストールが遅くなさせる状況の最適化方法、ShaderVariantsをパッケージするとAssetBundleが失う、Graphcis APIがAutoに切り替えると特殊効果が異常になる。

---
###アセット管理
Q1: 私たちのプロジェクトには16,000を超えるリソースファイルがあり、Android設備でのインストールが遅くなります。そのために、他のゲームを分析したところ、すべてのファイルが1つの大きなファイルに統合され、それが読み取られることがわかりました。

マネして簡単なファイル統合を行って、すべてのファイルを一つの大きいファイルに書き込んでから、記録させたPosとLengthを介して、Seekがデータを読み取り、結構重いと感じました。このような多すぎるファイルに対して、何か良い解決策はありませんか？

①
>大部分のリソースはBundleに対応しています、大きなファイルにパッケージし、ヘッダーと長さを記録するのは大丈夫ですけど、いくつ注意すべき点があります。
１、重複コストを回避するために、ハンドルを離さないでください。
２、Unityは、AssetBundleを読み取るときにoffsetのインターフェイスを提供し、使用できます。public static AssetBundle LoadFromFile(string path, uint crc, ulong offset)。
３、自己管理型ヘッダーファイルを事前に読み取り、マッピング関係を確立します。
４、マルチファイルヘッダーマッピングをサポートします。これにより、パッケージを分けてiOの速度を上げることができます。


②
>問題主が言ったインストールプロセスが非常に遅いことは、apkのインストールプロセスでありますか？それとも、インストール後にゲームを初めて起動する時のリソースのコピープロセスでありますか？

>私たちのゲームプロジェクトのAssetBundleファイルの数は１万を越えまして、大体同じ数級です。一部のAndroidデバイスでのapkのインストール時間は確かに速くありませんが、テスト時に時間に影響を与えるだけなので、プレーヤーエクスペリエンスの統計には含まれていません。そのため、どのくらいの時間がかかるかわかりません。apkをインストールした後、解凍することを選択しなかったため、この方面で時間のコストはありません。
一つの大きなファイルへの統合はよく見える方法ですが、個人的な感覚には、C++最下層でこの仕様を直接するのは一番いいです。１セットの仮想ファイルシステムを作るみたいです。C#層でこの仕様をするのは少し面倒くさいで、意味のないメモリ占用とCPUコストをたくさん引き起こすかもしれません。一方で、問題主はProfilerでこの部分の具体的なコストはどこにありますかを確認できし、改善の余地があるかもしれません。
大きなファイルを使用したいが、offsetに基づいての読み取りは最適化スペースを見つかれない場合は、それを1つまたは複数の大きなファイルに圧縮してから、初めて実行するときに解凍することもできますが、少し長いインストール時間の方がより良いと感じます。

>さらに、問題主は現在のパッケージのサイズを考慮し、できる限りにいくつ合理的な合併をし、Patchのサイズをコントロールながら、AssetBundleの数を減少できます。パッケージサイズが１GBの場合、ファイルが２万あると、平均値は50KBであり、非常に小さいです。AssetBundleファイル数を１万に合併しても、平均値は100KBで、納得できます。
もちろん、そのような質問を提起した問題主のおかげで、apkのインストール時間とファイル数の間に関係があるかもしれないことに気づきました。ありがとうございます。

---
###Shader
Q2: 私たちのプロジェクトのShaderにShader_featureを使用して、複数の変体を生成します。 エディターモードではすべて正常に動作しますが、Androidにパッケージ化すると、Shaderが失う状況は出現します。その原因は、AssetBundleがパッケージ化されたときに、Shaderの名前を独立に設定したためと推測します。モデルが収集したとき、Materialにマクロ設定があるかどうかを考慮しなかったで、Shader自体は引用されましたけど、モデルが使ったのは変体ですから失いを引き起こします。

他の方法も試しました、例えば、ShaderをAlways Included Shadersに入れること、そうすると、コンパイルが長くなり、パッケージサイズも大きくなります。Shaderのすべての変体が一度独立してコンパイルされ、apkパッケージに入れられるため、確かに問題を解決できます。次に、ShaderVariantCollectionも使用してみました、バージョンはUnity 5.6.4p4です。効果はないと感じました。Collectionに必要なパラメータを設定し、Shaderと一緒にパッケージしましたが、実機でテストするとShaderは依然として失われます。全く効果なかったと感じました。

最後に一つ方法を試しました。自分がこの変体ShaderのMaterialを全部収集し、すべてのMaterialとShaderを一つのAssetBundleにパッケージします。これで失われなくなりますが、別の問題が発生します。一つのMaterialはKeywordを設定する必要がある場合は、新しい変体Shaderが必要です。現在のパッケージがこのShaderが生成されていない場合、同じように失われます。

何か良い解決策ありませんか？

①
>ShaderVariantCollectionに関して、簡単に言えば、上位バージョン（Unity 2018）では正常に使用できます。他の場合にはDummy Materialを使用することの方がより安定です。

>「一つのMaterialはKeywordを設定する必要がある場合は、新しい変体Shaderが必要です。現在のパッケージがこのShaderが生成されていない場合、同じように失われます。」という問題については、良い方法がないようです。 この方法の欠点は、維持が面倒なことです。

②
>ShaderVariantCollectionは自動的に収集するだけでなく、Keywordを手動で設定することもできるため、最初にすべての変体とアーティストが使用するすべての変体を確認する必要があります。後で問題を回避するために、アーティストが使用する前に制限することをお勧めします。更に、Shaderコードの分離、Lightmap関連Shaderとキャラクターの分離、LightなしとLightあるの分離、一つLightと二つLightの分離を行って、できる限りにコードの種類を減少させ、アーティストが使用できるShaderの範囲を制限します。

---
###OpenGL
Q3: 以前のプロジェクトでは、Android Graphics APIは常に強制OpenGL ES 2.0を選択しました。最近、最適化したいの上、「Auto」を選択しました。切り替えると、ほとんどのスマホで正常に動作できし、テスト後のパフォーマンスも若干向上しましたが、現在、妙な現象があります。
1）OPPO R9、MEIZU m2e 2台のスマホでは、特殊効果には粒子のフラッシュ、拡大、消失などのさまざまな妙な現象があり、特殊効果のみに問題があり、他の画面は正常です。
2）同じリソースの場合、以前の強制OpenGL ES 2.0に戻したら正常になります。
3）うまくいかない特殊効果は、すべてUIに配置された特殊効果です。例えば、UIパネルの特殊効果や、RenderTextureによって実現されたUIモデルに関連付けられた特殊効果など、同じモデルの特殊効果は、シーンで正常に再生できます。

Unity 5.3.6バージョンを使用しています。誰かが同じような問題に遭う経験ありませんか？

①
>https://en.wikipedia.org/wiki/OpenGL_ES
OES_vertex_half_floatは、OpenGLES2.0の一つの拡張機能です。

②
>現在、ハイエンドマシンを含む多くのスマホで精度の問題が発生しています。floatを使用できる状況で、floatを使用して徐々に最適化することをお勧めします。

③
>慎重に検討し、繰り返し観察した結果、問題のある特殊効果の共通点を見つけました。それは「位置」であります。特殊効果Shaderの頂点計算部分を調べてみると、大部分の特殊効果の頂点属性はhalfであります。シーン特殊効果とUI特殊効果の違いを比較したところ、UI特殊効果のワールド座標がシーン特殊効果よりもはるかに大きいことがわかりました。ですから、half精度に問題があるといつも感じていました。halfをfloatに変更し、再パッケージして検証すると、問題は解決されました。

---
###Shader
Q4: Shaderで、実際の計算に使用していないパラメーターはGPUに入りますか？下記のようなShaderを仮定します。Property内に一つのテクスチャ_MainTextureが定義され、割り当てられています。そしてSubShaderにもテクスチャ_MainTextureを定義しましたが、実際の計算プロセスにはこのテクスチャは使用されません。
この場合、CPUはまだテクスチャをビデオメモリに入力し、CPUとビデオメモリのコストを引き起こしますか？Propertyの存在を保持しますが、SubShaderにSampler2Dを定義しない場合、このコストを回避できますか？

```ruby:
Shader "Test/DummyTex"
{
    Properties
    {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        
        Pass {  
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata_t {
                float4 vertex : POSITION;
            };

            struct v2f {
                float4 vertex : SV_POSITION;
            };

            fixed4 _Color;
            sampler2D _MainTex;
                
            v2f vert (appdata_t v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
                
            fixed4 frag (v2f i) : COLOR
            {
                fixed4 col = _Color;
                return col;
            }
            ENDCG
        }
    }
}
```

>問題主はUnityのShaderをOpenGLまたはDXのShaderにコンパイルすることを試みることができます。OpenGL ES 2.0を例にします。ShaderのInspectorパネルにあるCompileand Show Codeボタンをクリックして、表示できます。問題主が与えたShaderをGLES 2.0バージョンにコンパイルしました。

>```ruby:
//////////////////////////////////
  //                              //
  //      Compiled programs       //
  //                              //
  //////////////////////////////////
//////////////////////////////////////////////////////
No keywords set in this variant.
-- Vertex shader for "gles":
Shader Disassembly:
#version 100

>#ifdef VERTEX
attribute vec4 _glesVertex;
uniform highp mat4 unity_ObjectToWorld;
uniform highp mat4 unity_MatrixVP;
void main ()
{
  highp vec4 tmpvar_1;
  tmpvar_1.w = 1.0;
  tmpvar_1.xyz = _glesVertex.xyz;
  gl_Position = (unity_MatrixVP * (unity_ObjectToWorld * tmpvar_1));
}


>#endif
#ifdef FRAGMENT
uniform lowp vec4 _Color;
void main ()
{
  gl_FragData[0] = _Color;
}


>#endif
```

>見られますのは、UnityのShaderコードではテクスチャサンプリングが使用されていないため、最終的に生成されたGLSLにはテクスチャサンプリング変数がありません（最適化されています）。したがって、このGLSLコードをコンパイルまたは使用する場合、テクスチャユニット変数を割り当てる必要はありません。 テクスチャメモリのコストはありません。

>さらに、テクスチャがGPUに占めるビデオメモリのコストは、Shaderに使用されているかどうかに決めます。すべてのShaderがテクスチャを使用するとコストがあるではありません。例えば、Shader AがテクスチャAを使用した後に、Shader BもテクスチャAを使用しました。これで、テクスチャAはGPUに２回アップロードする必要はありません、１回だけで結構です。

---
###アセット管理
Q5: Unity3Dエンジンの実行中に、ある圧縮形式のテクスチャを使用する場合、ロードする時に解凍してビットマップのRGBA32ビット形式に従ってメモリに保存されますか、それともメモリ内も圧縮形式のデータで、レンダリングを提出する時にGPUが自分で解凍してサンプリングしますか？GPUハードウェアが画像または動画の圧縮形式をサポートしているという記事をよく目にするのはなぜですか？ この処理過程は大体どのようですか？
>実際、これらは画像圧縮とテクスチャ圧縮の2つの違う概念です（用語が不正確な場合があります。訂正してください）。JPGとPNGなどは画像圧縮であり、ETC、PVRTC、ASTCなどはテクスチャ圧縮です。画像ファイルはハードディスクからメモリに読み込まれ、解凍されてテクスチャになり、GPUに転送され、GPUはそれを特定のRGBカラー値に解凍します。Read&Writeがオンになっていない場合、テクスチャはメモリからアンロードされます。

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

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