GLSL optimization: check if variable is within range

Viewed 3328

In my shader I have variable b and need to determine within which range it lies and from that assign the right value to variable a. I ended up with a lot of if statements:

    float a = const1;

    if (b >= 2.0 && b < 4.0) {
        a = const2;
    } else if (b >= 4.0 && b < 6.0) {
        a = const3;
    } else if (b >= 6.0 && b < 8.0) {
        a = const4;
    } else if (b >= 8.0) {
        a = const5;
    }

My question is could this lead to performance issues (branching) and how can I optimize it? I've looked at the step and smoothstep functions but haven't figured out a good way to accomplish this.

3 Answers

To solve the problem depicted and avoid branching the usual techniques is to find a series of math functions, one for each condition, that evaluate to 0 for all the conditions except the one the variable satisfies. We can use these functions as gains to build a sum that evaluates to the right value each time. In this case the conditions are simple intervals, so using the step functions we could write:

x in [a,b] as step(a,x)*step(x,b) (notice the inversion of x and b to get x<=b)

Or

x in [a,b[ as step(a,x)-step(x,b) as explained in this other post: GLSL point inside box test

Using this technique we obtain:

float a = (step(x,2.0)-((step(2.0,x)*step(x,2.0)))*const1 + 
                (step(2.0,x)-step(4.0,x))*const2 + 
                (step(4.0,x)-step(6.0,x))*const3 + 
                (step(6.0,x)-step(8.0,x))*const4 + 
                step(8.0,x)*const5

This works for general disjoint intervals, but in the case of a step or staircase function as in this question, we can simplify it as:

float a = const1 + step(2.0,x)*(const2-const1) +
                   step(4.0,x)*(const3-const2) +
                   step(6.0,x)*(const4-const3) +
                   step(8.0,x)*(const5-const4)

We could also use a 'bool conversion to float' as means to express our conditions, so as an example step(8.0,x)*(const5-const4) is equivalent to float(x>=8.0)*(const5-const4)

You can avoid branching by creating kind of a lookup table:

float table[5] = {const1, const2, const3, const4, const5};
float a = table[int(clamp(b, 0.0, 8.0) / 2)];

But the performance will depend on whether the lookup table will have to be created in every shader or if it's some kind of uniform... As always, measure first...

It turned out Jaa-cs answere wasn't viable for me as I'm targeting WebGL which doesn't allow variables as indexes (unless it's a loop index). His solution might work great for other OpenGL implementations though.

I came up with this solution using mix and step functions:

//Outside of main function:
uniform vec3 constArray[5]; // Values are sent in to shader

//Inside main function:
float a = constArray[0];
a = mix(a, constArray[1], step(2.0, b));
a = mix(a, constArray[2], step(4.0, b));
a = mix(a, constArray[3], step(6.0, b));
a = mix(a, constArray[4], step(8.0, b));

But after some testing it didn't give any visible performance boost. I finally ended up with this solution:

float a = constArray[0];

if (b >= 2.0)
    a = constArray[1];
if (b >= 4.0)
    a = constArray[2];
if (b >= 6.0)
    a = constArray[3];
if (b >= 8.0)
    a = constArray[4];

Which is both compact and easily readable. In my case both these alternatives and my original code performed equally, but at least here are some options to try out.

Related