Robust atan(y,x) on GLSL for converting XY coordinate to angle

后端 未结 5 684
花落未央
花落未央 2021-02-02 16:34

In GLSL (specifically 3.00 that I\'m using), there are two versions of atan(): atan(y_over_x) can only return angles between -PI/2, PI/2, while

相关标签:
5条回答
  • 2021-02-02 17:08

    Your proposed solution still fails in the case x=y=0. Here both of the atan() functions return NaN.

    Further I would not rely on mix to switch between the two cases. I am not sure how this is implemented/compiled, but IEEE float rules for x*NaN and x+NaN result again in NaN. So if your compiler really used mix/interpolation the result should be NaN for x=0 or y=0.

    Here is another fix which solved the problem for me:

    float atan2(in float y, in float x)
    {
        return x == 0.0 ? sign(y)*PI/2 : atan(y, x);
    }
    

    When x=0 the angle can be ±π/2. Which of the two depends on y only. If y=0 too, the angle can be arbitrary (vector has length 0). sign(y) returns 0 in that case which is just ok.

    0 讨论(0)
  • 2021-02-02 17:10

    I'm going to answer my own question to share my knowledge. We first notice that the instability happens when x is near zero. However, we can also translate that as abs(x) << abs(y). So first we divide the plane (assuming we are on a unit circle) into two regions: one where |x| <= |y| and another where |x| > |y|, as shown below:

    two regions

    We know that atan(x,y) is much more stable in the green region -- when x is close to zero we simply have something close to atan(0.0) which is very stable numerically, while the usual atan(y,x) is more stable in the orange region. You can also convince yourself that this relationship:

    atan(x,y) = PI/2 - atan(y,x)
    

    holds for all non-origin (x,y), where it is undefined, and we are talking about atan(y,x) that is able to return angle value in the entire range of -PI,PI, not atan(y_over_x) which only returns angle between -PI/2, PI/2. Therefore, our robust atan2() routine for GLSL is quite simple:

    float atan2(in float y, in float x)
    {
        bool s = (abs(x) > abs(y));
        return mix(PI/2.0 - atan(x,y), atan(y,x), s);
    }
    

    As a side note, the identity for mathematical function atan(x) is actually:

    atan(x) + atan(1/x) = sgn(x) * PI/2
    

    which is true because its range is (-PI/2, PI/2).

    graph

    0 讨论(0)
  • 2021-02-02 17:15

    Depending on your targeted platform, this might be a solved problem. The OpenGL spec for atan(y, x) specifies that it should work in all quadrants, leaving behavior undefined only when x and y are both 0.

    So one would expect any decent implementation to be stable near all axes, as this is the whole purpose behind 2-argument atan (or atan2).

    The questioner/answerer is correct in that some implementations do take shortcuts. However, the accepted solution makes the assumption that a bad implementation will always be unstable when x is near zero: on some hardware (my Galaxy S4 for example) the value is stable when x is near zero, but unstable when y is near zero.

    To test your GLSL renderer's implementation of atan(y,x), here's a WebGL test pattern. Follow the link below and as long as your OpenGL implementation is decent, you should see something like this:

    GLSL atan(y,x) test pattern

    Test pattern using native atan(y,x): http://glslsandbox.com/e#26563.2

    If all is well, you should see 8 distinct colors (ignoring the center).

    The linked demo samples atan(y,x) for several values of x and y, including 0, very large, and very small values. The central box is atan(0.,0.)--undefined mathematically, and implementations vary. I've seen 0 (red), PI/2 (green), and NaN (black) on hardware I've tested.

    Here's a test page for the accepted solution. Note: the host's WebGL version lacks mix(float,float,bool), so I added an implementation that matches the spec.

    Test pattern using atan2(y,x) from accepted answer: http://glslsandbox.com/e#26666.0

    0 讨论(0)
  • 2021-02-02 17:18

    Sometimes the best way to improve the performance of a piece of code is to avoid calling it in the first place. For example, one of the reasons you might want to determine the angle of a vector is so that you can use this angle to construct a rotation matrix using combinations of the angle's sine and cosine. However, the sine and cosine of a vector (relative to the origin) are already hidden in plain sight inside the vector itself. All you need to do is to create a normalized version of the vector by dividing each vector coordinate by the total length of the vector. Here's the two-dimensional example to calculate the sine and cosine of the angle of vector [ x y ]:

    double length = sqrt(x*x + y*y);
    double cos = x / length;
    double sin = y / length;
    

    Once you have the sine and cosine values, you can now directly populate a rotation matrix with these values to perform a clockwise or counterclockwise rotation of arbitrary vectors by the same angle, or you can concatenate a second rotation matrix to rotate to an angle other than zero. In this case, you can think of the rotation matrix as "normalizing" the angle to zero for an arbitrary vector. This approach is extensible to the three-dimensional (or N-dimensional) case as well, although for example you will have three angles and six sin/cos pairs to calculate (one angle per plane) for 3D rotation.

    In situations where you can use this approach, you get a big win by bypassing the atan calculation completely, which is possible since the only reason you wanted to determine the angle was to calculate the sine and cosine values. By skipping the conversion to angle space and back, you not only avoid worrying about division by zero, but you also improve precision for angles which are near the poles and would otherwise suffer from being multiplied/divided by large numbers. I've successfully used this approach in a GLSL program which rotates a scene to zero degrees to simplify a computation.

    It can be easy to get so caught up in an immediate problem that you can lose sight of why you need this information in the first place. Not that this works in every case, but sometimes it helps to think out of the box...

    0 讨论(0)
  • 2021-02-02 17:22

    A formula that gives an angle in the four quadrants for any value

    of coordinates x and y. For x=y=0 the result is undefined.

    f(x,y)=pi()-pi()/2*(1+sign(x))* (1-sign(y^2))-pi()/4*(2+sign(x))*sign(y)

       -sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y))) 
    
    0 讨论(0)
提交回复
热议问题