基于四元数的第一人称视角相机

时间:2018-04-02 10:37:13

标签: c++ opengl glm-math

我按照位于https://paroj.github.io/gltut/的教程学习OpenGL。

通过基础知识,我对理解四元数及其与空间方向和变换的关系有点困惑,特别是从世界到相机空间,反之亦然。在相机相对方向一章中,作者制作了一个相机,它相对于相机方向在 world 空间中旋转模型。引用:

  

我们想要应用一个方向偏移(R),它取得相机空间中的点数。如果我们想将它应用于相机矩阵,它将简单地乘以相机矩阵:R * C * O * p。这很好,但我们想要将变换应​​用于O,而不是C。

我没有受过教育的猜测是,如果我们将偏移应用于相机空间,我们将获得第一人称相机。它是否正确?相反,偏移应用于 world 空间中的模型,使得太空船相对于该空间旋转,而不是相机空间。我们只是观察它从相机空间旋转。

受至少对四元数的理解(或者我认为)的启发,我试图实现第一人称相机。它有两个属性:

struct Camera{ 
  glm::vec3 position; // Position in world space.
  glm::quat orientation; // Orientation in world space.
}

位置因键盘操作而被修改,而方向因屏幕上的鼠标移动而改变。

注意:GLM重载了* glm::quat * glm::vec3运算符,其关系是通过四元数旋转向量(更紧凑的v' = qvq^-1形式)

例如,向前移动并向右移动:

glm::vec3 worldOffset;
float scaleFactor = 0.5f;
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
    worldOffset = orientation * (axis_vectors[AxisVector::AXIS_Z_NEG]); // AXIS_Z_NEG = glm::vec3(0, 0, -1)
    position += worldOffset * scaleFactor;
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
    worldOffset = orientation  * (axis_vectors[AxisVector::AXIS_X_NEG]); // AXIS_Z_NEG = glm::vec3(-1, 0, 0)
    position += worldOffset * scaleFactor;
}

方向和位置信息被传递到glm::lookAt矩阵,用于构建世界到相机的转换,如下所示:

auto camPosition = position;
auto camForward = orientation * glm::vec3(0.0, 0.0, -1.0);
viewMatrix = glm::lookAt(camPosition, camPosition + camForward, glm::vec3(0.0, 1.0, 0.0));

组合模型,视图和投影矩阵并将结果传递给顶点着色器显示一切正常 - 人们期望从第一人称POV中看到事物的方式。但是,当我添加鼠标移动,跟踪x和y方向的移动量时,事情会变得混乱。我想围绕 world y轴和 local x轴旋转:

auto xOffset = glm::angleAxis(xAmount, axis_vectors[AxisVector::AXIS_Y_POS]); // mouse movement in x-direction
auto yOffset = glm::angleAxis(yAmount, axis_vectors[AxisVector::AXIS_X_POS]); // mouse movement in y-direction
orientation = orientation * xOffset; // Works OK, can look left/right
orientation = yOffset * orientation; // When adding this line, things get ugly

问题是什么? 我承认,我没有足够的知识来正确调试鼠标移动代码,我主要是按照行,说"右边乘以在世界空间中应用偏移,左边相乘以在相机空间中进行。 "

我觉得我对事情有所了解,从过多的电子资源中得出结论,同时得到更多的教育和更多的困惑。 谢谢你的回答。

2 个答案:

答案 0 :(得分:1)

旋转表示方向的glm四元数:

//Precomputation: 
//pitch (rot around x in radians), 
//yaw (rot around y in radians), 
//roll (rot around z in radians)
//are computed/incremented by mouse/keyboard events

计算视图矩阵:

void CameraFPSQuaternion::UpdateView()
{
  //FPS camera:  RotationX(pitch) * RotationY(yaw)
  glm::quat qPitch = glm::angleAxis(pitch, glm::vec3(1, 0, 0));
  glm::quat qYaw = glm::angleAxis(yaw, glm::vec3(0, 1, 0));
  glm::quat qRoll = glm::angleAxis(roll,glm::vec3(0,0,1));  

  //For a FPS camera we can omit roll
  glm::quat orientation = qPitch * qYaw;
  orientation = glm::normalize(orientation);
  glm::mat4 rotate = glm::mat4_cast(orientation);

  glm::mat4 translate = glm::mat4(1.0f);
  translate = glm::translate(translate, -eye);

  viewMatrix = rotate * translate;
}

如果您想存储四元数,那么每当偏航,俯仰或滚动发生变化时,您都会重新计算它:

void CameraFPSQuaternion::RotatePitch(float rads) // rotate around cams local X axis
{
  glm::quat qPitch = glm::angleAxis(rads, glm::vec3(1, 0, 0));

  m_orientation = glm::normalize(qPitch) * m_orientation;
  glm::mat4 rotate = glm::mat4_cast(m_orientation);

  glm::mat4 translate = glm::mat4(1.0f);
  translate = glm::translate(translate, -eye);

  m_viewMatrix = rotate * translate;
}

如果要围绕给定轴给出旋转速度,请使用slerp:

void CameraFPSQuaternion::Update(float deltaTimeSeconds)
{
  //FPS camera:  RotationX(pitch) * RotationY(yaw)
  glm::quat qPitch = glm::angleAxis(m_d_pitch, glm::vec3(1, 0, 0));
  glm::quat qYaw = glm::angleAxis(m_d_yaw, glm::vec3(0, 1, 0));
  glm::quat qRoll = glm::angleAxis(m_d_roll,glm::vec3(0,0,1));  

  //For a FPS camera we can omit roll
  glm::quat m_d_orientation = qPitch * qYaw;
  glm::quat delta = glm::mix(glm::quat(0,0,0,0),m_d_orientation,deltaTimeSeconds);
  m_orientation = glm::normalize(delta) * m_orientation;
  glm::mat4 rotate = glm::mat4_cast(orientation);

  glm::mat4 translate = glm::mat4(1.0f);
  translate = glm::translate(translate, -eye);

  viewMatrix = rotate * translate;
}

答案 1 :(得分:0)

问题在于使用glm::lookAt构建视图矩阵。相反,我现在正在构建视图矩阵,如下所示:

auto rotate = glm::mat4_cast(entity->orientation);
auto translate = glm::mat4(1.0f);
translate = glm::translate(translate, -entity->position);
viewMatrix = rotate * translate;

对于翻译,我现在乘以方向的倒数而不是方向。

glm::quat invOrient = glm::conjugate(orientation);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
  worldOffset = invOrient * (axis_vectors[AxisVector::AXIS_Z_NEG]);
  position += worldOffset * scaleFactor;
}
...

除了鼠标移动代码中的一些进一步偏移四元数标准化之外,其他所有内容都是相同的。

相机现在的行为和感觉就像是第一人称相机。

我仍然没有正确理解视图矩阵和lookAt矩阵之间的区别,如果有的话。但这是另一个问题的主题。