条件分岐のためにstep関数を使う時の考え方をまとめてみた

  • 28
    いいね
  • 0
    コメント

GLSLでは条件分岐をする際にif/else構文を使うと処理負荷が高いと言われています。
(その分だけ暖を取りやすくなるかもしれませんが・・・?)

そこで、最初に考えつく代替案は三項演算子ではないでしょうか?

vec3 BLACK = vec3(0.0);
vec3 WHITE = vec3(1.0);

vec3 color;

// こんなif文は、
if (x < 0.5) {
  color = WHITE;
} else {
  color = BLACK;
}

// こんな三項演算子に置き換える
color = (x < 0.5) ? WHITE : BLACK;

こんな感じでif/else構文を避けていくわけですが、この方法の欠点はベクトルを一度に条件分岐することができない事です。

ですが、step関数とmix関数を使うとベクトルの要素ごとに条件分岐をしてその結果をベクトルに直接反映させる事ができます。

とりあえずstepの用法

とりあえずstep関数の用法ですが、困った時の「WebGL 1.0 API Quick Reference Card」にはこんな説明が書いています。

// 0.0 if x < edge, else 1.0
T step(T edge, T x)
T step(float edge, T x)

要は引数の内容に応じて0.0か1.0を返してくれる関数ですね。

例えばこんな感じです。

vec2 x = vec2(0.25, 0.75);
vec2 edge = vec2(0.5);

// .xは 0.25 < 0.5 なので、0.0
// .yは 0.75 >= 0.5 なので、1.0
vec2 y = step(edge, x) // vec2(0.0, 1.0) 

vec2を使っていますが条件分岐の結果が.xと.yで異なっているのが分かるかと思います。

で、これとmix関数を組み合わせる事でベクトルでもif/else構文や三項演算子の代わりを果たす事ができます。

vec3 BLACK = vec3(0.0);
vec3 WHITE = vec3(1.0);

vec3 color;

vec3 x = vec3(0.4, 0.5, 0.6);
vec3 edge = vec2(0.5)

// .xは 0.4 < 0.5 なので、0.0
// .yは 0.5 >= 0.5 なので、1.0
// .zは 0.6 >= 0.5 なので、1.0
// 結果、vec3(1.0, 0.0, 0.0)で赤色になる
color = mix(WHITE, BLACK, step(edge, x));

mix関数は第3引数の値に応じて、第1,2引数の値を文字通りミックスしてくれるものですが、step関数は0.0か1.0しか返さないので、1つ目か2つ目のどちらかが返ってきます。

でも何か分かりづらい

step関数の用法はこんな感じです。

ですが、個人的に思うのは、
条件を満たしたら0.0じゃなくて1.0の方が直感的
って事です。

そこで、最近は以下のルールで考えるようにしています。

x >= edge

// x >= edge なら 1.0
step(edge, x);

これはリファレンスの説明にある0.0 if x < edge, else 1.0の意味を逆にした考えですね。

x <= edge

// x <= edge なら 1.0
step(x, edge);

引数の順番を逆にすれば>=<=になりますね。

x < edge

// x < edge なら 1.0
// vec4の場合
vec4(1.0) - step(edge, x);

今度はリファレンスの説明にある0.0 if x < edge, else 1.0の結果の方を逆にします。
注意点としては、vec4(1.0)の部分の次元を合わせるようにしてください。

x > edge

// x > edge なら 1.0
// vec4の場合
vec4(1.0) - step(x, edge);

同じく引数の順番を逆にすれば<>になります。

まとめ

x >= edge x <= edge x > edge x < edge
step(edge,x) step(x,edge) 1.0 - step(edge,x) 1.0 - step(x,edge)

とりあえず、この4パターンを頭に入れておくと良いと思います。

一応、動作確認用のデモを「GLSL Sandbox」に置いてあるのでこちらも参考にしてみてください。

引数の次元に注意

ひとつ注意したいのは、
T step(float edge, T x)
はできても、
T step(T x, float edge)
はできないという事です。

vec4 v4 = vec4(1.0, 0.75, 0.5, 0.25);
vec4 result;

// 1つ目がベクトルなら2つ目もベクトルにしないとダメ
// result = step(v4, 0.5);

// ベクトルの次元を合わせればOK
result = step(v4, vec4(0.5));

その場合は素直にベクトルの次元を合わせましょう。

おまけ

最後にもう一つ。

==!=はどうするの?

と思った方、答えは以下です。

x != edge

// x != edge なら 1.0
abs(sign(x - edge));

signは引数の符号を返す意味で、値が正なら1.0、負なら-1.0、値がゼロなら0.0を返します。
それをabsで絶対値にすれば1.00.0になります。
そして、xedgeの差分を渡す事で、x != edge なら 1.0の意味になります。

x == edge

// x == edge なら 1.0
// vec4の場合
vec4(1.0) - abs(sign(x - edge));

結果を逆にすればOKです。

それでは、良きGLSLライフを!