OpenGL学习笔记《七》摄像机

  在上一篇写opengl坐标系统的文章中,有提到视图空间(View Space),也可以称之为摄像机空间,即从摄像机角度去观察对象。MVP转换矩阵中,上篇文章给了一个简单的视图矩阵(View Matrix)将世界空间坐标转换到视图空间坐标,即相对于摄像机的坐标。

  opengl中实际上并没有直接提供摄像机对象,我们是根据一系列的向量运算在游戏空间中创建了一个摄像机对象,并生成对应的视图矩阵(View Matrix)。

  创建一个摄像机对象,我们需要构建对应的坐标体系。首先,我们需要知道我们是从哪里观察/用摄像机拍摄,所以需要确定一个摄像机的坐标。在opengl的右手坐标系下,我们先假定摄像机坐标是cameraPos=(0,0,3),即z轴正方向3个单位的位置;确定了摄像机的位置之后,利用向量减法,我们可以从原点出发得到摄像机的方向,cameraDir = vp - vo,不过我们在这里得到的方向其实是摄像机拍摄方向的反反向;

  得到了摄像机方向,再利用一个世界空间内相对于原点的单位向量up=(0,1,0),使用向量叉乘,我们可以得到右轴向量,cameraRight = up x cameraDir;

  最后,根据摄像机方向,右轴,再使用向量叉乘,我们可以得到上轴向量,cameraUp = cameraDir x cameraRight;

  利用上述得到的方向,右轴,上轴,我们就可以构建出摄像机坐标系统,利用这些向量我们可以构造一个称之为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)。

  现在我们可以使用lookAt方法,生成一个上一篇文章中得到的视图矩阵(View Matrix)

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)。

  在实际应用中,我们如果想得到丰富的摄像机效果,主要就是镜头的前后左右推移,视角移动。

  镜头的前后左右推移,我们可以通过修改摄像机的世界坐标来达到效果,在这里针对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;

  首先需要将欧拉角由角度制转为弧度制,然后根据公式就能计算出四元数四个分量的值,再根据这个四元数应用到矩阵计算中,进行旋转操作。

  

posted @ 2019-10-15 17:58  Le Ciel  阅读(873)  评论(0编辑  收藏  举报