面白い議論が上がっていたので、少し試しにやってみました。
顔だけアイコンでマスクをするのは勿体無い
ゲームでUIを見る時、立ち絵の一部を切り取ってバストアップのアイコンにするというのはよく見ます。
で、例えば上のような画像をuGUIで作ろうと思った時に、真っ先に思いつくのはMaskやRectMask2Dを使用するアプローチでしょう。このアプローチならば、立ち絵から一部を切り出して表示することは簡単に実現出来ます。
ただしマスクを使用した画像の切り抜きは殆どのケースでバッチを破壊しますし、それ自体割と重い処理でもあります。トップの画像は各キャラクター毎にマスクで切り抜きしていますが、これを表示すると描画命令が15回程になりました。また、マスクで表示されない箇所は表示されないだけで透明が塗られている状態です。場合によってはステンシルが塗られます。これが広範囲になると余り宜しくない現象になります。
とは言え、テクスチャを別途用意するのは少し勿体無い気もします。一覧表示のUIは兎も角、ちょっとした会話シーンなどでMipmapのように複数のテクスチャを用意しても、使用するのはほぼ同じテクスチャです。
バストアップのスプライトを用意する
ということで、立ち絵全体のテクスチャからキャラクターの一部しか表示しないスプライトを生成し使用するアプローチです。今回の場合、全体像のスプライトと同時にバストアップだけのスプライトです。
最終的には、下のような感じになります。キャラクターのバストアップを表示する事は変わりませんが、描画面積が大幅に縮小されています。またマスクを排除した事で各要素を跨いだバッチングが可能になり、描画命令が15から5まで小さくなりました(要素の背景1、文字列1、キャラクター3)。
勿論、バストアップ用と全体像の2つのテクスチャを用意するといった事はありません。
やり方
この考え方は至極単純で「1枚のテクスチャから複数のスプライトを切り出せるなら、範囲がかぶるスプライトを作っても良いよね!」という方針です。
実際の手順を見ていきます。 一枚のテクスチャから複数のスプライトを作成
まず最初にやることは、Sprite Mode(スプライトモード)をMultiple(複数)に変更します。これでテクスチャから一部をスプライトとして取り出す事が出来るようになります。
次にSpriteEditorで顔の部分だけを切り出します。ドラッグ&ドロップで範囲を指定するんですが、アイコンのような固定サイズの物は数値で幅と高さを設定し、後は切り出す範囲を指定する方がワークフロー化しやすいかなと思います。
スプライトは範囲が被っても問題なく作れるので、全体像は全体像で別途範囲を囲って作るのが良いです。 うまく作業出来れば、下のように顔だけと体全体を表現するスプライトが生成されます。
あとは普通に切り抜いたスプライトをUIに使用し、場合によってはImageのUseSprite
を使用してポリゴンを切り抜けば、マスク無しでアイコンを作ることが出来ました。
オマケ:ポリゴンの切り抜き
さて、立ち絵の方は透明を描画している範囲が広くて無駄に見えます。また、切り抜く画像が矩形ではなく円の場合も、出来れば切り抜く形を決めたいと思うかもしれません。
なので矩形ではなくポリゴンで表現して無駄な範囲を削ります。ちなみに個人的には立ち絵を表現する場合はUIではなくSpriteRendererの方が良い気もしますが、コンテンツによってはUIで表現するほうが後腐れ無くて良いというケースもありそうです(例えばゲームを一時停止して会話シーンを挟むような場合)
以前は矩形しか使えないのでポリゴンを自作して流し込む(遅い)必要がありましたが、2018.3からUI自体がSpriteのポリゴンをサポートしたため、割と簡単に使用できるようになりました。これを利用するためにはスプライト自体もポリゴンでくり抜く必要があります。特に何もせずとも透明の情報をもとにある程度のくり抜きが設定されていますが、もっと透明を削りつつポリゴン数も削りたいので、ある程度手動で設定していきます。
まずCustom outline
を選択します。これでスプライトを編集するモードに切り替わります。ポリゴンを調整するスプライトは、プロジェクトビューで選択するのが一番手っ取り早いです。
ポイントは枠外をクリックして一旦フォーカスを外す事ですね。何もフォーカスしてない状態から、最初にクリックしたスプライトを良い感じに選択してくれます。
— 椿 (@tsubaki_t1) 2019年1月24日
面倒くさいならProjectViewでスプライトを選択すれば選択したスプライトが編集出来ます。 pic.twitter.com/R4nSJl6AEE
あとはOutline Tolerance(輪郭の許容)
を調整大きければGenerate
を押します。後は自動生成されたアウトラインの内で理にかなっていない場所を調整すれば完了です。コツは頂点を作る際に、即座にドラッグするのではなく一旦クリックして頂点を確実に作る事です。ドラッグすると稀に他のスプライトを選択してしまうケースがありますが、一旦クリックして頂点を確立してしまえば他のスプライトは余り選択しなくなります。
立ち絵を比較すると、ポリゴン数はそこまで変わってませんが綺麗に切り抜けました。
たぶん、こういう手順は無敵にディープラーニング先生がそのうち何とかしてくれる気がしますが、今はアルゴリズムか人類の仕事です。現状のベターなアイディアは、法則を知って応用することでしょう。
アウトラインの調整はテクスチャの解像度に依存します。つまり解像度の高いテクスチャは積極的に非常に細かく分割されます。下の図では解像度を128に変更してから自動生成すると、ある意味期待通りのポリゴンを切り抜いてくれました。これを生成後に512や2048に戻してやれば、切り詰めすぎないポリゴンが作れます。
ただし同時に「テクスチャを切り詰めない=透明なピクセルが広い」という意味でもあります。テクスチャ解像度が高い場合はポリゴンを増やしてでも透明を切り取るスタンスは、間違っていないようにも思われます。
ただし、今回のような「UIに使用する」場合には頂点が増えれば増える程にジオメトリ生成の時間が増える点に注意する必要があります。処理負荷がメインスレッドでは無いので観測しにくいですが、ジオメトリのリビルド完了までレンダースレッドが待機する場合もあるので、正直アイコンのような細々としたものには使うべきではないと個人的には思います。
オマケ:Atlasを使用する場合での注意
このアプローチの問題はSpriteAtlasでパッキングした際に、複数の画像が作られてしまうという点です。SpriteAtlasはスプライトの参照元を余り気にせず、ポリゴンとポリゴンが使用しているテクスチャを1枚のテクスチャに配置していきます。なので、今回のアプローチで生成したスプライトをSpriteAtlasに通すと、全体の立ち絵と表情アイコンの2つが作られます。
ただ、これは特に問題がないとも言えるかもしれません。SpriteAtlasによるパッキングはマテリアルを統合しドローコールを下げる事が機体出来ますが、同時に「1キャラでも表示したければ全てのキャラクターの立ち絵をロードし無くてはならない」事、そして「アプリのコンテンツを更新したときにバッチングが途切れるかテクスチャをしなくてはならない」という面倒くさい事を引き起こすためです。
逆にアイコンを大量に描画したい…というときはバッチングが推奨されますが(実際には小さいアイコン画像を別途作ってロードする所が多いんじゃなかろうか)、そちらは正直な所「SpriteAtlasを使用してアイコンのみをまとめたテクスチャ」を別途作るべきでしょう。立ち絵は非常に大きなテクスチャですので、大量に表示するように引っ張り出すのは理に適っていません。
関連
バッチングやドローコール、フィルレートについて云々
www.slideshare.net
UIのuseSpriteについて。2018.3から使用可能
Unity 2018.3未満の場合、ポリゴンを削る際にはこんなアプローチもある