Unity、Mobile端末向けの画一的なテクスチャ圧縮方法

みなさんはじめまして。

 現在DeNAで開発中の、Unityを用いたアプリゲームプロジェクトにおいて、主にパフォーマンス面での改善に取り組んでいる エンジニアの内田 です。

 パフォーマンス、アプリサイズ、メモリ使用量などの改善をするにあたり、現在のプロジェクトで実際に使用した、「アルファ分離テクスチャ圧縮」という手法について、その有効性などをお伝えできればと思います。

 

目次

  •  テクスチャ圧縮とは
  •  Mobile端末向けにテクスチャ圧縮する上での問題点
  •  アルファ分離テクスチャ圧縮手法
  •  プロジェクト全体でのデータサイズ削減効果
  •  今後に向けて

 

テクスチャ圧縮とは

テクスチャ圧縮?JPEGじゃだめなの?

 なんらかの画像データを画面に表示するには、画面の表示処理を行うハードウェアが扱うことのできる画像形式に変換しなければなりません。

Fig.1 JPEG形式の画像

Fig.1 JPEG形式の画像

 JPEG画像は、iOS端末やAndroid端末に搭載されているGPUでは直接扱うことはできないため、Unityを使ってこのJPEG画像をこれらの端末に表示しようとすると、通常、RGB24bit TrueColorなどに変換されます。(Fig.2)

Fig.2 JPEG画像がRGB24bit TrueColor形式に変換された状態

Fig.2 JPEG画像がRGB24bit TrueColor形式に変換された状態

ZIPとかで圧縮すれば小さくなるのでは?

 例えば、透過部分の大きさだけが異なり、画像全体の面積としては4倍の違いがある、次のような画像を

Fig.3 512x512 RGBA 32bit TrueColor(左)と 256x256 RGBA 32bit TrueColor(右)

Fig.3 512x512 RGBA 32bit TrueColor(左)と 256x256 RGBA 32bit TrueColor(右)

 Unity5でアセットバンドル化し、LZ4というZIPに近いアルゴリズムの圧縮を適用すると、透過部分の大きさにあまり関係なく、確かに、圧縮後のデータサイズの差は、画像全体の面積の差ほど大きくはなりません。

Fig.4 LZ4圧縮したアセットバンドルのサイズ

Fig.4 LZ4圧縮したアセットバンドルのサイズ

 しかし、このアセットバンドルから画像データをメモリ上に読み込むと、LZ4圧縮されたデータは解凍処理され、画像全体の面積に比例して、メモリ使用量もちょうど4倍近く大きくなります。

Fig.5 メモリ上のサイズ

Fig.5 メモリ上のサイズ

GPUが直接扱えるテクスチャ圧縮フォーマットだとメモリに読み込んでも小さいまま

 iOS端末ではPVRTC形式で圧縮されたテクスチャを直接扱うことができるため、PVRTC形式で圧縮されたテクスチャの場合、メモリ上にロードしても、RGBA32bit TrueColorなどの形式に変換されることはなく、サイズは小さいままです。

 Fig.3の透過画像を、Unity上でiOS PlatformのCompressed形式のRGBA Compressed PVRTC 4bit形式にし、さらにLZ4圧縮をかけたアセットバンドルにすると、サイズは次のようになります。

Fig.6  CompressedのPVRTC形式のテクスチャをLZ4で圧縮したアセットバンドル

Fig.6  CompressedのPVRTC形式のテクスチャをLZ4で圧縮したアセットバンドル

 このアセットバンドルからメモリ上にテクスチャをロードしても、TrueColor形式のものと比べ、PVRTC形式のテクスチャは1/8のメモリ使用量で済みます。

Fig.7  PVRTC形式のテクスチャのメモリ上でのサイズ

Fig.7  PVRTC形式のテクスチャのメモリ上でのサイズ

 このように、iOS端末向けの画像の場合、PVRTC形式にすることでメモリ使用量を抑えられ、アプリサイズを小さくすることや、サーバーから画像データをダウンロードする際にかかる時間の短縮にもつながります。

 なお、Android端末向けの画像の場合でも、ETC1などのテクスチャ圧縮フォーマットを使用すれば同様の効果を得ることができます。

GPUが直接扱えるテクスチャ圧縮フォーマットなら描画速度も向上する

 PVRTCやETC1などのiOS端末やAndroid端末のGPUが直接扱うことのできるテクスチャ圧縮フォーマットを用いると、描画処理時にテクスチャキャッシュのヒット率が向上し、描画速度も向上します。

Fig.8 画像A

Fig.8 画像A

 例えば、上のようなテクスチャ(Fig.8)は、TrueColor形式では、テクスチャのデータはメモリ上には次のような順番で配置されます。

Fig.9 画像AのTrueColorの場合のメモリレイアウト

Fig.9 画像AのTrueColorの場合のメモリレイアウト

PVRTC形式の場合では、次のような順番でメモリ上に配置されます。

Fig.10 画像AのPVRTC形式の場合のメモリレイアウト

Fig.10 画像AのPVRTC形式の場合のメモリレイアウト

 多くのアプリゲームでは、ドローコール数を減らすために、スプライト用のテクスチャはアトラス化(パック)することが一般的ですが、次のようなシーンをUnityで構築し、簡単な実験をしてみます。

Fig.11 テストシーンの構成

Fig.11 テストシーンの構成

Fig.12 最終的な画面出力

Fig.12 最終的な画面出力

「D4」というスプライトに注目してみると、アトラス化されたテクスチャ(Fig.12)は、
 TrueColor形式の場合は次のような順番でメモリに配置され、

Fig.13 Atlas TrueColor テクスチャのメモリレイアウト

Fig.13 Atlas TrueColor テクスチャのメモリレイアウト

 このTrueColorのテクスチャ(Fig.13)は、GPUの概念的には次のように処理されています。

Fig.14 TrueColorテクスチャの描画処理

Fig.14 TrueColorテクスチャの描画処理

 「D4」のテクスチャデータは、RAMへの1度のアクセスでキャッシュに載せることのできる量は少なく、「D4」のスプライト全体を画面に表示するには、何度もRAMにアクセスしなければならず、描画処理に時間がかかります。

 一方で、PVRTC形式の場合は次のような順番でメモリに配置され、

Fig.15 Atlas Compressedテクスチャのメモリレイアウト

Fig.15 Atlas Compressedテクスチャのメモリレイアウト

このPVRTCのテクスチャ(Fig.15)は、GPUでは次のように処理されます。

Fig.16 PVRTCテクスチャの描画処理

Fig.16 PVRTCテクスチャの描画処理

 キャッシュの容量次第ではありますが、RAMへの1度のアクセスで、キャッシュに載せることのできる量がTrueColorの場合よりも多くなり、RAMから「D4」のテクスチャデータを取得するのにかかる時間を大きく短縮することができます。

 実際にiPhone5の実機上で、XCodeを用いて負荷を見てみると、TrueColorでは、GPUの処理時間が2.6msかかっています。

Fig.17 TrueColorの負荷

Fig.17 TrueColorの負荷

 PVRTCの場合は、GPUの処理時間が2.0msとなり、TrueColorより速くなっています。

Fig.18 PVRTCの負荷

Fig.18 PVRTCの負荷

 このように、計算機のキャッシュシステムと相性のよいアルゴリズムやデータ構造を、「Cache-Oblivious Algorithum, Cache-Oblivious Data Structure」などといい、アルゴリズムやデータ構造を考える上で非常に重要なポイントになります。

 

Mobile端末向けにテクスチャ圧縮する上での問題点

先述のとおり、テクスチャ圧縮には様々なメリットがあるのですが、一方でいくつか問題もあります。

iOSのPVRTCの問題

 iOS端末向けのPVRTCは非常に圧縮率が高いのですが、透過画像に適用すると、透明部分と不透明部分の境界付近などで画像の劣化が顕著に見られるようになってしまいます。

Fig.19 透過画像をPVRTC形式にした場合

Fig.19 透過画像をPVRTC形式にした場合

 透過部分のない、つまり、A(アルファ)チャンネルのないRGBだけの画像の場合は、比較的劣化は少なく済みます。

Fig.20 不透明画像をPVRTC形式にした場合

Fig.20 不透明画像をPVRTC形式にした場合

Androidの多様なGPU、テクスチャ圧縮フォーマットの問題

 多様な種類のGPUが存在するAndroid端末で、共通して扱えるテクスチャ圧縮フォーマットとしてETC1がありますが、ETC1は透過画像には対応していません。

 UnityのHardware Staticsを見てみると、Andoridでのテクスチャ圧縮フォーマットのシェア状況が見えてきます。

GPUベンダ シェア 主な圧縮フォーマット
ARM 46.6 % ETC1、ETC2、ASTC
Qualcomm 38.6 % ATC、ETC1、ETC2
ImgTec 7.7 % PVRTC、ETC1
Vivante 3.2 % DXTC、ETC1、ETC2
Broadcom 1.9 % ...
NVIDIA 0.8 % DXTC、ETC1
Intel 0.6 % DXTC、ETC1
Other 0.5 % ...

Table.1 Unity Hardware Staticsによる2016.6時点のAndroidのGPUベンダのシェア状況
http://hwstats.unity3d.com/mobile/gpu-android.html )

 

圧縮フォーマット 透過画像対応
ETC1 x
ETC2
ATC
PVRTC
DXTC
ASTC

Table.2 各フォーマットの透過画像対応の可否

 これらの様々な圧縮フォーマットの中でも、ETC2やASTCが、透過画像も含めてAndroidで共通してを扱えるテクスチャ圧縮フォーマットとして提案されていますが、少なくともOpenGLES3.0に対応した端末でなければこれらのフォーマットは使えません。

GraphicsAPI シェア
OpenGLES2.0 / DX9 SM2 44.4 %
OpenGLES3.0 / DX9 10level9 33.7 %
DX11 21.7 %
Other 0.1 %

Table.3 Unity Hardware Staticsによる2016.6時点のAndroidでのGraphicsAPIシェア状況
http://hwstats.unity3d.com/mobile/gpu-android.html

 OpenGLSE2.0までしか対応していない端末のシェアは、2016.6時点でも44.4%あり、ETC2やASTCを共通圧縮フォーマットとして使うのはまだ暫くは厳しそうです。

 このように、Androidでは多様なGPUが存在し、それらのGPUの種類毎にアセットバンドルを用意するという方法も考えられますが、運用コストが懸念されます。

 

RGBA16bitの問題

 RGBそれぞれ4bitづつ、各成分ごとに16段階しか色の差をつけれられないため、グラデーション画像を表示しようとすると、グラデーション部分に筋や線のような模様が見られるようになってしまいます。(Fig.21

Fig.21 TrueColor と RGB16bitのグラデーション表示

Fig.21 TrueColor と RGB16bitのグラデーション表示

 RGB16bitにディザ処理をかけることで若干軽減はできますが、それでもグラデーションの荒さはまだ目に付きます。(Fig.22)

Fig.22 RGB16bitにディザをかけた場合のグラデーション表示

Fig.22 RGB16bitにディザをかけた場合のグラデーション表示

 PVRTCやETC1では、アルファチャンネルがなければ、グラデーション画像も綺麗に表示することができます。(Fig.23)

Fig.23 PVRTCとETC1でのグラデーション表示

Fig.23 PVRTCとETC1でのグラデーション表示

 グラデーション表示の問題もさることながら、RGBA16bitでは、RGBA32bitに比べてデータサイズが半分にしかなっておらず、PVRTC4bitと比べると4倍大きいことになります。

 

アルファ分離テクスチャ圧縮手法

アルファ分離テクスチャ圧縮とは

Keijiro TakahashiさんのGithub上で紹介されていた手法 ( https://github.com/keijiro/unity-alphamask )ですが、

Fig.24 RGBAの元画像

Fig.24 RGBAの元画像

上記、Fig.24 「RGBA」の画像を、

  •  「RGB」だけのテクスチャ(RGB = 元画像のRGB) …下記、Fig.25(左)
  •  「A(アルファ)」だけのテクスチャ(RGB = 元画像のAAA) …下記、Fig.25(右)

の2枚のテクスチャに分離して、

Fig.25 分離後のRGBテクスチャとアルファテクスチャ

Fig.25 分離後のRGBテクスチャとアルファテクスチャ

 それぞれ別々にPVRTCやETC1のRGB Compressedの圧縮を適用し、画面に表示する直前のShader上でこれら2枚のテクスチャをRGBAに合成するという方法です。

iOS Android
RGB テクスチャ RGB Compressed PVRTC 4bit RGB Compressed ETC1 4bit
アルファ テクスチャ RGB Compressed PVRTC 4bit RGB Compressed ETC1 4bit

Unity5の標準機能のETC1 SplitAllphaの問題

 Unity5の標準機能で「Compress using ETC1 (split alpha channel)」というものがありますが、これはSpritePackerでパックする画像に対してしか適用できず、パックせずに画像単体で扱いたいキャラクター画像などに対しては使うことができず、また、iOSには対応していません。

 そこで、現在のプロジェクトでは、アルファ分離テクスチャ圧縮に対応した、画像アセットのUnityへの組み込みパイプラインを独自に構築し、さらに、SpitePakcerでパックするスプライト画像やuGUIでのシーン作成においても、iOS、Androidプラットフォームで共通して使える、新しいアルファ分離テクスチャ圧縮機能を開発しました。

Fig.26 UnityEngine.UI.RawImageでアルファ分離テクスチャ圧縮機能を使用している様子

Fig.26 UnityEngine.UI.RawImageでアルファ分離テクスチャ圧縮機能を使用している様子

 アルファ分離テクスチャ圧縮を使った表示では、RGBA PVRTC 4bitより大きく画質が改善されていることが確認できます。

Fig.27 アルファ分離テクスチャ圧縮の画質

Fig.27 アルファ分離テクスチャ圧縮の画質

アルファテクスチャのサイズを小さくする

 データサイズは小さいほど、アプリサイズやメモリ使用量は小さくなり、アセットのダウンロード時間も短く、CPUやGPUでデータを処理する際にもキャッシュヒット率が向上し、処理が速くなる可能性があります。

 例えば、アルファチャンネルの大部分がグラデーションでできているような画像などでは、アルファ分離後のアルファテクスチャをサイズダウンして、バイリニアフィルタで拡大して使用する、というようなことも十分考えられます。

Fig.28 アルファテクスチャだけサイズダウンした状態

Fig.28 アルファテクスチャだけサイズダウンした状態

 Fig.25の画像をアルファテクスチャだけ128x128にサイズダウンしたものと、256x256のままのものを比較してみると、

Fig.29 アルファテクスチャを1/4にした場合の画質

Fig.29 アルファテクスチャを1/4にした場合の画質

 これでもまだ RGBA PVRTC 4bitよりも表示品質は高いと言えるのではないでしょうか。

 このアルファテクスチャを、縦横1/2、合計1/4にサイズダウンすることによるデータサイズの削減率は、メモリ上のサイズベースで、37.5%にもなります。

 

プロジェクト全体でのデータサイズ削減効果

現在のプロジェクトのUnityEditorのAssetDatabase内のテクスチャの数とサイズの状況です。

Texture 総数 2688
NPOT Texture 数 1043
Size16 667
Size32 63
Size64 206
Size128 497
Size256 531
Size512 582
Size1024 99
Size2048 43
Size4096 0

Table.4 実際のプロジェクトでのUnityEditorのAssetDatabase内のテクスチャの数とサイズ

「NPOT(None Power of Two)Texutre数」は「Texture 総数」に含まれています。
Size ~ はそのサイズに近いテクスチャの数です。

 このプロジェクトのテクスチャ全てに対して、実際にアルファ分離テクスチャ圧縮を適用した場合のデータサイズの削減率です。

合計サイズ RGBA 32bit 比の削減率
RGBA 32bit 1758 MiB 0 %
RGBA 16bit 879 MiB 50 %
RGBA PVRTC 4bit 229 MiB 87 %
RGBA ETC2 8bit 431 MiB 75 %
Split Alpha
(iOS)
272 MiB 85 %
Split Alpha
(Android)
249 MiB 86 %

Table.5 実際のプロジェクトのテクスチャ全てを各形式にした場合の合計サイズと削減率

 NPOTの画像には、NPOTScaleをToNearestで適用しています。

「Split Alpha」のiOSとAndroidの合計サイズの違いは、NPOTScaleをかけると、iOS(PVRTC)では正方形になるが、Android(ETC)では長方形にもできるため、その分iOSよりも小さくなっています。

 アルファチャンネル分離後の一部のアルファテクスチャは、元画像の解像度から縦横それぞれ1/2ずつ、面積的には1/4にサイズダウンしている状態です。

 

今後に向けて

 Mobile端末の性能が向上し、高品質なアプリゲームの開発が可能になってくると、より一層テクスチャ圧縮の重要性は増してくるのではないでしょうか。

 今回行ったアルファ分離圧縮は、多様なMobile端末に対して画一的に対応できることが大きなメリットでしたが、MetalやVulkanといったGraphicsAPIに対応した端末が普及してくれば、アルファ分離テクスチャ圧縮などといったややこしいことをせずとも済むようになるかもしれません。

 一方で、比較的低コストで作れる、旧世代のGraphicsAPIまでの対応にとどまっているGPUを搭載したMobile端末も、世界的にみると、まだ暫く需要が続くことも予想されます。

 プロジェクトが対象とするプラットフォームや運用コスト、ゲームの絵作りの方向性などを考慮して適切な方法を選択をすることが重要ですが、2Dベースで透過画像表示を多用するようなアプリゲームにおいては、このアルファ分離テクスチャ圧縮手法はまだまだ有効な手法であるのではないかと思います。