Hua*_*ham 13 c++ glsl coordinates atan2 numerical-stability
在GLSL(特别是我正在使用的3.00)中,有两个版本
atan():atan(y_over_x)只能返回-PI/2,PI/2之间的角度,同时atan(y/x)可以考虑所有4个象限,因此角度范围涵盖-PI, PI,就像atan2()在C++中一样.
我想用第二个atan将XY坐标转换为角度.但是,atan()在GLSL中,除了不能处理的时候x = 0,还不是很稳定.特别是在x接近零的地方,除法会溢出,导致相反的结果角度(你得到的东西接近-PI/2,你可以得到大约PI/2).
什么是一个好的,简单的实现,我们可以在GLSL之上构建,atan(y,x)使其更强大?
Jon*_*eck 11
根据您的目标平台,这可能是一个已解决的问题.atan(y,x)的OpenGL规范指定它应该适用于所有象限,只有当x和y都为0时才会保持行为不确定.
所以人们会期望任何体面的实现在所有轴附近都是稳定的,因为这是2-argument atan(或atan2)背后的全部目的.
提问者/回答者是正确的,因为某些实现确实采取了快捷方式.然而,接受的解决方案假设当x接近零时,不良实现总是不稳定:在某些硬件(例如我的Galaxy S4)上,当x接近零时值稳定,但当y接近零时不稳定.
为了测试你的GLSL渲染器的实现atan(y,x),这里是一个WebGL测试模式.按照下面的链接,只要你的OpenGL实现得体,你应该看到这样的事情:

使用原生的测试模式atan(y,x): http ://glslsandbox.com/e#26563.2
如果一切顺利,你应该看到8种不同的颜色(忽略中心).
链接的演示样本atan(y,x)用于x和y的多个值,包括0,非常大和非常小的值.中心框是 - atan(0.,0.)数学上未定义的,实现方式各不相同.我在硬件上看过0(红色),PI/2(绿色)和NaN(黑色).
这是接受解决方案的测试页面.注意:主机的WebGL版本不足mix(float,float,bool),所以我添加了一个与规范匹配的实现.
使用已atan2(y,x)接受答案的测试模式: http ://glslsandbox.com/e#26666.0
Hua*_*ham 10
我将回答我自己的问题,分享我的知识.我们首先注意到当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)
Run Code Online (Sandbox Code Playgroud)
对于所有非原点(x,y),它是未定义的,我们所说的atan(y,x)是能够在-PI,PI的整个范围内返回角度值,而不是atan(y_over_x)只返回-PI/2之间的角度, PI/2.因此,我们atan2()对GLSL 的强大程序非常简单:
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);
}
Run Code Online (Sandbox Code Playgroud)
作为旁注,数学函数的标识atan(x)实际上是:
atan(x) + atan(1/x) = sgn(x) * PI/2
Run Code Online (Sandbox Code Playgroud)
这是真的,因为它的范围是(-PI/2,PI/2).

您提出的解决方案仍然无法解决x=y=0.这两个atan()函数都返回NaN.
此外,我不会依赖混合来在两种情况之间切换.我不确定这是如何实现/编译的,但是x*NaN和x + NaN的IEEE浮点规则再次在NaN中产生.因此,如果你的编译器真的使用了混合/插值,那么结果应该是NaN for x=0或y=0.
这是另一个解决了我的问题的解决方案:
float atan2(in float y, in float x)
{
return x == 0.0 ? sign(y)*PI/2 : atan(y, x);
}
Run Code Online (Sandbox Code Playgroud)
当x=0角度可以是±π/ 2.这两者中的哪一个y仅取决于.如果y=0也是,则角度可以是任意的(向量具有长度0).在那种情况下sign(y)返回0,这是正常的.
有时,提高一段代码性能的最佳方法是首先避免调用它。例如,您可能想要确定向量角度的原因之一是,您可以使用该角度通过角度的正弦和余弦组合构造旋转矩阵。然而,矢量的正弦和余弦(相对于原点)已经隐藏在矢量本身的内部。您需要做的就是通过将每个向量坐标除以向量的总长度来创建向量的标准化版本。下面是计算向量 [ xy ] 角度的正弦和余弦的二维示例:
double length = sqrt(x*x + y*y);
double cos = x / length;
double sin = y / length;
Run Code Online (Sandbox Code Playgroud)
获得正弦和余弦值后,您现在可以直接使用这些值填充旋转矩阵,以按相同角度对任意向量执行顺时针或逆时针旋转,或者您可以连接第二个旋转矩阵以旋转到除零。在这种情况下,您可以将旋转矩阵视为将任意向量的角度“归一化”为零。这种方法也可以扩展到三维(或 N 维)情况,尽管例如您将有三个角度和六个正弦/余弦对来计算(每个平面一个角度)以进行 3D 旋转。
在可以使用这种方法的情况下,您可以通过完全绕过 atan 计算来获得巨大的胜利,这是可能的,因为您想要确定角度的唯一原因是计算正弦和余弦值。通过跳过与角度空间的转换并返回,您不仅可以避免担心被零除,还可以提高极点附近角度的精度,否则会因被大量数字相乘/除而受到影响。我已经在 GLSL 程序中成功使用了这种方法,该程序将场景旋转到零度以简化计算。
您很容易陷入眼前的问题,以至于忘记为什么需要这些信息。并不是说这在所有情况下都有效,但有时跳出框框思考会有所帮助......
| 归档时间: |
|
| 查看次数: |
7660 次 |
| 最近记录: |