【Unity】【シェーダ】シェーダにおいてif文は悪か

シェーダではif文が避けられる傾向にあります。
ただどういうケースでなぜ避けるべきなのか、ということについては耳にする機会があまりなかったので調べてみました。

if文の処理負荷

GPUがシェーダを処理するとき、頂点ごとやピクセルごとに同じような処理をたくさんするわけですが、基本的には入力値を変えただけの同じ命令を呼び出しているそうです。
同じ命令なので素早く処理することができます。

ここでif文による分岐があると、どの分岐に入るかによって処理すべき命令が変わってしまいます。
よってそのたびに命令を変更しなければならず、処理負荷が大きくなります。

if文が害にならないケース

分岐による命令の変更が処理負荷を大きくする原因だとすると、分岐のうちどちらかにしか入らないことが決まっていれば、if文を使っても命令を変更するコストがかからないと言えそうです。
例えば、次のようなケースです。

  1. 条件式に使われている値がstaticなものである
  2. 条件式に使われている値がuniform変数である
  3. 動的な判定であっても一回のレンダリングで必ずどちらかの分岐にしか入らない

実際には、これらのケースで処理負荷が増大しないかどうかは言語のバージョンにより変わるようです。
モバイルでいうと、ES2.0は1.は最適化されるものの、2.は最適化されない端末もあります。
ES3.0以降は2.も問題なく最適化され、さらに端末によっては3.も最適化されるようです。

この辺りの情報は下の記事を参考にしています。

stackoverflow.com

if文と条件演算子の処理負荷の違い

ここまでif文の処理負荷について見てきましたが、if文と条件演算子による処理負荷の違いもあります。

まず下記のようなif文を使った処理があるとします。

if (color.a > 0.5) {

 color.r += 0.5;

}

else {

    color.r -= 0.5;

}

条件演算子を使って下記のように書くと、これと同等の結果が得られます。

color.r += color.a > 0.5 ? 0.5 : -0.5;

上記二つの式は結果は同等ですが、計算過程が異なります。
まずif文は最初に条件式を評価して、分岐のどちらか1つだけを処理します。
これに対して条件演算子では両方の分岐内の処理をしてから条件を評価して適切な方を返します。

これだけ聞くとif文がいい気がしますが、中の処理が軽いものであれば、条件演算子の方が処理負荷が小さくなるようです。

Most of the time you'll want to branch, but for especially cheap calculations the branch itself might be more expensive than just doing both.
https://forum.unity.com/threads/correct-use-of-unity_branch.476804/ より

UNITY_BRANCHとUNITY_FLATTEN

一部の古い端末では、if文と条件演算子コンパイル時に最適な方に変換されます。
ただ場合によっては確実にif文として取り扱いたい場合などもあると思います。

これを行うためのマクロがUNITY_BRANCHとUNITY_FLATTENです。
if文の前につけるとコンパイル時に強制的にif文にしたり条件演算子として処理したりします。

ただしこれはHLSLのみ有効なマクロで、OpenGL ESでは単純にif文はif文として書き、条件演算子は条件演算子として書けばOKです。

docs.unity3d.com

参考

stackoverflow.com

https://forum.unity.com/threads/correct-use-of-unity_branch.476804/

docs.unity3d.com