モバイルではclip()やdiscardを多用しないほうがいいという話を聞きます。
これに関連して、タイルベースレンダリングを概要からまとめてみました。
タイルベースレンダリング?
タイルベースレンダリングというのは画面のピクセルを一定の大きさの領域に切り分けて、その単位で処理を行うレンダリング方式のことです。
PowerVR - embedded graphics processors powering iconic products より引用
この一つ一つのグループが「タイル」というわけです。
レンダリングを行うときには深度を使って一番手前にあるポリゴンを割り出し、重なっているポリゴンを処理しないようにしますが、
タイルベースではタイル単位の処理に最適化されたチップを使ってタイルごとにこれを行うことで高速化します。
この結果タイルベースレンダリングでは、ピクセルごとに最終的に描画されるポリゴンがあらかじめ決定されるようです。
フラグメントを破棄する処理が重い?
さてこのタイルベースレンダリングではフラグメントを破棄する処理(すなわちclip()やdiscard)が高負荷になるといわれます。
この理由については調べてもしっかりとは理解できなかったのですが、
本来テクスチャアクセスが必要ないのに、Cutoutがあるとやる羽目になり、みたいな話っぽい
【コラム】Unityキセキの世代の皆さんが(モバイルの)半透明のパフォーマンスについて知るべき事 - Unity より引用
ということが原因になるようです。
想像ですが・・
前述の通りタイルベースレンダリングでは、深度を使うことによりそのピクセルに描画されるポリゴンを一つに決めてから描画を行います。
破棄されるピクセルは深度が書き込まれないわけだから、描画するピクセルを決める際に深度を使うときに、まず破棄されるかどうかの条件をチェックする処理をしなければいけません。
つまり「描画ポリゴン決定時」と「描画時」の二回、この破棄されるかどうかの条件をチェックする処理を行わなければならなくなる、ということでしょうか。
まあでも、Unityのフォーラムでもこれは議論された形跡がありましたが、
I think it suffices to say that discarding fragments of opaque geometry is very expensive on tile-based deferred renderers.
https://forum.unity.com/threads/clip.257310/ より
と中の人が言っているので、「不透明ジオメトリでフラグメントを破棄するのは高価」とだけ覚えておけばよさそうです。
タイルベースのデバイス多いの?
ではタイルベースってどんなデバイスで採用されているのか?という話ですが、下記のとおりiOSはいずれもタイルベースのようです。
iOSデバイスのGPUはいずれも、タイルベースの遅延レンダリング(TBDR、Tile-Based Deferred Rendering)を実装しています。 日本語ドキュメント - Apple Developer より
Androidは端末によるみたいですが、モバイルアプリを開発する以上タイルベースレンダリングを意識する必要はありそうです。
ちなみに上記のTBDR(Tile-Based Deferred Rendering)はフォワードレンダリングとディファードレンダリングのディファードとは無関係です(非常に紛らわしいですね・・)。
"Tile-based deferred" GPU architectures are a completely different aspect from Unity's "deferred render path".
https://forum.unity.com/threads/clip.257310/ より
対応策
さて、モバイルでは不透明パスにおけるフラグメントの破棄に気を配る必要があることはわかりました。
では具体的にどうアクションすればいいのでしょうか?
まず、前提としてclip()やdiscardを書くようなシェーダはAlphaTestのQueueで行いましょう。
次に、できる限りアルファに沿ったポリゴンを用意してしまうという手もあります。
なおフィルレートが問題になりやすい昨今の環境では、テクスチャをQUAD(板ポリ)に貼るより、アルファに沿って綺麗に切り抜いたポリゴンを表示した方が高速と言われています。 Cutoutが最近のGPUでは相性が悪いのも、こう言われてる要因の一つです。 【Unity】SpriteをMeshとして利用可能にする - テラシュールブログ より引用
あとは、ケースバイケースなので最適化段階での話だとは思いますが、
思い切って半透明描画にして、clip()やdiscardの処理を消してしまうのも手かと思います。