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)  );


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)

  • 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\$ Aug 10 '16 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\$ Aug 10 '16 at 10:19
  • \$\begingroup\$ What you mean by different outputs? 4 insted of 5, or 4.000001 instead of 4.000002? \$\endgroup\$
    – Mars
    Aug 10 '16 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\$ Aug 10 '16 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\$ Aug 12 '16 at 4:48

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.

  • \$\begingroup\$ Oh, now I get it. Zero times NaN is not Zero, but NaN. \$\endgroup\$ Aug 12 '16 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\$ Aug 12 '16 at 10:32
  • \$\begingroup\$ Thanks for letting me know about step() ! Is there also an alternative for vec3(equal(b,ONE3)) ? \$\endgroup\$ Aug 12 '16 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\$ Aug 12 '16 at 22:46

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));

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.