神様は有休消化中です。

Unity関連の技術ネタを書いてます。

【Unity】uGUI ImageでAlpha Maskを使えるように実装する方法

はじめに

この記事は、先日公開した以下のリポジトリの技術解説です。
使ってみたいという方は、ぜひ使ってみてください。
github.com

概要

Unity uGUIでMask表現を行う場合、標準で実装されているMaskコンポーネントを使用すると、境界にひどいジャギーがでることがあります。
f:id:appleorbit:20180728183724p:plain

Unity標準のMaskコンポーネントはステンシルでクリップを行うため、斜めのラインにエイリアスが出てしまうことが原因です。
Mask画像にアルファ値を仕込んでも、ステンシルに書き込まれる際に描く or 描かないの2値情報になってしまうためです。

高解像度の環境であればあまり問題にならないのですが、低解像度環境であったり、モバイルのようにパフォーマンス上の問題で解像度を下げる場合に問題が表面化します。
マスクのアルファ値によって、マスク対象のアルファ値を操作することができれば、マスク画像の境界にアルファフェードをかけることでこのジャギーを目立たないようにすることができそうです。

実装上のハードル

実現したいことは「uGUI-Maskと同じインターフェースでAlpha Maskを実現したい」です。
しかし、これが結構面倒臭い。
マスクとマスク対象が1対1の関係で、座標が絶対に動かないのであればシェーダーで楽に実装できますが、1対多の関係に加えてマスクもマスク対象も自由に動くとなると途端に難しくなります。例えば下図のような場合です。
f:id:appleorbit:20180728185920p:plain
この場合、求める動作は「赤丸とマスクの白い部分が被っているピクセルだけ、画面に表示される(黄色い丸は表示されない)」となります。

考え方

マスク対象の頂点を描画する際に、その頂点位置のマスクUVを求めることができれば、UI/Defaultを改造した以下のようなシェーダーでAlpha Maskを実現できそうです。

つまり、何らかの方法でマスク対象の頂点座標を、マスクのUV座標に変換してやる必要があります。
f:id:appleorbit:20180728201228p:plain

実装方法

画面内のマスクのみを映すVP行列を作る*1

uGUI-Imageのポリゴンは必ず四角形なので、その四角形をスクリーンと見立てたVP行列を作成できれば、頂点座標から特定ポリゴンのUVを求めることができます。
マスクのImageコンポーネントでタイリングやスライスは使えなくなりますが、諦めることにします。

Unityでは、Matrix4x4.Ortho関数を使って、Orthograohicなプロジェクション行列を生成することができます。
docs.unity3d.com

この関数の引数に渡すleftなどは、以下の座標系での値です。*2
f:id:appleorbit:20180728204453p:plain

この座標系で、マスクのuGUI-Imageの矩形を定義します。
具体的にはuGUI-Imageの4角のワールド座標を取得してスクリーン座標に変換した後、正規化してcamera.orthographicSizeの座標系に変換します。

ここまでできれば後は簡単で、VP行列を生成してマテリアルにSetMatrixしてやればCPU側の処理は完了です。
TRS行列をかけているのは、-1〜1の範囲で取得した値を0〜1の範囲に変換するためです。

対応する頂点シェーダーで、先ほど作成したVP行列からUV座標を計算する処理は以下です。

結果

これで、uGUI-ImageでAlpha Maskを使えるようになりました。
Unity標準のMaskよりも、Alpha Maskの方が境界線が綺麗です。
f:id:appleorbit:20180728205432p:plain