モバイルブラウザでもUnity WebGLが動いてほしい!

2021年アドベントカレンダーネタです

モバイル用のブラウザ特有の事情をチラチラ書いていった感じです。
具体的な実装の話というより、サポートしてないものを動かすとロクなことはないですが動かすために調べてた事柄の調査ログです。
特に実際に生かされる場面もなさそうだったのでアウトプットしたいと思います。

ニッチなネタですがウケる人が居るとうれしい・・・



[[Unity]WebGL出力をスマホのブラウザで実行する](https://qiita.com/yuji_yasuhara/items/237a571b9ba8be3d0cbd)
最小構成でいいのでひとまずスマホでブラウザ実行したい！という人は上記の記事の内容が実務的でおすすめです。



# Unity WebGLのモバイル対応
2021年12月現在もまだUnity公式ではモバイルブラウザをサポートしてないという [公式ドキュメント](https://docs.unity3d.com/ja/2021.2/Manual/webgl-browsercompatibility.html) と明記されてれています。

## 動かないわけではない
ビルドチェーンがasm.jsだった頃はかなり辛かったと聞きますがスマホ性能の向上やUnityチームの努力により割と動いてしまうようになりました、しかしまだ様々な問題が残されており開発コストを爆増させます。


## AndroidとiOSの深い壁
モバイルブラウザで動かすという一つの目標ですが実際にはAndroidとiOSでわけて考えるのがベターです、理由としてはいくつかありますがWebGL2.0の [対応状況](https://caniuse.com/webgl2) をご覧ください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/59a78793-f2ee-53c7-ff79-b6d71c9874b8.png)

今年の9月にリリースされたSafari15までWebGl2.0が一切サポートされていませんでした、つまり夏頃までWebGL2.0を封印して開発する必要がありました。
WebGL2.0を封印するとただでさえ制約の多いWebGL開発において使用可能なAssetなどがガクッと減るので自分でShaderを書くなり吟味するなりする必要が出てくるので自然と開発速度とクオリティに制約がかかるわけです。
一方AndroidではChrome57で対応しているので多くのユーザーが問題なく使用可能です。
先ほど紹介した記事ではビルド設定でWebGL1.0にしていますがSafari15&AndroidターゲットであればWebGl2.0でも動くので問題ありません。
WebGL2.0を封印するとuniGLTFの導入時やPost Processingなどで必要なlinear色空間の使用ができず割としんどい思いをするので、技術選定時には細心の注意を払う必要があります。


### Safari15の罠
先ほどWebGL2.0に対応したとして紹介したiOS Safari15ですが、深刻なバグがあるようで実用レベルに2021年12月10日現在も達していません。

WebGL2.0を有効化すると急にクラッシュする挙動が私の手元や各種WebGLプラットフォームのフォーラムでも確認されています（どうもMac版Safari15でも起きてるらしい）

[参考1(Unityフォーラム)](https://forum.unity.com/threads/monterey-safari-15-1-broken-graphics.1189021/)
[参考2(Babylonjs)](https://forum.babylonjs.com/t/ios-15-and-webgl-2-issues/23984/17)


まだiOS15.1では修正しきれていないみたいですがiOS15.2では直されるとするUnityの中の人の[発言](https://forum.unity.com/threads/ios-15-webgl-2-issue.1176116/#post-7681486)もあったので気の早い人であれば近日リリースになったiOS15.2 RCあたりとか試してもいいかもしれません（注:この箇所は2021/12/09に書かれておりこの記事公開時点では15.2はリリースされているはずです）

### iOS版 Choromeの罠
SafariがダメならiOS版Chromeを使えばいいじゃんと思われる方もいらっしゃるかもしれませんが実はiOS版ChromeとSafariはレンダリングエンジンが同じWebkitベースなのでWebGL周りの基本挙動は同じになります、

参考:[iOSはChromeもSafariも同じレンダリングとかいう訳ワカメな件](https://qiita.com/pink/items/a54b8cadbe39a06956a9)


## テクスチャ圧縮
WebGLビルドではテクスチャ周りの節約は必須ですがここでもモバイルブラウザ特有の挙動があります。

### DXT1/DXT5はモバイルブラウザでは使えない
2021年12月現在デファクトといえるUnity2019.4/2020.3のドキュメントではWebGL向けのテクスチャ圧縮形式として[UnityのTexture形式対応表](https://docs.unity3d.com/ja/2019.4/Manual/class-TextureImporterOverride.html) ではDXT1/DXT5(所謂S3TC)がサポートされています。

このDXT圧縮ですが[WEBGL_compressed_texture_s3tc](https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_compressed_texture_s3tc)APIによってサポートされています。
![スクリーンショット 2021-12-18 12.15.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/95e14aac-881f-25fb-c59e-52ab7ef6ad6c.png)

しかしモバイルブラウザではサポートされておらず、ただでさえ少ないメモリをtextureに食われつくしてしまいます。


※ [WebGLReport](https://webglreport.com/)で使用中のブラウザのWebGL対応情報を返してくれるくれるので想定端末で事前に確認できるので実機で対応状況を把握可能
※モバイルブラウザが悪いというよりモバイル端末で使われる多くのGPUが対応してないという言い方が正しい
例えばQualcommの[ゲームデベロッパーガイド](https://developer.qualcomm.com/sites/default/files/docs/adreno-gpu/developer-guide/index.html)では
>Significant texture compression formats supported by Adreno GPUs are:
    ATC – Proprietary Adreno texture compression format supporting RGB and RGBA component layouts
    ETC – Standard OpenGL ES 2.0 texture compression format supporting the RGB component layout only
    ETC2 – Standard OpenGL ES 3.0 texture compression format supporting R, RG, RGB, and RGBA component layouts, as well as sRGB texture data
    ASTC – Texture compression format supported in OpenGL ES (3.0 and later) and Vulkan that allows the compression use a variable block size, offering remarkably low levels of image degradation given the compression ratios achieved

となっておりこれはブラウザ的な制約というよりハードウェア側の制約になるので端末固有だとおもっておくといいかもしれません。

## じゃあどうすればいい？

### 対応１ そのままでいく
諦めて最低限のテクスチャで済むように工夫する。
限界ギリギリまでアトラス化して詰め込み、ライトマップのサイズをできるだけ減らし、演出上どうでもいい部分のテクスチャをケチるなどの涙ぐましいアプローチをひたすらしていくなどをしていく（どちらにせよある程度の最適化はWebGLやるならやらないといけないけど）
特にiOSは特にメモリ制限が厳しく、Androidより制限が厳しい印象があり
端末の物理RAMサイズ以前に300MB~500MB程度でブラウザ側で強制的にタスクキルされる傾向があります　
※正確なベンチマークはとってないので要調査

### 対応2 Unity2021を使う
Unity2021.2はモバイルブラウザ対応を徐々の進めており2020.2.0b16のリリースノートでETC形式などの圧縮サポートが追加されています。

[Preview of Final 2021.2.0b16 Release Notes](https://unity3d.com/jp/beta/2021.2a)
>WebGL: Enabled ETC/ETC2/ASTC/BC4/BC5/BC6/BC7 compressed texture formats for WebGL in editor, build and runtime.

textureごとのフォーマットは[import setting](https://docs.unity3d.com/ja/2021.1/Manual/class-TextureImporter.html) から確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/488577c8-0136-bab2-7eb3-fa3189ac3456.png)
主にモバイル端末では定番の圧縮形式になっているのでモバイルでネイティブ向け開発している人なら知ってる形式だと思います。

Unity2021.2の[圧縮対応表](https://docs.unity3d.com/ja/2021.2/Manual/class-TextureImporterOverride.html)でもなぜかサポートされない「はず」の `WebGL (iOS and Android browser)` の項目があり`RGBA Crunched ETC2` あたりが「可」のステータス扱いになっています。
ただしUnity2021はまだ記事執筆時点ではLTSになっておらず長期の追加開発が想定されるプロダクトで使うのは微妙な感じが否めません（後から出てくるLTS版を見据えるという考え方もいいのかもしれない）

しかし課題もありETC圧縮はWindowsなどで動いてるマシンの多くは対応しておらず、DXT圧縮とETC圧縮を良い感じに切り替える対応が必要になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/93c7fcc7-ca3f-3e97-bf01-9c3fc0c25dc0.png)
[Texture Format Test](https://toji.github.io/texture-tester/)でWindows10(Intel Xe Graphics)+Firefoxで確認した時の挙動

ちなみに無理やりWindowsマシンでETC圧縮を適用したビルドファイルを実行するとブラウザのコンソールで `WARNING: RGB Compressed ETC UNorm format is not supported, decompressing texture` と怒られ圧縮は強制的に解凍されるもののtextureの表示自体は可能です(AndroidでDXT圧縮を食わせるとこれと同じことが起きる）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/9ac05c38-bc2a-54cf-879e-bc4e6d7b157a.png)

この部分を良い感じにUA別に切り分ける実装を自分は知らないのでGameのトップページでUAを見たうえでビルドファイルを分けるとかするしかないのかな・・？と思いました、ビルド時点でプラットフォームが決まる分にはdefaultのフォーマットを定義しておけばいいのですがWebGLの場合URLを開いて初めてプラットフォームが判明する仕様なので無理もないのかなと思います。　


### 対応3 KTX/Basis Texture Unity PackageでBASIC圧縮形式にする

[KTX/Basis Texture Unity Package](https://github.com/atteneder/KtxUnity)とはWebGLでお馴染みのKhronosグループが規格制定した[KTX](https://github.com/KhronosGroup/KTX-Specification)（Khronos TeXture）フォーマットをUnityでも使うためのOSSライブラリです

#### そもそもKTX形式とは
DDSことDirectDrawSurfaceみたいな中間フォーマットと言える存在です、ETC1/2あたりとも互換性があります
ただし一度中間フォーマットに変換しなければならない為一手間かかります。　
もしプロダクトに組み込むなら画像自動変換サーバーみたいなのあった方がいいかも・・・？

変換には[basis universal]((https://github.com/BinomialLLC/basis_universal)
)を使います



## Unity 2020で使う
[Demo](https://github.com/atteneder/KtxUnityDemo)を参考にして触ってみる `StreamingAssets`にbasicファイルを置く前提の実装になっているのでそこに合わせてやってみる、実際の開発シーンではBlobストレージ上にbasicファイルを置いて逐次アセットバンドルダウンロードをしていく感じになります。

## 環境構築
アドベンドカレンダーの投稿時間までにこの項が書ききれなかったので別記事で追記という形で出したいと思います。


### Basic圧縮によるMemoryBenchmark
実際の端末上で同種のtextureをフォーマット別でどれだけ生成してタブがおちるか的な簡単なテストをしたいと思います。
できれば直接wasmのMemoryをプロファイルしたいんですが、PCとスマホのブラウザで公平にプロファイリングしつつ比較する方法が正直よくわからないので一旦限界までtextureを生成してプロセスキルされるタイミングで把握する原始的なアプローチで検証したいと思います。

本当はbenchmark用Scriptも自作するつもりだったんですが記事投稿予定までに間に合わない感じがしたので今回は作者謹製のdemoプログラムを使います。
リポジトリは[こちら](https://github.com/atteneder/KtxUnityDemo) 使用Unityバージョンは2020　3.25f1です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/735b0972-4be4-234a-a82d-b989eb6f37a5.png)
こんな感じのテスト環境になっています

#### 検証機材について

・Mac(PC1)
MacBookPro M1(2020) 
RAM:8GB

・Windows10(PC2)
CPU:Ryzen Thredripper 3960X
GPU:RTX3090(Driver471.41) 
RAM:DDR4 64GB
Browser: Chrome96

・Android端末
Huawei Nova5t(Android10 EMUI11.00)
Google Chrome96

・iOS端末
iPhoneSE2(iOS 15.1)
Safari15.1

#### benchmark結果
単位は枚数
テストモード trout.jpg/trant.ktx
解像度:1024×1024
上記画像はデモサンプルで参照可能

| textureフォーマット| PC1(M1 Mac Safari) | PC2(Windows10 Chrome) | Android Chrome(Nova5t) | iOS Safari(iPhoneSE2) |
|:-:|:-:|:-:|:-:|:-:|
|  tront.ktx(BASIC圧縮) |10000| 61900| 9856 |2310|
|  tront.jpg(無圧縮) |1736 |6000|1219|225|

###　この結果をどう解釈するべきか
まず同じ圧縮フォーマット(ktx)を使用してワンソース、マルチブラウザを実現している点は強く評価されるべきです、ktxでの展開中
Windowsでは `Transcode to GraphicsFormat RGBA_DXT1_UNorm (BC1_RGB)` iOSでは `Transcode to GraphicsFormat RGB_ETC_UNorm (ETC1_RGB)` Androidでは `Transcode to GraphicsFormat RGB_ETC_UNorm (ETC_RGB)`
とログが出ていました、中間フォーマットを経由している為端末にあったフォーマットで展開してくれていることがわかります。

#### 圧縮効果について
おおよそ各マシンで10倍ほどの差が無圧縮jpgとついており明らかな差があることがあります、ただDXT1/5の理論圧縮は1/4か1/8なのでやや値が大きすぎる気がします
もしかしたら圧縮時のパラメーター次第で変わったりするんでしょうか、今後よく検討したいです。

### BASIC圧縮に対する問題点
どうでもいいですが以外と困るのが一回圧縮すると実際のプロダクトに組み込みをしてUnityで表示するまでtextureの中身が見えなくなります![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/3da9b156-5764-2722-e3fa-c4564de1de85.png)
画像ファイルとして認識されなくなりますので実際の運用時には細心の注意を払って命名規則をつけるなどしないと破滅します
それでもUnity2020でETC圧縮を一気にかけるよりはこっちの方がどう考えてもスマートだしMobileブラウザの相手をするならKHRNOS KTXは必須になるでしょう

### 追記(2022/10/25)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/f32b1f0c-c0d5-7f10-2ceb-6a47766f8097.png)
https://unity.com/ja/roadmap/unity-platform/platforms

最近気づいたんですけどUnityのロードマップにBasic圧縮が検討されているようです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/6f93008b-ea1a-2eee-9947-b83fe7958385.png)

in Progressにもmobile ブラウザサポートが明記されてるのでUnity2023~2024あたりに期待ですかね...

### メモリ消費について
凄い当たり前なんですけどスマホや一般PCはVRAMが通常のMemoryユニットとわけずにVRAMを置いているので気付かないんですが外付けGPUの場合グラフィックボードについているVRAMのサイズが限界値そのものになるっぽく24GBを超えてVRAMが消費されたあたりでWindowsマシンのブラウザがクラッシュしました、GPUの性能も確認しましょう


#参考文献
[Tips and tricks for using WebGL on mobile (tested up to 2018.3.13f1)] (https://forum.unity.com/threads/tips-and-tricks-for-using-webgl-on-mobile-tested-up-to-2018-3-13f1.666121/)
[ETC圧縮のアルゴリズムとは？](https://www.webtech.co.jp/blog/optpix_labs/format/5882/)
[UnityWebGLロードマップフォーラム](https://forum.unity.com/threads/webgl-roadmap.334408/page-11)
[HYPERでんち Texture File Format](https://dench.flatlib.jp/opengl/texturefileformat)
[KtxUnityDemo](https://github.com/atteneder/KtxUnityDemo)
[BenchmarkDemo](https://atteneder.netlify.app/posts/benchmarking-basis-universal-transcoding-in-unity/)