zoukankan      html  css  js  c++  java
  • 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 = egin{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} * egin{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;

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

      

  • 相关阅读:
    Java实现各种内部排序算法
    Java实现堆排序(大根堆)
    Java对象的序列化和反序列化
    Java实现链式存储的二叉查找树(递归方法)
    337. House Robber III(包含I和II)
    318. Maximum Product of Word Lengths
    114. Flatten Binary Tree to Linked List
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    96. Unique Binary Search Trees(I 和 II)
  • 原文地址:https://www.cnblogs.com/zhong-dev/p/11678698.html
Copyright © 2011-2022 走看看