あまブロ

UnityとかWebプログラミングとか

floatの値が0〜1の範囲内に収まっているかif文を使わずに判断する

どんなシェーダというのは書きませんが...

フラグメントシェーダ(ピクセルシェーダ)内で、
floatの値(例としてx)が0〜1ならA、それ以外ならBを返す処理が欲しかった。

通常なら、

if(x >= 0.0f && x <= 1.0f) {
     return A;
} else {
     return B;
}

これで終了なのですが、ピクセルの分だけループするので条件分岐を使うのは避ける必要があり...
小難しいことを考えるのが苦手な僕は、

迷った。

まず辿り着いたのがxを-0.5した絶対値を四捨五入する方法

int isInRange = 1 - (int)(abs(x - 0.5f) + 0.5f);

abs(x - 0.5f) のところで絶対値が0.5に満たない場合は、+0.5しても1にならないので、
int型にキャストすることで0か1に分けることができます

でもこれ、

abs関数の中でif文使ってるんじゃね?

という問題があることに気がついてしまった
組み込み関数なので中で何やってるか分かりませんが、

if(x < 0) {
     return x * -1;
} else {
     return x;
}

もし内部がこんな感じだったら意味ないじゃん。
ということで最終的に辿り着いたのがこれ。

int isInRange =
     (1 + (int)(x - 1.0f)) *
     (1 - (int)x);

というようにマイナス方向とプラス方向に分けてチェックして、
掛け合わせればなんとかなった

ちなみに以下のパターンは今回必要なかったので考慮していません
・x <= -1
・x >= 2
必要な場合はmodとか使うかabs使う方法で妥協する必要がありそうですね

あとは線形補間(lerp)で目的の値を取得するだけ!

return lerp(B, A, isInRange);

もっといい方法あったらぜひ教えて欲しいです...

(2016-10-07 追記)

x <= -1とx >= 2のケースまで対応できて分かりやすい方法教えて頂いたので紹介します!

int v = (int)((x - 0.5f) * 2);
float isInRange = (float)v / (v - ε);

εはイプシロン定数(CのFLT_MIN、Unityのfloat.Epsilonなど)限りなく小さい値
シェーダ言語にはなかったりするので、自前で用意する必要はあるかも(0.00....001fとか適当に定義するだけでOK)

上に書いたlerpで使うような値なら、厳密に0 or 1じゃなくていいのでこれで十分対応できそうです