0
\$\begingroup\$

I was expecting these two code snippets to do the same thing:

return  vec3(  1.0-b.r>=a.r ? 0.0 : 1.0-((1.0-b.r)/a.r),
               1.0-b.g>=a.g ? 0.0 : 1.0-((1.0-b.g)/a.g),
               1.0-b.b>=a.b ? 0.0 : 1.0-((1.0-b.b)/a.b)  );

and

return mix( ONE3 - ((ONE3-b)/a), ZERO3, vec3(greaterThanEqual(ONE3-b, a)) ); 
       // ONE3=vec3(1.0,1.0,1.0); ZERO3=vec3(0.0,0.0,0.0);

For some reason, they have different outputs. Do you know why? (a can have zeros sometimes)

\$\endgroup\$
5
  • 2
    \$\begingroup\$ Rather than saying "I think it will perform faster", why not profile your code, determine if your current approach actually is a performance problem, then determine if your alternate approach actually will perform faster first? \$\endgroup\$ Commented Aug 10, 2016 at 10:18
  • \$\begingroup\$ I will profile my code. But now I am trying to find out, why they behave differently, when they are logically the same. \$\endgroup\$ Commented Aug 10, 2016 at 10:19
  • \$\begingroup\$ What you mean by different outputs? 4 insted of 5, or 4.000001 instead of 4.000002? \$\endgroup\$
    – Mars
    Commented Aug 10, 2016 at 13:21
  • \$\begingroup\$ I am using it to combine pixels of two textures and the output color is a lot different ... so like 4 instead of 5. \$\endgroup\$ Commented Aug 10, 2016 at 15:03
  • \$\begingroup\$ @IvanKuckir TL;DR: you're getting NaN from the divisions by zero and mix doesn't like it. Longer answer below. \$\endgroup\$ Commented Aug 12, 2016 at 4:48

2 Answers 2

4
\$\begingroup\$

The main issue is that (ONE3-b)/a may cause a division by zero. This causes the result to be NaN (Not A Number). In that case mix will also return NaN because pretty much anything done to a NaN gives back an NaN and mix(a, b, i) internally does a*(1-i) + b*i or a + (b-a)*i.

try using mix( ONE3 - ((ONE3-b)/max(a, vec3(0.001, 0.001, 0.001))), ZERO3, step(a, ONE3-b) );

Using max(a, tiny_value ) prevents a division by zero.

Using ?: avoids the NaN case completely.

And instead of greaterThanEqual and a cast back to vec3 you can use step(edge, x) which returns 0 if x < edge and 1 otherwise without branching.

You should really profile your shaders on the target platform. Depending on the GPU and the operands used sometimes ?: will be faster.

Some GPUs (especially mobile GPUs) don't mind branches too much and may benefit from them if the calculation skipped is quite long.

\$\endgroup\$
4
  • \$\begingroup\$ Oh, now I get it. Zero times NaN is not Zero, but NaN. \$\endgroup\$ Commented Aug 12, 2016 at 10:32
  • \$\begingroup\$ BTW. I am doing it in WebGL, which may run on different desktop and mobile devices, so it is hard to profile. But I am sure it will not be the bottleneck of my app, so I will use the mix() version, because it is shorter. \$\endgroup\$ Commented Aug 12, 2016 at 10:32
  • \$\begingroup\$ Thanks for letting me know about step() ! Is there also an alternative for vec3(equal(b,ONE3)) ? \$\endgroup\$ Commented Aug 12, 2016 at 10:38
  • \$\begingroup\$ @IvanKuckir: Yes, but it may not be faster. make sure to profile this on different GPUs: (vec3(1,1,1) - abs(sign(b - vec3(1,1,1)))) \$\endgroup\$ Commented Aug 12, 2016 at 22:46
0
\$\begingroup\$

You do not actually need mix. Just realise that 1.0-(1.0-b)/(1.0-b) is always zero (except in the case of a division by zero, we’ll come to that later).

So you can just forget the ternary operator and when 1.0-b>=a, instead of dividing by a you just divide by 1-b to get zero in the end. This means dividing by max(1-b,a):

vec3 m = max(ONE3 - b, a);
return ONE3 - (ONE3 - b) / m;

This can be rewritten as:

vec3 m = max(ONE3 - b, a);
return (m + b - ONE3) / m;

Now you still have a division by zero when b is exactly 1 and a is exactly 0. This can be prevented using a small correcting factor:

vec3 m = max(ONE3 - b, a);
return (m + b - ONE3) / max(m, vec3(1e-6));
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .