OpenGL鼠标视角交互

  • 1. 欧拉角
  • 2. 鼠标输入

1. 欧拉角

欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:
OpenGL鼠标视角交互-编程知识网

  1. 俯仰角是描述我们如何往上或往下看的角
  2. 偏航角表示我们往左和往右看的程度
  3. 滚转角代表我们如何翻滚摄像机

把三个角结合起来我们就能够计算3D空间中任何的旋转向量
本摄像机系统仅关注俯仰角和偏航角
对于俯仰角,观察Y轴朝上的方向:
OpenGL鼠标视角交互-编程知识网
俯仰角的Y值 等于sinθ

direction.y = sin(glm::radians(pitch)); // 注意角度转为弧度

俯仰角的X与Z值 等于cosθ

direction.x = cos(glm::radians(pitch));
direction.z = cos(glm::radians(pitch));

对于偏航角,观察XOZ平面:
OpenGL鼠标视角交互-编程知识网
俯仰角的Z值 等于sinθ

direction.z = sin(glm::radians(yaw))

俯仰角的X值 等于cosθ

direction.x = cos(glm::radians(yaw));

把这个加到前面的值中,会得到基于俯仰角和偏航角的方向向量

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); 
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

2. 鼠标输入

偏航角和俯仰角是通过鼠标移动获得的,水平的移动影响偏航角,竖直的移动影响俯仰角。
实现的原理:储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置与上一帧的位置相差多少。如果水平/竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离。
首先告诉GLFW,应该隐藏光标,并进行捕捉(Capture),用一个简单地配置调用来完成

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

调用这个函数之后,无论我们怎么去移动鼠标,光标都不会显示了,它也不会离开窗口,如果没有使用的话,离开窗口就会失效
为了计算俯仰角和偏航角,我们需要让GLFW监听鼠标移动事件。(和键盘输入相似)我们会用一个回调函数来完成,函数的原型如下:

void mouse_callback(GLFWwindow* window, double xpos, double ypos);

xposypos代表当前鼠标的位置。当我们用GLFW注册了回调函数之后,鼠标一移动mouse_callback函数就会被调用:
鼠标交互的具体实现步骤如下:

  1. 计算鼠标距上一帧的偏移量。
  2. 把偏移量添加到摄像机的俯仰角和偏航角中。
  3. 对偏航角和俯仰角进行最大和最小值的限制。
  4. 计算方向向量。

第一步是计算鼠标自上一帧的偏移量。我们必须先在程序中储存上一帧的鼠标位置,我们把它的初始值设置为屏幕的中心(屏幕的尺寸是800×600):

float lastX = 400, lastY = 300;

然后在鼠标的回调函数中我们计算当前帧和上一帧鼠标位置的偏移量:

float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // 注意这里是相反的,因为y坐标是从底部往顶部依次增大的
lastX = xpos;
lastY = ypos;float sensitivity = 0.05f;
xoffset *= sensitivity;//偏移量乘以了sensitivity(灵敏度)值
yoffset *= sensitivity;

第二步把偏移量加到全局变量pitch和yaw上:

yaw   += xoffset;
pitch += yoffset;

第三步,需要给摄像机添加一些限制,这样摄像机就不会发生奇怪的移动了(这样也会避免一些奇怪的问题)。对于俯仰角,要让用户不能看向高于89度的地方(在90度时视角会发生逆转,所以我们把89度作为极限),同样也不允许小于-89度。这样能够保证用户只能看到天空或脚下,但是不能超越这个限制。我们可以在值超过限制的时候将其改为极限值来实现:

if(pitch > 89.0f)pitch =  89.0f;
if(pitch < -89.0f)pitch = -89.0f;

第四步,通过俯仰角和偏航角计算以得到真正的方向向量:

glm::vec3 front;
front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
front.y = sin(glm::radians(pitch));
front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
cameraFront = glm::normalize(front);

如果你现在运行代码,你会发现在窗口第一次获取焦点的时候摄像机会突然跳一下。这个问题产生的原因是,在你的鼠标移动进窗口的那一刻,鼠标回调函数就会被调用,这时候的xpos和ypos会等于鼠标刚刚进入屏幕的那个位置。这通常是一个距离屏幕中心很远的地方,因而产生一个很大的偏移量,所以就会跳了。我们可以简单的使用一个bool变量检验我们是否是第一次获取鼠标输入,如果是,那么我们先把鼠标的初始位置更新为xpos和ypos值,这样就能解决这个问题;接下来的鼠标移动就会使用刚进入的鼠标位置坐标来计算偏移量了:

if(firstMouse) // 这个bool变量初始时是设定为true的
{lastX = xpos;lastY = ypos;firstMouse = false;
}

最后的代码如下:

void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{float xpos = static_cast<float>(xposIn);float ypos = static_cast<float>(yposIn);if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to toplastX = xpos;lastY = ypos;float sensitivity = 0.1f; // change this value to your likingxoffset *= sensitivity;yoffset *= sensitivity;yaw += xoffset;pitch += yoffset;// make sure that when pitch is out of bounds, screen doesn't get flippedif (pitch > 89.0f)pitch = 89.0f;if (pitch < -89.0f)pitch = -89.0f;glm::vec3 front;front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));front.y = sin(glm::radians(pitch));front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));cameraFront = glm::normalize(front);
}

实现结果如下:
OpenGL鼠标视角交互-编程知识网