基于四元数的相机

Pil*_*pel 9 opengl directx 3d graphics 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;
}
Run Code Online (Sandbox Code Playgroud)

应用程序可以通过GetOrientation简单地将四元数转换为矩阵来请求方向矩阵.

glm::mat4 Camera::GetOrientation() const
{
    return glm::mat4_cast(_quat);
}
Run Code Online (Sandbox Code Playgroud)

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

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

camera.OffsetOrientation(g_mouseSensitivity * diffX, g_mouseSensitivity * diffY);
Run Code Online (Sandbox Code Playgroud)

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

Dam*_*mon 8

问题

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

反射

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

四元数通常用于以下原因:

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

这些都不会对你的问题产生影响.

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

据我所知,glm::rotate只有旋转任意轴.你可以,当然使用(但你宁愿合并两个四元!).幸运的是,矩阵的公式结合了围绕x,然后y,然后z的旋转是众所周知且直截了当的,例如在这里的(3)的第二段中找到它.
你不希望绕Z ^,所以cos(gamma) = 1sin(gamma) = 0,从而大大简化公式(写出来的一张纸).

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


Guy*_*yRT 6

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

到目前为止,实现FPS摄像机最简单的方法是简单地累积航向和俯仰的变化,然后在需要时转换为四分之一(或矩阵)。我会将您的相机类中的方法更改为:

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);
}
Run Code Online (Sandbox Code Playgroud)