UnityでSimpleTextureModifierを使用してSprite Packerを最適化する？!(減色編)

Unity上に完結した状態で、様々なイメージファイルを最適化するために[SimpleTextureModifier](https://github.com/spacebeagle/SimpleTextureModifier)というアセットポストプロセッサを作成しました。
画像の減色、圧縮時における劣化をUnity内だけで解決することができます。
しかし残念ながら最近のUnityの2D機能の追加、変更等によって、このアセットポストプロセッサの機能だけでは、かならずしも画像の変換処理を**最適**にはできない状況がでてきました。さらにUnityは秋のv5.2で2D関係機能の大幅アップデートも予定しているということで、こうした状況はさらに進行していってしまうかもしれません。
ただ、秋まではまだ時間がありますし、画像圧縮ではなく減色に関してはSimpleTextureModifierはまだ有用でしょう。
今回UnityのSprite機能に対応して若干の機能追加をおこないましたので、一度その典型的な使い方をおさらい的にたどって、この小さなアセットポストプロセッサによる画質改善の参考にしていただきたいと思います。
#Unityにおいて画像メモリーのサイズとは?
アプリのサイズや使用メモリー(RAM)を減らすには、画像の軽量化をおこなうのが有効です。
画像データはアプリ内でも多くの割合のデータ量を占めていることが多いからです。
いわゆるネイティブアプリケーションにおいて難しいのは
**アプリのサイズを減らす ≠ 使用メモリー(RAM)を減らす**になることが多いということです。
前者の**アプリのサイズを減らす**ためにはjpegやpngといった高い圧縮率のファイルを直接アプリに組み込んでやる方法は有効でしょう。ただし読み込まれたデータはメモリー(RAM)内ではRGB24bitsやRGBA32bitsというTrueColorと呼ばれる極大な状態に必ず変換されてしまいます(*コンシューマ機では、かつてPlayStation3でSCEの社内製ミドルウエアPhyreEngineがSPUの強力な演算能力によってjpeg形式のファイルを実機内でDXT形式に再圧縮して、アプリサイズも使用メモリー(RAM)も削減するという方法を提供したことがありましたが、これは例外中の例外でしょう*)。
まさしく**サイズ ≠ メモリー(RAM)**ということです。
Unityではこういった高圧縮率画像形式を直接読み込む方法はサポートしていません(自分でjpegやpngから読み込んでやるのは当然可能)。jpegやpngといった形式もいったんは、ハードウエアに密着したUnity内部の独自形式に変換されてしまいます。
内部形式はTexture InspectorのTexture Format設定で可能です。
![textureimportersetting.jpg](https://qiita-image-store.s3.amazonaws.com/0/80418/48dc4a69-238d-ea55-ab38-98abc66b0fb9.jpeg)
このプルダウンを押すと設定が可能です。
![textureimportersetting2.jpg](https://qiita-image-store.s3.amazonaws.com/0/80418/c665df02-4a83-d9d7-34ba-0ae44fea6d1f.jpeg)
Texture Typeの設定が**Advancedでなければ**、"Compressed"、"16 bits"、"Truecolor"、"Crunched"が設定可能です。
これらの指定は、Unityが適切な内部フォーマットを選択するためのヒントのようなものです。ソースの画像ファイルとターゲットになるプラットフォームから、Unityは自動的に適切と思われる内部画像形式を選択します。
**自動的に選択**というところが重要で、この機能のおかけでUnityは初心者が使っても、それほどには突拍子がないことも起こらずに無事に絵が出るわけです。
最終的なフォーマットは入力ソースがjpeg、png24、tga24等(αチャンネル無し)かpng32、tga32等(αチャンネルあり)かによってわかれます。
例えばターゲットプラットフォームにiOSが指定されている場合、

```text:iOSにおけるテクスチャフォーマットの割り振り
|                  | TrueColor           | 16bits              | Compress(Crunched)        | 
|------------------|---------------------|---------------------|---------------------------|
| Jpeg Png24 Tga24 | RGB24bit(RGB888)    | RGB16bit(RGB565)    | RGB Compress PVRTC 4bits  |
| Png32 Tga32      | RGBA32bit(RGBA8888) | RGBA16bit(RGBA4444) | RGBA Compress PVRTC 4bits |
```
のようになります。
#Unityにおいて画像メモリーのサイズを減らすには?
今回はpng32、tga32等の画像ファイル(jpegでも可能ですが、この場合元のデータになかった余分なαチャンネルが付加されてしまいます)をRGBA16bitにする方法について考えます。
これらの画像を劣化なく格納するにはRGBA32bitが必要です。これを半分のサイズのRGBA16bitに変換するわけです。サイズが半分になれば単純に使用メモリー(RAM)も半分になります(アプリのサイズ内の画像データは単純に半分にはなりません、再度gzip等の圧縮をかけた結果となるからです)。
試しにテスト画像、、Unity自体のカラー選択画像を使って減色をおこなってみましょう。
![RGBCOLOR.png](https://qiita-image-store.s3.amazonaws.com/0/80418/9e396c5e-2f3f-4aed-326a-caa7f17fc23e.png)　⇒　![RGBA16.png](https://qiita-image-store.s3.amazonaws.com/0/80418/434e6310-dca9-b94c-319d-7531c6bf2df5.png)
左が元のRGBA32bit、左がRGBA16bitへ減色した結果。
かなり劣化していることがわかります。

これが実際の画像だとさらに良くないことに、、
Unity Technologies社のTech2D Packに含まれている画像を使ってみます。
![URGBA32.png](https://qiita-image-store.s3.amazonaws.com/0/80418/1f4edc9b-e647-4cf2-9924-af5f62708408.png)　⇒　![URGBA16.png](https://qiita-image-store.s3.amazonaws.com/0/80418/30f4e49b-ad87-d610-f698-193a2a99315e.png)
けっこう汚い、、なんか変な色が出ています :cold_sweat: 
そこでディザリングをおこなってみます。
ディザは減色による画像の劣化を抑えるために、減色時にノイズをのせるもので、SimpleTextureModifierはFloyd–Steinbergという誤差拡散ディザのアルゴリズムを持っているのでそれを試します。
SimpleTextureModifierがUnityに入っているなら、
![menu.jpg](https://qiita-image-store.s3.amazonaws.com/0/80418/36c2a639-740b-691c-560f-6d3e0ca3a6e2.jpeg)
画像を選択してマウス右ボタンのプルダウンで、Texture Util⇒Set Label FloydSteinbergとTexture Util⇒Set Label Convert 16bitsで、ラベルを画像に設定します。
"Set Label Convert 16bits"を指定するのはSimpleTextureModifierに16bits化減色をおこなわせるためで、[Unityに32bits⇒16bits化減色をおこなわせようとすると、ポストプロセッサに先に16bits化されたデータのほうが届いてしまい、SimpleTextureModifierが処理をおこなえないからです。](http://anolddog.blog.fc2.com/blog-entry-35.html)
続いて、右ボタンプルダウンのReimportを選択してインポートをおこなってください。
残念ながら現在のSimpleTextureModifierではラベルを設定するだけで画像ImporterのApplyボタンをEnableにはできないので、右ボタンのReimportを使ってテクスチャをインポートしなければなりません。
これはお約束なので忘れないようにしてくださいね :sweat_smile: 

![RGBCOLOR.png](https://qiita-image-store.s3.amazonaws.com/0/80418/9e396c5e-2f3f-4aed-326a-caa7f17fc23e.png)　⇒　![RGBA16.png](https://qiita-image-store.s3.amazonaws.com/0/80418/434e6310-dca9-b94c-319d-7531c6bf2df5.png)　⇒　![RGBA16Dither.png](https://qiita-image-store.s3.amazonaws.com/0/80418/246a7d0c-2e21-021f-5158-e0efa720e50b.png)
処理結果です。左から32bits元画像、16bits減色、ディザ処理つき16bits減色と並べてみました。
では16トンな絵のほうは
Unity Technologies社のTech2D Packに含まれている画像を使ってみます。
![URGBA32.png](https://qiita-image-store.s3.amazonaws.com/0/80418/1f4edc9b-e647-4cf2-9924-af5f62708408.png)⇒![URGBA16.png](https://qiita-image-store.s3.amazonaws.com/0/80418/30f4e49b-ad87-d610-f698-193a2a99315e.png)⇒![URGBA16Dither.png](https://qiita-image-store.s3.amazonaws.com/0/80418/47b8431f-4318-8922-e792-b94100070750.png)
左から32bits元画像、16bits減色、ディザ処理つき16bits減色です。
使いどころによりけりでしょうが改善はみられると思います。 :grin: 
#UnityのSprite Packerにおいても画像メモリーのサイズを減らすには?
UnityにはSprite Packerというテクスチャアトラスを作成する機能が追加されました。
これはSpriteをまとめて一枚のテクスチャにしてくれる機能で、これを使用すると描画の大幅な高速化がおこなえます。
SimpleTextureModifierはこのSprite Packerにも対応しています。
個々のSpriteにディザ処理をかけてやれば、アトラス化されたときにもディザはかかったままです。
気をつけなければいけないのは、Set Label Convert 16bitsやSet Label Convert Compressed、Set Label Convert Compressed no alpha、Set Label Convert Compressed with alphaといった画像フォーマットを変換する機能は使えなくなってしまうということです。
**UnityのFormatに画像フォーマットを指定して32bits⇒16bits化減色等はおこなってください。**
これは頭のほうでの説明と矛盾すると思います。しかしUnityはSprite Packerでアトラス化する画像データに関しては扱いが異なるのです。
UnityはSprite Packerにアトラス化の作業をおこなわせるために、Texture ImporterのPacking Tagが指定されている画像に関しては、フォーマット変換をおこなわずに一旦RGB24bitやRGBA32bitのTrueColorで画像を出力します。Sprite PackerはこのTrueColorをアトラス化してFormatに指定された画像フォーマットを元に減色や圧縮等をおこなって最終的なテクスチャとします。
仮にSimpleTextureModifierが画像のフォーマットを変更するとSprite Packerは画像をアトラス化できなくなります。また通常の画像と違いSprite Packerによるアトラス化を前提としたPacking Tagが指定された画像の場合、FormatにTrueColor以外のSimpleTextureModifierが処理できないフォーマットが指定してあったとしても、SimpleTextureModifierに渡されるのはTrueColor画像になるために問題にはならないのです。
#ディザリングの問題点
ディザリングがかかった画像の場合、拡大をおこなうとディザのパターンが見えてしまって、劣化が目立つことがあります。
![URGBA16DitherExpand.png](https://qiita-image-store.s3.amazonaws.com/0/80418/e447bdac-8881-3920-1a44-621ffb984c39.png)
これは先ほどの16トン画像の下端を4倍に拡大したものですが、デイザのパターンが見えてしまっています。
こうした画像をテクスチャとして3Dオブジェクトに貼るには適切でないでしょう。メモリー(RAM)の使用量を削減したい場合はCompressテクスチャを使ったほうがいいと思います。
UIパーツ等でも問題が発生する場合があります。
スプライトはUIパーツを表現するために"9スライス"スプライトという機能を持っています。
![9patch.jpg](https://qiita-image-store.s3.amazonaws.com/0/80418/7970c4ae-6296-1c1b-2cae-b85648659559.jpeg)
スプライトを9つのパーツに分割して個々に拡大を制御することによって、枠つきの背景といったものを実現します。
こういったスプライトにはディザリングをおこなうことはできません。
中央のパーツが上下左右に拡大、左右のパーツが縦に拡大、上下のパーツが横に拡大されることを想定しているために、通常の使用で簡単にディザリングのパターンが表示されてしまいます。
![9patchUI.jpg](https://qiita-image-store.s3.amazonaws.com/0/80418/1525df4f-8cad-d0f2-95f0-5da592590fdb.jpeg)
"9スライス"スプライトにディザリング処理をおこなうことは**不可能**ではありませんが、高度な調整が必要になります :triumph: 
ディザリング無しの減色処理をおこなわければならないと考えましょう。
解決法の一つとしては、"周辺を囲む8個のパーツ"と"真ん中の拡大されるフラットな板"に分けて別のパーツとして扱う方法があります。重ねて表示してやれば枠に囲まれた掲示板のような表現がうまくできます。そして前後左右に大きく引き伸ばされる真ん中の板のパーツのみディザリング無しの減色処理をおこなうのです。
このとき気をつけるのが外枠の画像として指定したUIパーツ用ImageのFill Centerをoffにしておいてやることです。これによりスプライト中心部の画像は、透明パーツとして物理的にもまったく描画されることはありませんから、ハードウエア描画機能を余分に使うことはありません。
またSimpleTextureModifierには"Set Label FloydSteinberg"以外にも"Set Label Reduced16bits"という機能があって、これを指定しておくとディザリング無しで、ホントにわずかではありますがUnityの16bits減色よりはましなアルゴリズムの減色をおこないます。
試してみてもいいでしょう。
#課題
SimpleTextureModifierは16bits減色以外にも[テクスチャ圧縮を使用したワークフロー](http://tsubakit1.hateblo.jp/entry/20131003/1380811651)にも対応するように作られていますが、Sprite Packerを使用した場合こういった部分がうまく働いてくれなくなってしまいます。
AndroidのETC1圧縮に関して特に顕著になりますが、Unity5.2でこのへんに修正がはいるらしいので、どんなふうになるかが今から楽しみですね :satisfied: