现代OpenGL学习笔记七:摄像机

摄像机

上篇学习笔记重点学习了OpenGL里的坐标系统,学习了局部空间、世界空间、观察空间、裁剪空间等概念,并应用旋转、平移、缩放等实现物体在场景中的移动,初步学习了利用摄像机设置观察矩阵view(将其后移),本次将重点学习OpenGL的摄像机,利用摄像机设置各种观察矩阵,并实现不同的场景变换。

推荐参考原文:https://learnopengl-cn.github.io/
https://learnopengl-cn.github.io/01 Getting started/09 Camera/


前面的教程中我们讨论了观察矩阵以及如何使用观察矩阵移动场景(我们向后移动了一点)。OpenGL本身没有摄像机(Camera)的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。(模型观察二元性)

本节我们将会讨论如何在OpenGL中配置一个摄像机,并且将会讨论FPS风格的摄像机,让你能够在3D场景中自由移动,我们也会学习使用键盘和鼠标输入,最终完成一个自定义的摄像机类

摄像机/观察空间


当我们讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右测的向量以及一个指向它上方的向量。细心的读者可能已经注意到我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。


现代OpenGL学习笔记七:摄像机_第1张图片

1、摄像机位置(Position)
获取摄像机位置很简单。摄像机位置简单来说就是世界空间中一个指向摄像机位置的向量(Z轴是从屏幕指向你的,摄像机后移,就是往Z轴正方向移动):

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

2、摄像机方向(Direction)
下一个需要的向量是摄像机的方向,这里指的是摄像机指向哪个方向。现在我们让摄像机指向场景原点:(0, 0, 0)。还记得如果将两个矢量相减,我们就能得到这两个矢量的差吗?用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。由于我们知道摄像机指向z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量:

glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

方向向量(Direction Vector)并不是最好的名字,因为它实际上指向从它到目标向量的相反方向(译注:注意看前面的那个图,蓝色的方向向量大概指向z轴的正方向,与摄像机实际指向的方向是正好相反的)。

3、右轴
我们需要的另一个向量是一个右向量(Right Vector),它代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的方向向量进行叉乘。两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向(注意是摄像机的x轴)的那个向量(如果我们交换两个向量叉乘的顺序就会得到相反的指向x轴负方向的向量):

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

4、上轴
现在我们已经有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了:我们把右向量和方向向量进行叉乘:

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

在叉乘和一些小技巧的帮助下,我们创建了所有构成观察/摄像机空间的向量。对于想学到更多数学原理的读者,提示一下,在线性代数中这个处理叫做格拉姆—施密特正交化(Gram-Schmidt Process)。使用这些摄像机向量我们就可以创建一个LookAt矩阵了,它在创建摄像机的时候非常有用。

Look At


使用矩阵的好处之一是如果你使用3个相互垂直(或非线性)的轴定义了一个坐标空间,你可以用这3个轴外加一个平移向量来创建一个矩阵,并且你可以用这个矩阵乘以任何向量来将其变换到那个坐标空间。这正是LookAt矩阵所做的,现在我们有了3个相互垂直的轴和一个定义摄像机空间的位置坐标,我们可以创建我们自己的LookAt矩阵了(lookAt矩阵就是决定了摄像机在哪(摄像机位置),看向哪(目标位置)、还有确定一个上向量(与OpenGL的y轴正方向相匹配)):
L o o k A t = [ R x R y R z 0 U x U y U z 0 D x D y D z 0 0 0 0 1 ] ∗ [ 1 0 0 − P x 0 1 0 − P y 0 0 1 − P z 0 0 0 1 ] 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{purple}{P_x} \\ 0 & 1 & 0 & -\color{purple}{P_y} \\ 0 & 0 & 1 & -\color{purple}{P_z} \\ 0 & 0 & 0 & 1 \end{bmatrix} LookAt=RxUxDx0RyUyDy0RzUzDz00001100001000010PxPyPz1
其中, R \color{red}R R是右向量, U \color{green}U U是上向量, D \color{blue}D D是方向向量,P是摄像机位置向量。注意,位置向量是相反的,因为我们最终希望把世界平移到与我们自身移动的相反方向(沿Z轴正方向移动摄像机,就是远离场景中的物体;这个操作也就相当于把物体沿着z轴负方向移动,上一个学习笔记中的view矩阵就是朝后平移(负值)以达到远离场景中的物体)

把这个LookAt矩阵作为观察矩阵可以很高效地把所有世界坐标变换到刚刚定义的观察空间。LookAt矩阵就像它的名字表达的那样:它会创建一个看着(Look at)给定目标的观察矩阵。

GLM已经提供了一个lookAt矩阵,我们要做的只是定义一个摄像机位置(position),一个目标位置(target position)和一个表示世界空间中的上向量的向量(我们计算右向量使用的那个上向量),接着GLM就会创建一个LookAt矩阵,我们可以把它当作我们的观察矩阵:

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), 
       glm::vec3(0.0f, 0.0f, 0.0f), 
       glm::vec3(0.0f, 1.0f, 0.0f));

glm::LookAt函数需要一个摄像机位置目标位置上向量,它会创建一个和在上一节使用的一样的观察矩阵。

在学习使用键盘鼠标等输入操作之前,学习通过glfw时间函数把我们的摄像机在场景中一直旋转,我们会将摄像机的注视点保持在(0, 0, 0)。

我们需要用到一点三角学的知识来在每一帧创建一个x和z坐标(也就是把摄像机绕着Y轴进行旋转),它会代表圆上的一点,我们将会使用它作为摄像机的位置(Postion)。通过重新计算x和z坐标,我们会遍历圆上的所有点,这样摄像机就会绕着场景旋转了。我们预先定义这个圆的半径radius,在每次渲染迭代中使用GLFW的glfwGetTime函数重新创建观察矩阵,来扩大这个圆。

float radius = 10.0f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
glm::mat4 view;
view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0)); 

顶点着色器的代码为:

#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
      gl_Position = projection * view * model * vec4(aPos, 1.0f);
      TexCoord = aTexCoord;
}

片段着色器代码为:

#version 460 core
out vec4 fragColor;

in vec2 TexCoord;

/*多个纹理*/
uniform sampler2D texture1;/*sampler采样器 纹理对象数据类型*/
uniform sampler2D texture2;

void main()
{
   /*片段着色器访问纹理对象,80%container+20%awesomeface*/  
   fragColor=mix(texture(texture1,TexCoord), texture(texture2, TexCoord), 0.2);
}

修改坐标系统笔记中的观察矩阵,运行代码,会看到箱子在场景中不断地旋转。

现代OpenGL学习笔记七:摄像机_第2张图片

自由移动


首先我们必须设置一个摄像机系统,所以在我们的程序前面定义一些摄像机变量很有用:

glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f);

LookAt函数现在成了:

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
//cameraPos + cameraFront即为摄像机目标位置

我们首先将摄像机位置设置为之前定义的cameraPos。目标位置是当前的位置cameraPos加上我们刚刚定义的方向向量cameraFront,这样能保证无论我们怎么移动,摄像机都会注视着目标方向(把摄像机的方向固定到平行于z轴,也就是在摄像机的位置处给定一个方向)。让我们设置一下这些向量,在按下某些按钮时更新cameraPos向量。

我们已经为GLFW的键盘输入定义过一个processInput函数了,我们来新添加几个需要检查的按键命令:

void processInput(GLFWwindow *window)
{
    ...
    float cameraSpeed = 0.05f; // adjust accordingly
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        cameraPos += cameraSpeed * cameraFront;//摄像机前移
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        cameraPos -= cameraSpeed * cameraFront;//摄像机后移
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;//摄像机左移
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;//摄像机右移
        //单位右向量是(1,0,0),也就是增加x值
}

当我们按下WASD键的任意一个,摄像机的位置都会相应更新。如果我们希望向前或向后移动,我们就把位置向量加上或减去方向向量。如果我们希望向左右移动,我们使用叉乘来创建一个右向量(Right Vector),并沿着它相应移动就可以了。这样就创建了使用摄像机时熟悉的横移(Strafe)效果。

注意,我们对右向量进行了标准化。如果我们没对这个向量进行标准化,最后的叉乘结果会根据cameraFront变量返回大小不同的向量。如果我们不对向量进行标准化,我们就得根据摄像机的朝向不同加速或减速移动了,但如果进行了标准化移动就是匀速的。虽然移动速度和系统有关,你可能会需要调整一下cameraSpeed。

移动速度


目前我们的移动速度是个常量。理论上没什么问题,但是实际情况下根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数。结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。当你发布你的程序的时候,你必须确保它在所有硬件上移动速度都一样。

图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的摄像机移动速度需要变得更快来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。

我们跟踪两个全局变量来计算出deltaTime值:

float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

在每一帧中(While循环中)我们计算出新的deltaTime以备后用。

float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

现在我们有了deltaTime,在计算速度的时候可以将其考虑进去了:

void processInput(GLFWwindow *window)
{
  float cameraSpeed = 2.5f * deltaTime;
  ...
}

运行代码可以得到一个在任何系统上移动速度都差不多的摄像机观察系统。

视角移动


键盘输入的操作只能前后左右的移动,采用鼠标可以对摄像机系统进行旋转,从而改变视角,为此我需要依据鼠标的输入改变cameraFront的值,其本质就是改变我们的观察方向。

欧拉角

欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:


现代OpenGL学习笔记七:摄像机_第3张图片

俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。

目前,我们只关心俯仰角与偏航角,定一个俯仰角和偏航角,我们可以把它们转换为一个代表新的方向向量的3D向量。俯仰角和偏航角转换为方向向量的处理需要一些基本的三角学知识,我们先从最基本的情况开始:
俯仰角

现代OpenGL学习笔记七:摄像机_第4张图片

如果我们想象自己在xz平面上,看向y轴,我们将得到y轴方向的强度(Strength)(我们往上或往下看多少),其实就是在y轴上的分量,斜边设置为1,则其值为:

//direction看成是前轴,cameraFront
direction.y = sin(glm::radians(pitch)); // 注意我们先把角度转为弧度

此时,其在xz平面上的分量为cos(glm::radians(pitch)),注意此时的这个分量是由xz平面的两个轴的坐标共同决定的,在得到偏航角之后就可以知道x、z轴上的分量。
偏航角


现代OpenGL学习笔记七:摄像机_第5张图片

跟俯仰角的三角形一样,我们可以看到x分量取决于偏航角的余弦值cos(yaw),z值取决于偏航角的正弦值sin(yaw)(个人觉得即使交换x、z的值也没什么),需要注意的是,此时的斜边图中绿色的那段)就是由俯仰角pitch得到的余弦值cos(glm::radians(pitch)),从而可得到基于俯仰角和偏航角的方向向量:

 // 译注:direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的
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));

鼠标输入


偏航角和俯仰角是通过鼠标(或手柄)移动获得的,水平的移动影响偏航角,竖直的移动影响俯仰角。它的原理就是,储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置与上一帧的位置相差多少。如果水平/竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离。

首先我们要告诉GLFW,它应该隐藏光标,并捕捉(Capture)它。捕捉光标表示的是,如果焦点在你的程序上(译注:即表示你正在操作这个程序,Windows中拥有焦点的程序标题栏通常是有颜色的那个,而失去焦点的程序标题栏则是灰色的),光标应该停留在窗口中(除非程序失去焦点或者退出)。我们可以用一个简单地配置调用来完成:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

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

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

这里的xpos和ypos代表当前鼠标的位置。当我们用GLFW注册了回调函数之后,鼠标一移动mouse_callback函数就会被调用:

glfwSetCursorPosCallback(window, mouse_callback);

我们必须在最终获取方向向量之前做下面这几步:

  • 计算鼠标距上一帧的偏移量。
  • 把偏移量添加到摄像机的俯仰角和偏航角中。
  • 对偏航角和俯仰角进行最大和最小值的限制。
  • 计算方向向量。
    第一步是计算鼠标自上一帧的偏移量。我们必须先在程序中储存上一帧的鼠标位置,我们把它的初始值设置为屏幕的中心(屏幕的尺寸是800x600):
float lastX = 400, lastY = 300;

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

 //鼠标响应函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{ 
    //第一次得到鼠标信息时只需要保存坐标即可
    if (firstMouse)// 这个bool变量初始时是设定为true的
    {
       lastX = xpos;
       lastY = ypos;
       firstMouse = false;
    }
       float xoffset = xpos - lastX;
       /*注意这里是相反的,因为获取的鼠标的y坐标是从顶部往底部依次增加的,
        往下拖动鼠标时,y值增加,这与openGL里的y轴正方向是相反的*/
       float yoffset = lastY - ypos;
       lastX = xpos;
       lastY = ypos;
        	
       float sensitivity = 0.05f;//灵敏度值,如果我们忽略这个值,鼠标移动就会太大了
       xoffset *= sensitivity;
       yoffset *= sensitivity;
       
       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));
     
    //lookAt 的前轴
     cameraFront = glm::normalize(front);
}

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

缩放


我们还会来实现一个缩放(Zoom)接口。在之前的教程中我们说视野(Field of View)或fov定义了我们可以看到场景中多大的范围。当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。我们会使用鼠标的滚轮来放大。与鼠标移动、键盘输入一样,我们需要一个鼠标滚轮的回调函数:

    void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
    {
      if(fov >= 1.0f && fov <= 45.0f)
        fov -= yoffset;
      if(fov <= 1.0f)
        fov = 1.0f;
      if(fov >= 45.0f)
        fov = 45.0f;
    }

当滚动鼠标滚轮的时候,yoffset值代表我们竖直滚动的大小。当scroll_callback函数被调用后,我们改变全局变量fov变量的内容。因为45.0f是默认的视野值,我们将会把缩放级别(Zoom Level)限制在1.0f到45.0f。

我们现在在每一帧都必须把透视投影矩阵上传到GPU,但现在使用fov变量作为它的视野:

projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);

最后不要忘记注册鼠标滚轮的回调函数:

glfwSetScrollCallback(window, scroll_callback);

我们就实现了一个简单的摄像机系统了,它能够让我们在3D环境中自由移动,可以检查下代码。

摄像机类


编写摄像机的占用很多的篇幅,通过创建自己的摄像机类,以摄像机对象的形式完成大部分的工作,其代码如下:

#ifndef CAMERA_H
#define CAMERA_H

#include 
#include 
#include 

#include 

// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods
enum Camera_Movement {
    FORWARD,
    BACKWARD,
    LEFT,
    RIGHT
};

// Default camera values
const float YAW         = -90.0f; //偏航角设置为0的话初始的cameraFront就指向右侧(1,0,0),而初始的cameraFront需指向z的负半轴
const float PITCH       =  0.0f;  //俯仰角设置为0,正视物体
const float SPEED       =  2.5f;  //摄像机移动速度
const float SENSITIVITY =  0.1f;  //鼠标的移动灵敏度值
const float ZOOM        =  45.0f; //投影视野值 


// An abstract camera class that processes input and calculates the corresponding Euler Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:
    // Camera Attributes
    glm::vec3 Position;
    glm::vec3 Front;
    glm::vec3 Up;
    glm::vec3 Right;
    glm::vec3 WorldUp;
    // Euler Angles
    float Yaw;
    float Pitch;
    // Camera options
    float MovementSpeed;//摄像机移动速度调节参数
    float MouseSensitivity;//鼠标的移动灵敏度值
    float Zoom;

    // Constructor with vectors
    Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = position;
        WorldUp = up;
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();//会创建前向量,右向量和上向量
    }
    // Constructor with scalar values
    Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM)
    {
        Position = glm::vec3(posX, posY, posZ);
        WorldUp = glm::vec3(upX, upY, upZ);
        Yaw = yaw;
        Pitch = pitch;
        updateCameraVectors();//会创建前向量,右向量和上向量
    }

    // Returns the view matrix calculated using Euler Angles and the LookAt Matrix
    glm::mat4 GetViewMatrix()
    {
        return glm::lookAt(Position, Position + Front, Up);
    }

    /* Processes input received from any keyboard-like input system. Accepts input parameter in the form of camera 
    defined ENUM (to abstract it from windowing systems)*/
    void ProcessKeyboard(Camera_Movement direction, float deltaTime)
    {
        float velocity = MovementSpeed * deltaTime;
        if (direction == FORWARD)
            Position += Front * velocity;
        if (direction == BACKWARD)
            Position -= Front * velocity;
        if (direction == LEFT)
            Position -= Right * velocity;//创建对象时,会自动创建右向量,右向量不需要被改变
        if (direction == RIGHT)
            Position += Right * velocity;
    }

    // Processes input received from a mouse input system. Expects the offset value in both the x and y direction.
    void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true)
    {
        xoffset *= MouseSensitivity;
        yoffset *= MouseSensitivity;

        Yaw   += xoffset;
        Pitch += yoffset;

        // Make sure that when pitch is out of bounds, screen doesn't get flipped
        if (constrainPitch)
        {
            if (Pitch > 89.0f)
                Pitch = 89.0f;
            if (Pitch < -89.0f)
                Pitch = -89.0f;
        }

        // Update Front, Right and Up Vectors using the updated Euler angles
        updateCameraVectors();
    }

    // Processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
    void ProcessMouseScroll(float yoffset)
    {
        if (Zoom >= 1.0f && Zoom <= 45.0f)
            Zoom -= yoffset;
        if (Zoom <= 1.0f)
            Zoom = 1.0f;
        if (Zoom >= 45.0f)
            Zoom = 45.0f;
    }

private:
    // Calculates the front vector from the Camera's (updated) Euler Angles
    void updateCameraVectors()
    {
        // Calculate the new Front vector
        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));
        Front = glm::normalize(front);//初始值为(0,0,-1)
        // Also re-calculate the Right and Up vector
        Right = glm::normalize(glm::cross(Front, WorldUp));  // Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
        Up    = glm::normalize(glm::cross(Right, Front));
    }
};
#endif

采用摄像机类编写的上述场景移动的代码可以在这里找到

你可能感兴趣的:(OpenGL)