在上一篇写opengl坐标系统的文章中,有提到视图空间(View Space),也能够称之为摄像机空间,即从摄像机角度去观察对象。MVP转换矩阵中,上篇文章给了一个简单的视图矩阵(View Matrix)将世界空间坐标转换到视图空间坐标,即相对于摄像机的坐标。spa
opengl中实际上并无直接提供摄像机对象,咱们是根据一系列的向量运算在游戏空间中建立了一个摄像机对象,并生成对应的视图矩阵(View Matrix)。code
建立一个摄像机对象,咱们须要构建对应的坐标体系。首先,咱们须要知道咱们是从哪里观察/用摄像机拍摄,因此须要肯定一个摄像机的坐标。在opengl的右手坐标系下,咱们先假定摄像机坐标是cameraPos=(0,0,3),即z轴正方向3个单位的位置;肯定了摄像机的位置以后,利用向量减法,咱们能够从原点出发获得摄像机的方向,cameraDir = vp - vo,不过咱们在这里获得的方向实际上是摄像机拍摄方向的反反向;orm
获得了摄像机方向,再利用一个世界空间内相对于原点的单位向量up=(0,1,0),使用向量叉乘,咱们能够获得右轴向量,cameraRight = up x cameraDir;对象
最后,根据摄像机方向,右轴,再使用向量叉乘,咱们能够获得上轴向量,cameraUp = cameraDir x cameraRight;blog
利用上述获得的方向,右轴,上轴,咱们就能够构建出摄像机坐标系统,利用这些向量咱们能够构造一个称之为LookAt的矩阵,使用这个矩阵就能够将世界空间坐标转换为视图空间、摄像机空间的坐标了。这个矩阵的定义以下:教程
\[LookAt = \begin{bmatrix} {\color{Red}R_x} & {\color{Red}R_y} & {\color{Red}R_z} & 0\\ {\color{Green}U_x} & {\color{Green}U_y} & {\color{Green}U_z} & 0\\ {\color{Blue}D_x} & {\color{Blue}D_y} & {\color{Blue}D_z} & 0\\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} 1 & 0 & 0 & -{\color{Magenta} P_x}\\ 1 & 0 & 0 & -{\color{Magenta} P_y}\\ 1 & 0 & 0 & -{\color{Magenta} P_z}\\ 1 & 0 & 0 & 1\\ \end{bmatrix}\]游戏
R表示右轴向量,U表示上轴向量,D表示方向向量,P则表示摄像机的位置。固然咱们以前引入的glm库,也提供了一个直接生成lookat矩阵的方法,glm::lookAt,该方法接收三个参数,参数1为摄像机的坐标向量,参数2为原点坐标,参数3为相对于原点的单位向量up=(0,1,0)。it
如今咱们可使用lookAt方法,生成一个上一篇文章中获得的视图矩阵(View Matrix)io
view = glm::lookAt(glm::vec3(0, 0, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1.0, 0));
注意这里,咱们将摄像机的坐标设置为(0,0,3),而上一篇文章中咱们的视图位置设置的是(0,0,-3)。咱们能够这样理解,让世界对象往前移动三个单位,换个角度能够认为让摄像机日后移动三个单位。而在咱们使用的右手坐标系中,从咱们眼睛出发,咱们眼前的方向是z轴的负方向,而咱们的脑后则是z轴的正方向。因此咱们在这里将摄像机的坐标设置为(0,0,3)。class
在实际应用中,咱们若是想获得丰富的摄像机效果,主要就是镜头的先后左右推移,视角移动。
镜头的先后左右推移,咱们能够经过修改摄像机的世界坐标来达到效果,在这里针对lookAt参数稍加调整,参数2调整为cameraPos + cameraFront(根据向量加减法可知,这里的cameraFront实际上是前文计算到的cameraDir的反方向),向量位置加上向量方向,以便在咱们推移过程当中摄像机都一直注视目标方向:
switch (dir) { case CameraDir::FORWARD: // 向前 cameraPos += cameraFront * m_moveSpeed; break; case CameraDir::BACKWARD: // 向后 cameraPos -= cameraFront * m_moveSpeed; break; case CameraDir::LEFT: // 向左 cameraPos -= cameraRight * m_moveSpeed; break; case CameraDir::RIGHT: // 向右 cameraPos += cameraRight * m_moveSpeed; break; default: break; }
先后推移是加减摄像头方向向量乘以一个速度,左右推移则是加减摄像头右轴向量乘以一个速度。
而视角的移动,咱们则是调整cameraFront向量,咱们在这里先引入欧拉角的概念,有三种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下图给了直观的表示:
从图中能够看到,俯仰角能够产生镜头上下转换的效果,偏航角则是镜头的左右转换效果,滚转角则是翻转效果。从图中也能够看到,俯仰角表现为y轴数值变更,偏航角表现为x轴数值的变更,滚转角表现为z轴数值变更。根据欧拉公式,咱们能够基于这些角度获得咱们的方向向量。教程中介绍的摄像机系统主要关注的是俯仰角和偏航角,因此基于这两个欧拉角咱们能够获得以下的方向向量:
glm::vec3 front; front.x = cos(_yaw) * cos(_pitch); front.y = sin(_pitch); front.z = sin(_yaw) * cos(_pitch); m_cameraFront = glm::normalize(front);
具体的推导我暂时没看懂,主要是从俯仰角获得x、y、z后,再根据偏航角获得x,z,为何还要再乘上从俯仰角获得的x,z两个数值。
在先后左右推移、旋转操做以后,咱们获得了新的摄像机坐标、方向向量,在代入公式,就能获得新的lookAt矩阵,用于将世界空间坐标转换为摄像机空间坐标。
另外:欧拉角在使用过程当中容易触发万向节死锁,致使有的时候不能旋转到咱们须要的角度。因此,好比在cocos2dx中,是用四元数计算,来进行摄像机视角旋转的操做。四元数没有欧拉角那么直观,不过也有现成的欧拉角转四元数的公式,下面引入一段cocos2dx引擎中的转换代码:
float halfRadx = CC_DEGREES_TO_RADIANS(_rotationX / 2.f), halfRady = CC_DEGREES_TO_RADIANS(_rotationY / 2.f), halfRadz = _rotationZ_X == _rotationZ_Y ? -CC_DEGREES_TO_RADIANS(_rotationZ_X / 2.f) : 0;
float coshalfRadx = cosf(halfRadx), sinhalfRadx = sinf(halfRadx), coshalfRady = cosf(halfRady), sinhalfRady = sinf(halfRady), coshalfRadz = cosf(halfRadz), sinhalfRadz = sinf(halfRadz); _rotationQuat.x = sinhalfRadx * coshalfRady * coshalfRadz - coshalfRadx * sinhalfRady * sinhalfRadz; _rotationQuat.y = coshalfRadx * sinhalfRady * coshalfRadz + sinhalfRadx * coshalfRady * sinhalfRadz; _rotationQuat.z = coshalfRadx * coshalfRady * sinhalfRadz - sinhalfRadx * sinhalfRady * coshalfRadz; _rotationQuat.w = coshalfRadx * coshalfRady * coshalfRadz + sinhalfRadx * sinhalfRady * sinhalfRadz;
首先须要将欧拉角由角度制转为弧度制,而后根据公式就能计算出四元数四个份量的值,再根据这个四元数应用到矩阵计算中,进行旋转操做。