基于四元数的相机

时间:2015-02-15 20:21:06

标签: opengl graphics 3d directx quaternions

我尝试基于四元数学实现FPS相机。 我存储一个名为_quat的旋转四元数变量,并在需要时将其乘以另一个四元数。这是一些代码:

void Camera::SetOrientation(float rightAngle, float upAngle)//in degrees
{
    glm::quat q = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0));
              q*= glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0));

    _quat = q;
}

void Camera::OffsetOrientation(float rightAngle, float upAngle)//in degrees
{
    glm::quat q = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0));
              q*= glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0));

    _quat *= q;
}

应用程序可以通过GetOrientation请求方向矩阵,它只是将四元数转换为矩阵。

glm::mat4 Camera::GetOrientation() const
{
    return glm::mat4_cast(_quat);
}

应用程序按以下方式更改方向:

int diffX = ...;//some computations based on mouse movement
int diffY = ...;

camera.OffsetOrientation(g_mouseSensitivity * diffX, g_mouseSensitivity * diffY);

这会在几乎所有的轴周围产生不良的混合旋转。我做错了什么?

5 个答案:

答案 0 :(得分:7)

问题

正如GuyRT已经指出的那样,积累的方式并不好。从理论上讲,它会以这种方式运作。但是,浮点数学远非完全精确,并且错误会累积更多的操作。组合两个四元数旋转是28次操作而不是单个操作将值添加到角度(加上,四元数乘法中的每个操作都会以非常明显的方式影响3D空间中的最终旋转)。 此外,用于旋转的四元数对于标准化是相当敏感的,并且旋转它们会使它们稍微去标准化(旋转它们很多次使它们非标准化,并且用另一个旋转它们,已经去标准化的四元数放大了效果)。 / p>

反射

为什么我们首先使用四元数?

通常使用四元数的原因如下:

  1. 避免可怕的万向节锁定(尽管很多人不理解这个问题,用三个四元数替换三个角度不会神奇地消除了一个结合了三个旋转的事实 - - 必须正确使用四元数以避免此问题)
  2. 许多旋转的有效组合,例如剥皮(使用矩阵时为28次操作与45次操作),节省了ALU。
  3. 与组合多次转换时使用矩阵相比,较少的值(因此自由度较小),操作次数较少,因此产生不良影响的机会较少。
  4. 上传的值较少,例如当蒙皮模型具有几百个骨骼或绘制一万个对象实例时。较小的顶点流或统一块。
  5. 四元数很酷,使用它们的人很酷。
  6. 这些都不会对你的问题产生影响。

    解决方案

    将两个旋转累积为角度(通常不合需要,但在这种情况下完全可以接受),并在需要时创建旋转矩阵。这可以通过组合两个四元数并按照GuyRT的答案转换为矩阵来完成,或者通过直接生成旋转矩阵(这可能更有效,并且OpenGL希望看到的所有内容都是一个矩阵)。

    据我所知,glm::rotate只绕旋转任意轴。当然 当然可以使用(但是你宁愿合并两个四元数!)。幸运的是,矩阵组合旋转x,然后y,然后z的公式是众所周知的,直截了当,你发现它for example in the second paragraph of (3) here
    您不希望围绕 z 进行旋转,因此cos(gamma) = 1sin(gamma) = 0会大大简化公式(在一张纸上写出来)。

    使用旋转角度会让很多人对你大喊大叫(通常不完全不应该) 一个更清晰的选择是跟踪您所看到的方向,或者使用从您的眼睛指向您想要观察的方向的矢量,或者通过记住您在空间中看到的点(这与物理学中的物理结合得非常好)。也是第三人称游戏)。如果你想允许任意旋转,那还需要一个“向上”向量 - 因为那时“向上”并不总是“向上”的世界空间 - 所以你可能需要两个向量。这更好,更灵活,但也更复杂 对于您的示例中所需的FPS,您唯一的选择是左右和上下,我发现旋转角度 - 仅适用于相机 - 完全可以接受。

答案 1 :(得分:5)

问题在于你积累旋转的方式。无论使用四元数还是矩阵,这都是相同的。将表示俯仰和偏航的旋转与另一旋转相结合将引入滚动。

到目前为止,实现FPS相机的最简单方法是简单地累积标题和音高的变化,然后在需要时转换为quaterion(或矩阵)。我会将您的相机类中的方法更改为:

void Camera::SetOrientation(float rightAngle, float upAngle)//in degrees
{
    _rightAngle = rightAngle;
    _upAngle = upAngle;
}

void Camera::OffsetOrientation(float rightAngle, float upAngle)//in degrees
{
    _rightAngle += rightAngle;
    _upAngle += upAngle;
}

glm::mat4 Camera::GetOrientation() const
{
    glm::quat q = glm::angleAxis(glm::radians(-_upAngle), glm::vec3(1,0,0));
              q*= glm::angleAxis(glm::radians(_rightAngle), glm::vec3(0,1,0));
    return glm::mat4_cast(q);
}

答案 2 :(得分:3)

我没有使用过GLM,所以也许你不会喜欢这个答案。但是,执行四元数旋转也不错。

假设您的相机具有初始保存的方向'vecOriginalDirection'(标准化的vec3)。假设您希望它遵循另一个'vecDirection'(也标准化)。这样我们就可以采用类似轨迹球的方法,并将vecDirection视为相机默认焦点的偏转。

在现实世界中进行四元数旋转的通常首选方法是使用NLERP。让我们看看我是否能记住:在伪代码中(假设是浮点数)我认为就是这样:

quat = normalize([   cross(vecDirection, vecOriginalDirection),
                  1. + dot(vecDirection, vecOriginalDirection)]);

(不要忘记'1. +';我忘了为什么它在那里,但它一次都有意义。我想我把头发拉了几天直到找到它。它基本上是单位四元数, IIRC,正在进行平均,从而使双角度行为像角度......也许:))

重新正规化,如上所示为'normalize()',是必不可少的(它是NLERP中的'N')。当然,归一化quat(x,y,z,w)只是:

quat /= sqrt(x*x+y*y+z*z+w*w);

然后,如果你想用你自己的函数从quat制作一个3x3方向矩阵:

xx=2.*x*x,
yy=2.*y*y,
zz=2.*z*z,
xy=2.*x*y,
xz=2.*x*z,
yz=2.*y*z,
wx=2.*w*x,
wy=2.*w*y,
wz=2.*w*z;
m[0]=1.-(yy+zz);
m[1]=xy+wz;
m[2]=xz-wy;
m[3]=xy-wz;
m[4]=1.-(xx+zz);
m[5]=yz+wx;
m[6]=xz+wy;
m[7]=yz-wx;
m[8]=1.-(xx+yy);

要实际实现轨迹球,您需要在按住手指时计算vecDirection,并在首次按下时将其保存到vecOriginalDirection(假设触摸界面)。

如果您还没有,您可能还想根据分段半球/双曲面函数计算这些值。我认为@minorlogic试图节省一些修补,因为听起来你可能只能使用一个简易虚拟轨迹球。

答案 3 :(得分:0)

上角旋转应预先相乘,后乘以将通过(1,0,0)旋转原点周围的世界,预乘会旋转相机。

glm::quat q_up = glm::angleAxis(glm::radians(-upAngle), glm::vec3(1,0,0));
          q_right = glm::angleAxis(glm::radians(rightAngle), glm::vec3(0,1,0));

    _quat *= q_right;
    _quat = q_up * _quat;

答案 4 :(得分:-2)

开始使用鼠标控制的最佳方式,请参阅" Arcball" "轨迹球"控制实现

例如,教程http://subokita.com/2014/03/12/trackball-arcball-in-opengl-4/