![45b4fd74cd332b14656662203b01971f.gif](https://img-blog.csdnimg.cn/img_convert/45b4fd74cd332b14656662203b01971f.gif)
本文转载于拉Box小能手知乎作者
原文链接:https://zhuanlan.zhihu.com/p/72497359
本文为学习LearnOpenGL的学习笔记,如有书写和理解错误还请大佬扶正;
教程链接:
https://link.zhihu.com/?target=https%3A//learnopengl-cn.github.io/01%2520Getting%2520started/09%2520Camera/
OpenGL本身没有 摄像机(Camera)的概念,但可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种摄像机在移动的感觉,而不是场景在移动。
一,基础概念
1,定义摄像机坐标轴
当我们讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。
要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右测的向量以及一个指向它上方的向量。创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系;
![d243809f54f22a56f1472f010a83d355.png](https://img-blog.csdnimg.cn/img_convert/d243809f54f22a56f1472f010a83d355.png)
代码示例
//摄像机位置
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
//摄像机方向
//把目标点设为原点 摄像机位置 - 目标点 = 方向 为由目标点指向摄像机的向量
glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);
//用向量叉乘的求出摄像机的 右轴
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));
//在用右轴与方向向量 叉乘求出上轴
glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);
2,GLM数学库内置的LookAt函数,创建观察矩阵
![56c5e7bf7cff48b704301fcb5f086fd0.png](https://img-blog.csdnimg.cn/img_convert/56c5e7bf7cff48b704301fcb5f086fd0.png)
其中R是右向量,U是上向量,D是方向向量P是摄像机位置向量;
位置向量是相反的,因为我们最终希望把世界平移到与我们自身移动的相反方向。
LookAt矩阵作为观察矩阵可以很高效地把所有世界坐标变换到刚刚定义的观察空间;
GLM已经提供了这些支持。需要做的只是定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(计算右向量使用的那个上向量)。接着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));
二,摄像机的移动与旋转理论部分
1,设置摄像机的观察矩阵
//初始化 构建摄像机坐标的 三个向量
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);
//观察矩阵
//(cameraPos + cameraFront)为了让摄像机的方向始终平行于Z轴 样能保证无论我们怎么移动,摄像机都会注视着目标方向
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
2,为用WASD移动摄像机声明响应函数
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;
}
3,移动速度/每次移动步长匹配问题
如果设置成常量的话,会因为使用者配置的不同,有些人可能移动很快,而有些人会移动很慢,很影响使用;
程序会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
//在每一帧中我们计算出新的deltaTime以备后用
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
//计算速度使用deltaTime
void processInput(GLFWwindow *window)
{
float cameraSpeed = 2.5f * deltaTime;
...
}
4,摄像机旋转
通过鼠标(或手柄)水平的移动影响摄像机的偏航角,竖直的移动影响摄像机的俯仰角;
工作原理:储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置与上一帧的位置相差多少。如果水平/竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离;
通过摄像机的俯仰角与偏航角来最后确定生相机的朝向,最后与摄像机移动一起构建改变之后的观察矩阵;
//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));
三,摄像机的移动与旋转实践部分
封装摄像机类CameraBase.h文件
#ifndef CAMERA_H
#define CAMERA_H
#include
#include
#include
#include
// 定义枚举 上下左右
enum Camera_Movement {
FORWARD,