在GLSL(特别是我正在使用的3.00)中,有两个版本的atan()
:atan(y_over_x)
只能返回-PI/2,PI/2之间的角度,而atan(y/x)
可以考虑所有4个象限,因此角度范围涵盖了-PI,PI的所有内容,就像C++中的atan2()
一样。
我想使用第二个atan
将XY坐标转换为角度。
但是,GLSL中的atan()
除了无法处理x = 0
之外,还不是很稳定。尤其是在x
接近于零的情况下,除法运算可能会溢出,从而导致相反的结果角度(您得到的值近似于-PI/2,而您假设该值近似为PI/2)。
我们可以在GLSL atan(y,x)
的基础上构建一个更好的简单实现,以使其更强大吗?
最佳答案
我将回答自己的问题,以分享我的知识。我们首先注意到x
接近零时会发生不稳定性。但是,我们也可以将其翻译为abs(x) << abs(y)
。因此,首先我们将平面(假设我们在单位圆上)划分为两个区域:一个区域为|x| <= |y|
,另一个区域为|x| > |y|
,如下所示:
我们知道atan(x,y)
在绿色区域中更加稳定-当x接近零时,我们仅拥有接近atan(0.0)的值(在数值上非常稳定),而通常的atan(y,x)
在橙色区域更稳定。您还可以使自己相信这种关系:
atan(x,y) = PI/2 - atan(y,x)
对于所有未定义的非原点(x,y)都成立,我们正在谈论的
atan(y,x)
可以返回整个-PI,PI范围内的角度值,而不是atan(y_over_x)
只能返回-PI/2,PI/2。因此,用于GLSL的健壮的atan2()
例程非常简单: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);
}
附带说明一下,数学函数
atan(x)
的标识实际上是:atan(x) + atan(1/x) = sgn(x) * PI/2
这是正确的,因为其范围是(-PI/2,PI/2)。