ASTCテクスチャが一部のAndroid端末で透ける問題について

# はじめに
Happy Elements Advent Calendar 2020 6日目の記事です。

Unity でのテクスチャフォーマットに関する記事です。テクスチャ圧縮のひとつであるASTCは圧縮率が高く綺麗なフォーマットですが、リニア色空間のプロジェクトでは一部の Android 端末でアルファ値がわずかに低下する問題があります。この問題の原因はGPUにあるようなので、今後の Unity エンジンの更新で問題が解決される可能性は低いです。そのため、アルファブレンディングかつ透けるとまずい部分には Android 向けにASTCテクスチャを使わないことをおすすめします。

# ASTCフォーマットについて
テクスチャのインポート設定において、プラットフォームごとのフォーマット指定をする欄で `RGB(A) Compressed ASTC NxN block` という選択肢が用意されています。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38700/ca02b9cd-98ce-999b-4afc-e01b84a82c97.png)

4x4 〜 12x12 の決まった大きさのブロック単位で圧縮を行い、ブロックには固定のビット数が割り当てられているので、ブロックが大きいほど圧縮率が高くなります。無圧縮と見分けがつかないほど綺麗な 4x4 でも圧縮率は8bit/ピクセルであることから、効率がよく、なおかつ圧縮率の微調整が可能な非常に使い勝手の良いフォーマットです。アルファチャンネルにも対応しています。[^1]

[^1]: Unity 2018 まではインポート設定が RGB と RGBA で分かれています。RGB を選択するとアルファチャンネルが無視されて全体のアルファ値が1となりますが、その場合もこの記事の問題は回避できないのでご注意ください。問題が発生すると、全体的にわずかに透明になります。

また、プラットフォームの対応状況についても近年対応端末の割合が高くなってきています。iOS は iPhone6 以上、Android は OpenGL ES 3.2、OpenGL ES 3.1+AEP 対応GPUまたは OpenGL ES 3.0 対応GPUの一部となっており、数年以内に発売された主要な種類のGPUは概ね対応していると考えて良さそうです。

# 問題の発生状況
以下の条件をすべて満たすとき問題が発生します。

* Adreno GPU 搭載端末（実機）
* プロジェクト設定はリニア色空間
* テクスチャフォーマットは任意のブロックサイズのASTC
* テクスチャ自体の色空間はsRGB

ビルドされたアプリに同梱されたアセットかアセットバンドルかの違いや、グラフィックAPIが OpenGL ES か Vulkan かの違いは関係ないようです。

例として次の画像を RGBA 32bit、ASTC 4x4 block の2種類のフォーマットでインポートし、適当な模様の上にアルファブレンドで描画してみましょう。元画像の円盤内部はアルファ値が1となっています。

![texture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38700/b7ebcede-e23f-eae6-86f5-746383c2c29d.png)

RGBA:
![0_RGBA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38700/f0156dee-e0e2-5012-1075-6daf78246984.png)

ASTC:
![0_ASTC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38700/40f75628-fc9d-d917-262d-6809d4b6bcc7.png)

このように、ASTCでは特に暗い色の円盤が透けて後ろの模様が見えてしまっています。[^2]

[^2]: リニア色空間の時のみ透ける、暗い色ほど透けるなどの性質は、リニアで計算された色が画面に表示される際ガンマに変換されることを考慮すると説明できそうです。

モバイルアプリでアルファブレンドが多用されるところとして、UI、2Dアニメーション、パーティクルシステムなどが挙げられます。このうちUI、2Dアニメーションについてはアルファ値が1の部分は後ろが透けて見えない前提で制作することがほとんどだと思うので、パーツの接合部分その他見えてはいけないものが見えてしまうなど、問題が顕在化してしまうことでしょう。

なお、3Dモデルのアルベドテクスチャとして使う場合など、不透明シェーダーで描画するときは当然透けることはありません。

# 対処法
これからテクスチャフォーマットを決める段階であれば、Android ではUIや2Dアニメーションなどに ASTC を使わないというのが無難です。代わりに ETC2 を検討すると良いでしょう。なお、ETC2 には画像サイズが4の倍数でなければならないという制限があるので注意してください。既に ASTC でビルドしたアセットを無差別に ETC2 で再ビルドするのは危険です。

もしも既に大量のテクスチャをビルドしてしまっていて変更が難しければ、以下のような対処法も考えられます。

* 不透明マテリアル/シェーダーに変更する
  * アルファブレンディング自体が不要な場合は `Unlit/Texture` のような不透明なマテリアルに変更すると良いでしょう。ただし描画順の制御的に uGUI との相性は悪いです。
  * uGUI の中で不透明で描画したい場合（背景を1枚の画像が覆うUIなど）、uGUI のデフォルトマテリアルではなくテクスチャのアルファ値を無視するシェーダーを作成して設定すると良いかも知れません。
* フラグメントシェーダー内でアルファ値を少し大きくする
  * 非常に場当たり的な方法ですが、アルファ値を 1.01 倍くらいすれば、低下したアルファ値を回復させることができます。

# Issue Tracker
Unity にバグ報告しましたが、問題の原因がGPUにあるということで、Unity 的には仕様ということになりました。

[Unity Issue Tracker - Android The alpha channel value of a ASTC Compressed sRGB textures is always slightly less than 1.0 Adreno GPU&#39;s](https://issuetracker.unity3d.com/issues/android-astc-compressed-texture-becomes-transparent-with-noticeable-artifacts-when-alpha-power-is-set-to-100-on-adreno-gpus)

# メンバー募集
Happy Elements株式会社 カカリアスタジオでは、
いっしょに【熱狂的に愛されるコンテンツ】をつくっていただけるメンバーを大募集中です！
もし弊社にご興味持っていただけましたら、是非一度
下記採用サイトをご覧ください。
[Happy Elements株式会社 採用特設サイト](https://recruit.happyelements.co.jp/)