UnityでKTX Compressed Texturesを使ってみる

# 前書き
2022年Unityアドベンドカレンダー25日目の記事になります。

:::note alert
　
お詫び:この記事のベースを用意しだしたのが今年の1月ぐらいからだったのでKTX/Unityのパッケージ導入手順が古く最新版ではないことに最後に実機ビルドをしていたら気づきました。
古いバージョンと新しいバージョンでは破壊的変更がありソースコードもかなり異なる上、今の手順ではUnity2021.2以降のUnityでWebGLビルドが通らないです。

ただこれから予定がある為記事と技術調査をする時間がないので非最新な状態であることを承知で一旦投稿とさせていただきます。　
大変申し訳ございません。

後日修正します...
:::


<details><summary>去年の私の記事を読み込んでくれてた人用の前書き</summary><div>
この記事は去年落とした環境構築部分の補足になります

[去年の記事](https://qiita.com/T_keigo/items/3227ab7df18dfcbe356a#%E5%AF%BE%E5%BF%9C3-ktxbasis-texture-unity-package%E3%81%A7basic%E5%9C%A7%E7%B8%AE%E5%BD%A2%E5%BC%8F%E3%81%AB%E3%81%99%E3%82%8B
)
>アドベンドカレンダーの投稿時間までにこの項が書ききれなかったので別記事で追記という形で出したいと思います。

去年この記事で取り上げたKTXフォーマット周りの実運用について別記事を書くって言ってましたが結局書かないまま1年経ってしまったので結局ここで書くことにしました。
~~WebGL扱ってた会社を辞めて暫くネイティブやってたからモチベが失せてたけど最近またUnity WebGL触りだしてモチベが蘇った~~

まあtexture圧縮周りの事情は色々な人が書いてるし出涸らし案件なんですがUnity WebGLって1つのコード、1回のビルド、3つのOS、4つのGPUアーキテクチャみたいなイカれた環境なんで当然Unity WebGLではデフォルトで使われるDXT圧縮だけしてればOKってわけじゃなくてなんらかのtexture圧縮もそれぞれ分かれてるんですよね、この辺の話はWebGLでは大分盛んにされているようですがUnityではForumの片隅でされているぐらいしかあまり見ない気がしています。

それに最近VRChatとかclusterとかVRSNSの類って外部からユーザー作成assetをランタイムに読み込む系assetが多い印象があるんですけど、まあ素直にUnityでやると生のpngとかをWebRequestで落として生のtextureがメモリにドンドン積まれていくことになると思うんですけどこは大変メモリ的には嬉しくないのでサーバーサイドとかでマルチプラットフォーム対応できるtexture圧縮形式をドシドシ詰めていけたりしたら嬉しいと思うんですよね。
まさにこれこそglTF(GL Transmission Format)の思想そのものなんじゃないかと思うんですけど皆さんはどう思いますか？
素晴らしい技術形態だと思います。

ただ今のUnityではそこまで考慮しきるのはちょっとまだ厳しくてやっとデフォルトでETS1/2とかができるようになったとかなんですよね、ただUnityのロードマップにBASIC圧縮は既に入っているので将来的にはライブラリに頼らずに素直にできるようになると思います。
Unity2023か2024あたりかな・・・


じゃあほかのWebGLライブラリはどうなんだって話なんですが

https://doc.babylonjs.com/features/featuresDeepDive/materials/using/ktx2Compression
MSがメンテしてるBabylon.jsは既にサポートしている感じです

https://support.playcanvas.jp/hc/ja/articles/1500001523822-PlayCanvas%E3%81%A7Basis%E3%83%86%E3%82%AF%E3%82%B9%E3%83%81%E3%83%A3%E5%9C%A7%E7%B8%AE%E3%81%8C%E5%88%A9%E7%94%A8%E5%8F%AF%E8%83%BD%E3%81%AB-
Playcanvasもサポートしています。

まあ少しUnityはこの辺は遅れ気味な感じですね。
既に(KTX/Unity)[https://github.com/atteneder/KtxUnity]がリリースされて3年近く経っているのでそろそろ注目されてもいいだろうと思うのですが・・・・


ぶっちゃけまだKTX形式の実戦投入はまだ出来てないのでその去年半端にしたサーベイの続きができたらな程度の気持ちで書きました。

</div></details>


# 本題
今回はVRAMの節約に使われるtexture圧縮フォーマットKTX形式をUnityで使うお話です。
プラットフォームに指定があるわけではありませんがこの記事はUnityWebGL前提で話を進めます。

## そもそもtexture圧縮について知らないという方
一般的にtextureの圧縮と言えば.jpegなどのストレージ容量や通信帯域削減を目的とした圧縮形式の事を指しますが本稿で扱うのはGPUメモリ（VRAM)上で扱う際のtexture圧縮の事を指します。
ある程度モバイル向けやWebGLなどメモリ要件が煩いUnityプロダクトに関わったことがある人であれば一回は聞いたことがあると思いますが。 `ETC1`や`DXT1`などのGPUがデコードできる形式の圧縮形式を活用することでよりメモリを節約して扱うことができます。

実際の細かい扱いについては
（自分も一々覚えてないです）一旦ここは有力な参考サイトとドキュメントをご紹介します。

実際の概要として[^1]
実際の各種圧縮形式の取り扱いや癖は[^2]の162p~以降の項目が詳しいです。　
Unityがどのプラットフォームでどの形式をサポートしているかは[^3] のドキュメントを参照



# KTX Compressed Texturesとは
KTXは`Khronos Texture Container` の略です、glTFの策定をしたKhronosグループが3D向けのtexture圧縮"コンテナ"形式として策定したものです。

コンテナ形式というのが大事でiOS,Android,Windowsと各々で使える圧縮形式が違うので中間形式を定義してラップすればマルチプラットフォームが出来るという仕組みです。[^4] [^5]

KTXもKTX1とKTX2があり最新はKTX2なので以降では基本的にKTX2を扱います。


## 圧縮済みTextureを用意する
普段CompressedなTextureを扱うときはUnityにimportさせてからやりますがKTXの場合は外部ツールでやります。


KTX

- BinomialLLC社の  [basis_universal](https://github.com/BinomialLLC/basis_universal)
    - 今回はこれを使う
- [PVRTEXTOOL](https://developer.imaginationtech.com/pvrtextool/)
    - なんかGUIツールっぽい
- [gltfpack](https://github.com/zeux/meshoptimizer/tree/master/gltf)
    - glTFの圧縮ライブラリだがオプションでtexture圧縮も可能、こっちの方が刺さる人も多いかもしれない
- [KTX-Softwear](https://github.com/KhronosGroup/KTX-Software)
    - クロノス公式ツール群
    - この中にある[toKTX](https://github.com/KhronosGroup/KTX-Software/tree/master/tools/toktx)がtexture圧縮ツールになる
    - [document](https://github.khronos.org/KTX-Software/ktxtools/toktx.html)
    - toKTXには[圧縮時間ベンチマーク](https://openbenchmarking.org/test/pts/toktx)がありCPU性能がまあまあ大事らしいですね。
    - 圧縮モードにもよるが大量のtextureを圧縮するとなるとまあまあ時間がかかることがわかります...大量に圧縮するなら秋葉原に行ってRyzen9 7950Xでも買ってきた方がいいかもしれない


ぱぱっと調べたらこれだけありますが今回はbasis_universalを使います、公式のtoKTXを使えよと思うかもですが後述のktx/unityがこれを使う前提でREADMEが書かれてたのでそのまま使っちゃいました。
あとビルドするときcmakeのバージョン周りとかで躓きがなかったです。

### basis_universalを使う
#### ちょくちょく出てくる.basisと.ktxについて
basis形式はKTX2とほぼおなじ実装らしいですが標準委員会の策定物ではなくBinomialLLC社の実装なので標準かどうかという点ではKTX2を推奨するそうです[^5]


#### 環境構築
今回は基本CUIツールなので何らかのコンソールを用意します、今回は以下の環境で進めます

- OS Windows10 21H2
- WSL1 Ubuntu22.04
- Unity 2020.3.43f1
- KtxUnity 0.8.2
- basis universal 1.16.3
- cmake version 3.22.1
- g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0

Visual Studioでも動くらしいですが私は日常的にGitクライアントにWSLを愛用しているのでWSLでやります。

ひとまずcmakeとかgccとか色々入れていきます
多分gcc,cmakem,makeあたりがあれば困らないと思います。


```
$ sudo apt install gcc g++
$ sudo apt install make
$ sudo apt install cmake
$ git clone https://github.com/BinomialLLC/basis_universal.git
$ cd ./basis_universal/
$ cmake CMakeLists.txt
$ sudo make install
```
```
$ basisu
Basis Universal GPU Texture Compressor v1.16.3
Copyright (C) 2019-2022 Binomial LLC, All rights reserved
以下略...
```

これで多分pathもちゃんと通って動くとおもいます。

一旦これでtexture圧縮の準備は整いました


<details><summary>Basisの圧縮オプションなど</summary><div>
まだ本格的に検証できてないのですが比較的低品質で高速圧縮なETC1S形式と高品質で圧縮が遅いUASTC形式があります。
basisのREADMEに細かく色々書かれているので今後色々検証したいところです。

</div></details>


## 実際にKTX形式のtextureをUnityに取り込む
KTX形式はUnityでは対応できない為[KtxUnity](https://github.com/atteneder/KtxUnity) を使います
`(KTXUnity) KTX/Basis Texture Unity Package` はglTFFastなどのUnityランタイム開発で多くの実績があるatteneder氏の開発によるものです。
[Demo](https://github.com/atteneder/KtxUnityDemo) もあるのでこちらも参考になります


:::note alert
この箇所は古いです、参考にしないでください
:::

~~次にKTX/UnityパッケージをUnityに導入します~~
```
 "com.atteneder.ktx": "https://gitlab.com/atteneder/ktxunity.git", //これは間違い
```
~~をmanifest.jsonに追加するだけでした~~

このやり方だと古いバージョンが落とされてしまい古いKTXTextureがダウンロードされてしまうんでやめましょう


### basisで画像を圧縮する
Basisでは画像のy flipがUnityでひっくり返るらしいので反転オプションを入れる必要があります
>--y_flip for basisu

y_filipを忘れるとこのようになります😢
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/30ab5f67-d315-cc37-d1c4-de2cade09642.png)


今回はダミー画像ジェネレーターを使用して作った480x320の画像でテストします。
![Test_480x320.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/3b5e8d54-a511-886c-901e-62aa22dba369.png)

- ETC1SModeで圧縮

`$ basisu -y_flip  -ktx2 Test_480x320.png -output_file Etc1sTest_480x320.ktx`

- uastcModeで圧縮
` basisu -y_flip  -ktx2 -uastc Test_480x320.png -output_file UastcTest_480x320.ktx` 

これらは一旦UnityAssetsのstreaming assetsディレクトリに入れてしまいます。


### UnityでKTXなTextureをLoadする
Unity内部で直接KTXTextureをLoadしてassetとして取り込む機能はないのでSpriteみたいにserializefieldでアタッチみたいなことは現状できません
基本的に`KtxTexture`クラスのLoadFromStreamingAssetsでLoadして運用します



# サンプルコード

```c#
using KtxUnity;
using UnityEngine;
using UnityEngine.UI;

public class TextureTest : MonoBehaviour
{
    [SerializeField]
    private RawImage _nomalImage;

    [SerializeField]
    private RawImage _etc1sImage;

    [SerializeField]
    private RawImage _uASTCImage;

    [SerializeField]
    private Texture2D _dxt1sample;

    private string _etc1sPath = "Etc1sTest_480x320.ktx";
    private string _uASTCTexPath = "UastcTest_480x320.ktx";

    private KtxTexture _etc1sTex = new KtxTexture();
    private KtxTexture _uASTCTex = new KtxTexture();

    void Start()
    {
        Init();
    }

    private void Init()
    {
        //ロード完了コールバックが定義されている
        _etc1sTex.onTextureLoaded += _etc1sTex_onTextureLoaded;
        //StartCorutineを駆動させる為に自身のゲームオブジェクトを渡してライフサイクルオブジェクトにする
        _etc1sTex.LoadFromStreamingAssets(_etc1sPath,this);

        //同じく
        _uASTCTex.onTextureLoaded += _uASTCTex_onTextureLoaded;
        _uASTCTex.LoadFromStreamingAssets(_uASTCTexPath, this);

        _nomalImage.texture = _dxt1sample;
        
    }

    private void _uASTCTex_onTextureLoaded(Texture2D arg0, TextureOrientation arg1)
    {
        if (_uASTCImage)
        {
            if (arg0 != null)
            {
                _uASTCImage.texture = arg0;
            }
            else
            {
                Debug.Log("Error");
            }
        }
    }

    private void _etc1sTex_onTextureLoaded(Texture2D arg0, TextureOrientation arg1)
    {
        if (_etc1sImage)
        {
            if (arg0 != null)
            {
                _etc1sImage.texture = arg0;
            }
            else
            {
                Debug.Log("Error");
            }
        }
    }
}


```

#### 実行
左からUnityデフォルトのDXT1圧縮,KTX ETC1S,KTX UASTSになります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/a5e31807-dbc9-f95c-12f4-f3e085af01d7.png)


### WebGLビルドに失敗する
原因はKTX0.8.2を使っていたことです、KTX2～からFIXが入った為Unity2021で更新されたemscriptenに対応するようになった為動くようになったみたいです。
一旦ここでは仮対応としてUnity2021.3.11から2020.3.43にダウングレードして対応しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232732/191b9b94-c712-2cc3-3358-6ceeea843ad9.png)


# おわび
やってみた系環境構築記事なのに手順が古くダメですは不味いのでそのうち記事修正かけます。
まあKTXTextureってのがあるんだ～～ってぐらいのノリで見ていただけると幸いです。
## TODO:
- パッケージ管理とサンプルコードを最新にする
- 圧縮時のtexture劣化特性をオプションごとに検討
- ktxのコンテナはどう動いているのか把握したい
- VRAM効率のベンチマーク

# 参考文献
[^1]: [3Dコンテンツの最適化に。圧縮テクスチャーをWebGLで扱う方法と利点](https://ics.media/entry/17863/)
[^2]: [Unity uGUI アドバンスド・リファレンス](https://github.com/heppoko/Unity_uGUI_Advance_Reference)
[^3]: [推奨、デフォルト、およびサポートされているテクスチャ形式 (プラットフォーム別)](https://docs.unity3d.com/ja/2022.2/Manual/class-TextureImporterOverride.html)
[^4]: [UnityForum KTX2 & BASIC Textures](https://forum.unity.com/threads/ktx2-basis-textures.999880/)
[^5]: [Babylon.jsの解説](https://doc.babylonjs.com/features/featuresDeepDive/materials/advanced/mutliPlatTextures#khronos-texture-container-format-ktx-files)