OpenGL中的三轴四元数旋转

5
我将尝试创建一个OpenGL程序,其中一只鸟的模型应该沿着Seiffert球形螺旋线描述的球面上的定义路径移动。然而,我已经卡在正确进行旋转上很长一段时间了。
首先,我让鸟只沿着x-z平面上的圆形路径移动:
// 1. Circle in x-z plane
float phi =  TWO_PI * t; // t = [0..1]

float x = boundingSphereRadius * cos(phi);
float y = 0.0f;
float z = boundingSphereRadius * sin(phi);

float rotationAngle = glm::orientedAngle(glm::vec3(0.0f, 0.0f, 1.0f),    
                                         glm::normalize(glm::vec3(x, 0, z)),
                                         glm::vec3(0.0f, 1.0f, 0.0f)) - HALF_PI;
glm::fquat rotation = glm::angleAxis(rotationAngle, glm::vec3(0.0f, 1.0f, 0.0f));

固定的-HALF_PI是必须的,以便正确对齐鸟。这个方法完全可行,类似地,我可以在x-y平面和y-z平面中实现圆形旋转。
当我尝试累加所有不同的旋转时,问题就出现了。我要遵循的路径如下所示:
作为要求,鸟的腹部应始终面向球体表面,鸟应向前飞行。
我的当前方法看起来像这样,只需要将三个方向的四元数组合起来:
glm::fquat rotationX  = glm::angleAxis(glm::orientedAngle(glm::normalize(glm::vec3(0.0f, 0.0f, 1.0f)), glm::normalize(glm::vec3(x, 0, z)), glm::vec3(0.0f, 1.0f, 0.0f)) - HALF_PI, glm::vec3(0.0f, 1.0f, 0.0f));
glm::fquat rotationY1 = glm::angleAxis(-HALF_PI, glm::vec3(0.0f, 1.0f, 0.0f));
glm::fquat rotationY2 = glm::angleAxis(glm::orientedAngle(glm::vec3(0.0f, 1.0f, 0.0f), glm::normalize(glm::vec3(x, y, 0)), glm::vec3(0.0f, 0.0f, 1.0f)), glm::vec3(0.0f, 0.0f, 1.0f));
glm::fquat rotationY  = rotationY2 * rotationY1;
glm::fquat rotationZ  = glm::angleAxis(glm::orientedAngle(glm::vec3(0.0f, 0.0f, 1.0f), glm::normalize(glm::vec3(0, y, z)), glm::vec3(1.0f, 0.0f, 0.0f)) + HALF_PI, glm::vec3(1.0f, 0.0f, 0.0f));
glm::fquat rotation   = rotationZ * rotationY * rotationX;

然而,方向变化完全错误,在某些角度会出现跳跃。

编辑:

我现在正在尝试不同的球面上的圆,需要多次旋转。对于beta = gamma = 0.0falpha = HALF_PI,这个圆再次位于x-z平面上,rotationAngleXZ 的值正在改变,而rotationAngleXY-HALF_PIHALF_PIrotationAngleYZ0.0fPI。我猜这里遇到了万向锁问题,我已经阅读了许多有关它的文章,但仍然不确定如何在这种情况下避免它。

// 10. `Arbitrary` circles on sphere surface
// http://math.stackexchange.com/questions/643130/circle-on-sphere
//
// Parameters:
//      alpha = 0...HALF_PI - For alpha = 0, the circle is just a point - For alpha = HALF_PI, the circle is a Great Circle
//      (beta, gamma) = center of circle in spherical coordinates
float phi =  TWO_PI * t;

float x = boundingSphereRadius * ( (sin(alpha) * cos(beta) * cos(gamma)) * cos(phi) + (sin(alpha) * sin(gamma)) * sin(phi) - (cos(alpha) * sin(beta) * cos(gamma)));
float y = boundingSphereRadius * ( (sin(alpha) * sin(beta)) * cos(phi) + cos(alpha) * cos(beta));
float z = boundingSphereRadius * (-(sin(alpha) * cos(beta) * sin(gamma)) * cos(phi) + (sin(alpha) * cos(gamma)) * sin(phi) + (cos(alpha) * sin(beta) * sin(gamma)));

float rotationAngleXZ = glm::orientedAngle(glm::normalize(glm::vec3(0.0f, 0.0f, 1.0f)), glm::normalize(glm::vec3(x, 0, z)), glm::vec3(0.0f, 1.0f, 0.0f));
std::cout << "Rotation Angle XZ = " << rotationAngleXZ << std::endl;
glm::fquat rotationXZ = glm::angleAxis(rotationAngleXZ - HALF_PI, glm::vec3(0.0f, 1.0f, 0.0f));

float rotationAngleXY = glm::orientedAngle(glm::vec3(0.0f, 1.0f, 0.0f), glm::normalize(glm::vec3(x, y, 0)), glm::vec3(0.0f, 0.0f, 1.0f));
std::cout << "Rotation Angle XY = " << rotationAngleXY << std::endl;
glm::fquat rotationXY_Y = glm::angleAxis(-HALF_PI, glm::vec3(0.0f, 1.0f, 0.0f));
glm::fquat rotationXY_Z = glm::angleAxis(rotationAngleXY, glm::vec3(0.0f, 0.0f, 1.0f));
glm::fquat rotationXY = rotationXY_Z * rotationXY_Y;

float rotationAngleYZ = glm::orientedAngle(glm::vec3(0.0f, 0.0f, 1.0f), glm::normalize(glm::vec3(0, y, z)), glm::vec3(1.0f, 0.0f, 0.0f));
std::cout << "Rotation Angle YZ = " << rotationAngleYZ << std::endl;
glm::fquat rotationYZ = glm::angleAxis(rotationAngleYZ + HALF_PI, glm::vec3(1.0f, 0.0f, 0.0f));

glm::fquat rotation = glm::normalize(rotationXZ) * glm::normalize(rotationXY) * glm::normalize(rotationYZ);
2个回答

3

我没有现成的代码能提供给你,但这个思路怎么样?假设你已经有了一个把鸟的三维坐标(x、y、z)作为时间t函数的公式(Seiffert的球面螺旋曲线)。那么:

eye    = fn(t)
center = fn(t + dt) // where will the bird be in the next time-step
up     = normalize(eye - sphereCenter)

现在,gluLookAt(eye,center,up)将提供一个矩阵,您应该能够使用它来定位您的鸟。
这个参考链接可能也有所帮助:https://gamedev.stackexchange.com/questions/41940/how-does-glulookat-work
希望这可以帮到您,
--Roger

3
你的代码使用了欧拉角(轴对齐旋转)。波动和跳跃是因为欧拉角不是三维旋转空间的良好参数化方式。相反,以下是两种替代方法。
通过框架构建旋转矩阵
假设鸟指向x轴并且在自己的本地坐标系中朝上。
让p = [x y z] 是鸟的位置。让v成为其速度矢量。让
f = v/|v|
up = p/|p|
s = cross(f, up)

现在构造一个矩阵,其中包括f、up、s三个行。具体操作如下:
[  f[0]   f[1]  f[2] ]
[ up[0]  up[1] up[2] ]
[  s[0]   s[1]  s[2] ]

然后通过GLM的quat_cast函数生成四元数。
避免使用gluLookAt,因为它使用了已弃用的固定功能矩阵堆栈。
通过旋转(四元数)构建
R0为从if的旋转。(角度为acos(dot(i,f)),轴为cross(i,f)
R1为从R0*jup的旋转。(在此上下文中使用矩阵乘法符号更容易)
R2为从R1*R0*ks的旋转。
最终旋转应为R2*R1*R0。检查此旋转是否等于上面的矩阵。

谢谢你的回答Taylor!我想我正在寻找一种不同类型的解决方案,可以将三个轴的单独四元数组合起来 - 我有一个针对x,y和z轴的方向四元数,如我发布的代码所述。据我所知,可以将这三个四元数相乘,以获得组合方向,这是每帧中我的鸟的最终方向。 - Schnigges
我已经尝试了许多不同的方法来组合四元数,但都没有成功,我想把赏金给那个能够解释如何实现这一点(或者为什么我尝试的方式不可行)的人。 - Schnigges
当然可以!我能理解为什么你想用四元数来表示最终的方向,但是为什么要通过单独的四元数来建立方向呢? - Taylor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接