写在前面
上一节视变换(view transformation) ,介绍了相机的设置参数,并建立了圆形坐标系和球形坐标系下的相机位置随着时间改变的绘制立方体程序。程序中用户无法通过键盘和鼠标来和场景中物体交互,本节实现一个第一人称相机来更好地与场景中物体交互。本节代码可以在我的github下载。
通过本节可以了解到
- 欧拉角
- 第一人称相机的实现
欧拉角和相机
在上一节中,我们设置相机通过三个参数,分别为相机位置eye、相机指向目标位置target,以及viewUp向量。我们的目标是建立这样一个相机系统:通过键盘AD键在场景中左右移动,通过WS键在场景中前后移动,通过鼠标上下移动实现观察者向上和向下看的效果,通过鼠标左右移动实现观察者xoz水平面上的观察方向改变,还可以实现绕z轴的旋转来调节相机。这个过程如下图所示:
称绕着x轴的旋转为pitch角度,绕着y轴的旋转为yaw角度,称绕着z轴的旋转为roll角度。一般地实现第一人称相机只需要考虑pitch和yaw角度即可,roll角度用于飞行器的相机模型。
通过旋转pitch、yaw、roll角度,并将相机移到指定位置eye,那么对应的视变换矩阵为:
view=(T∗Rroll∗Ryaw∗Rpitch)−1
要实现第一人称相机需要考虑的因素包括:
- 如何获取pitch和yaw旋转角度 ?
- 如何更新相机位置?
- 场景中移动速度如何决定?
- 如何获取视变换矩阵?
- 如何实现缩放 ?
获取pitch和yaw旋转角度
通过鼠标的水平位移来反映yaw角度的增长,垂直位移来反映pitch角度的增长,注册鼠标移动回调函数,代码如下:
void mouse_move_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouseMove) // 首次鼠标移动
{
lastX = xpos;
lastY = ypos;
firstMouseMove = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.handleMouseMove(xoffset, yoffset);
}
程序初始化时,记录第一次移动位置为lastX和lastY,从第二次移动时开始处理,这样可以避免窗口刚出现时,xoffset和yoffset过大而引起相机抖动。
获取了鼠标位移,我们利用这个位移来建立与pitch和yaw联系。鼠标移动时,可以提供灵敏度供用户选择,这里我们使用默认值,代码如下:
// 处理鼠标移动
void handleMouseMove(GLfloat xoffset, GLfloat yoffset)
{
xoffset *= this->mouse_sensitivity; // 用鼠标灵敏度调节角度变换
yoffset *= this->mouse_sensitivity;
this->pitchAngle += yoffset;
this->yawAngle += xoffset;
// 保证角度在合理范围内
if (this->pitchAngle > MAX_PITCH_ANGLE)
this->pitchAngle = MAX_PITCH_ANGLE;
if (this->pitchAngle < -MAX_PITCH_ANGLE)
this->pitchAngle = -MAX_PITCH_ANGLE;
if (this->yawAngle < 0.0f)
this->yawAngle += 360.0f;
this->updateCameraVectors(); // 更新相机向量
}
为了避免出现万向锁,控制pitch在[-89.0,89.0]的范围内,控制yaw在[0,360.0]范围内。
重新计算forward等向量
对相机进行pitch和yaw角度的旋转后,我们需要重新计算相机的forward向量,以及side向量用来完成相机的前后左右移动。这两个向量都是在世界坐标系下给定的。通过 (Ryaw∗Rpitch) 我们可以计算出相机的坐标系下的点经过旋转后,在世界坐标系下的值。计算得到: