OpenGL学习笔记(六)

OpenGL中级篇(二)

摄像机/观察空间

以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像位置与方向的观察坐标。
要定义一个摄像机,需要它在世界空间中的位置、观察的方向、一个指向它右侧的向量以及一个指向它上方的向量。实际上创建了一个三个单位轴相互垂直的、以摄像机位置为原点的坐标系。
在这里插入图片描述

1. 摄像机位置

获取摄像机位置很简单,摄像机位置简单来说就是世界空间中一个指向摄像机位置的向量:

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

注意:正z轴的方向是从屏幕指向观察者,如果希望摄像机向后移动,就沿着z轴的正方向移动。
在这里插入图片描述

2. 摄像机方向

接着需要的向量是摄像机方向,这里指摄像机指向哪个方向。
让摄像机指向场景原点:(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轴负方向的向量

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

在叉乘和一些小技巧的帮助下,创建了所有构成摄像机 / 观察空间的向量。使用这些摄像机向量就可以创建一个LookAt矩阵,它对摄像机的一些相关操作非常有用。

LookAt矩阵

在这里插入图片描述
其中R是右向量,U是上向量,D是方向向量,P是摄像机位置向量。注意,位置向量是相反的,因为最终希望把世界平移到与自身移动的相反方向。把这个LookAt矩阵作为观察矩阵可以很高效地把所有世界坐标变换到刚刚定义的观察空间。Look At矩阵就像它的名字表达的那样:创建一个看着(Look at)给定目标的观察矩阵。
GLM中,已提供了上述的运算支持。我们只需定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(计算右向量时使用的那个上向量)。接着GLM就会创建一个Look At矩阵,可以把它当作我们的观察矩阵:

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

摄像机在场景中旋转

来实现让摄像机在场景中旋转,将摄像机的注视点保持在(0, 0, 0)
需要用到三角学的知识,实现在每一帧创建一个x和z坐标,它代表圆上的一点,将会使用它作为摄像机的位置。通过遍历圆上的所有点,实现摄像机绕着场景旋转。
首先定义这个圆的半径radius,在每次渲染迭代中使用GLFWglfwGetTime() 重新创建观察矩阵,来扩大这个圆。
在这里插入图片描述

最终效果

请添加图片描述

鼠标输入控制视角移动

首先必须设置一个摄像机系统,定义一些摄像机变量:
在这里插入图片描述
根据鼠标的输入改变cameraFront向量。然后,根据鼠标移动改变方向向量有点复杂,需要一些三角学知识。
欧拉角(Euler Angle) 是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:
在这里插入图片描述
俯仰角是描述如何往上或往下看的角,如图一所示。
偏航角表示往左和往右看的程度。
滚转角代表如何翻滚摄像机
每个欧拉角都有一个值来表示,把三个角结合起来就能够计算3D空间中任何的旋转向量了。
对于摄像机系统来说,只需关心俯仰角和偏航角,所以不去讨论滚转角。
给定一个俯仰角和偏航角,可以把它们转换为一个代表新的方向向量的3D向量。俯仰角和偏航角转换为方向向量的处理需要一些三角学知识,先从最基本的情况开始:
如果把斜边边长定义为1,就能知道邻边的长度是cos x / h = cos x / 1 = cos⁡ x,它的对边是sin y / h = sin y / 1 = sin y。这样便获得了能得到x和y方向长度的通用公式,它们取决于所给角度。使用它来计算方向向量的分量:
在这里插入图片描述

俯仰角

这个三角形看起来和前面的三角形很像,所以如果想象自己在xz平面上,看向y轴。
在这里插入图片描述
从图中可以看到对于一个给定俯仰角的y值等于sin⁡ θ:

direction.y = sin(glm::radians(pitch)); // 注意要先把角度转为弧度
从三角形中可以看到x, z的值等于:
direction.x = cos(glm::radians(pitch));
direction.z = cos(glm::radians(pitch));

偏航角

接着为偏航角找到需要的分量,就像俯仰角的三角形一样,可以看到x分量取决于cos(yaw)的值,z值取决于偏航角的正弦值。
在这里插入图片描述
把x分量的值,z分量的值分别加到前面的值中,会得到基于俯仰角和偏航角的方向向量:

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

这样就获得一个可以把俯仰角和偏航角转化为用来自由旋转视角的摄像机的3维方向向量了。

鼠标输入

偏航角和俯仰角是通过鼠标(或手柄)移动获得的,水平的移动影响偏航角,竖直的移动影响俯仰角。

原理:储存上一帧鼠标的位置,在当前帧中计算当前鼠标位置与上一帧的位置相差多少。如果水平 / 竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离。
首先应该隐藏光标,并捕捉(Capture) 它。捕捉光标表示的是,如果焦点在程序上,光标应该停留在窗口中(除非程序失去焦点或者退出)。可以用一个简单地配置调用来完成:

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

在处理FPS风格摄像机的鼠标输入的时候,必须在最终获取方向向量之前做下面这几步:

1、计算鼠标距上一帧的偏移量。
2、把偏移量添加到摄像机的俯仰角和偏航角中。
3、对俯仰角进行最大和最小值的限制。
4、计算方向向量。

第一步是计算鼠标自上一帧的偏移量。必须先在程序中储存上一帧的鼠标位置,可以把它的初始值设置为.屏幕的中心(屏幕的尺寸是800x600):

float lastX = 400, lastY = 300;

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

float xoffset = xpos - lastX;
float yoffset = lastY - ypos;

// 注意这里是相反的,因为y坐标是从底部往顶部依次增大的

lastX = xpos;
lastY = ypos;
float sensitivity = 0.05f;
xoffset *= sensitivity;
yoffset *= sensitivity;

注意需要把偏移量乘以了sensitivity(灵敏度)值。如果忽略这个值,鼠标移动就会太大。
接下来把偏移量加到全局变量pitch和yaw上:

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));
cameraFront = glm::normalize(front);

计算出来的方向向量就会包含根据鼠标移动计算出来的所有旋转了。

缩放

来实现一个缩放(Zoom)接口,在之前课程中讲述了视野(Field of View)或fov定义了观察者可以看到场景中多大的范围。当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。可以使用鼠标的滚轮来放大。
与鼠标移动一样,首先定义一个鼠标滚轮的回调函数:
在这里插入图片描述
当滚动鼠标滚轮的时候,yoffset值代表竖直滚动的大小。当scroll_callback()被调用后,我们改变全局变量fov变量的内容。因为45.0f是默认的视野值,通过设置把缩放级别(Zoom Level)限制在1.0f到45.0f。
在这里插入图片描述
将fov的值给到透视投影矩阵:

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

最后注册鼠标滚轮的回调函数:

glfwSetScrollCallback(window, scroll_callback);

最终实现了一个简单的摄像机系统,包含有缩放功能,能够在3D环境中自由移动。

最终效果

请添加图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42050609/article/details/125145422