LearnOpenGL学习笔记—入门08:Camera
0 前言
本节笔记对应的内容 摄像机
在入门01中我们配置好了环境中我们配置好了环境
在入门02中我们可以检测输入并出现一个有颜色的窗口
在入门03中我们初步学习了图形渲染管线,尝试用不同方法画出了三角形和四边形
在入门04(上)中我们学习了shader和GLSL的相关知识,并给图形加上了变换的颜色以及彩色。
在入门04(下)中我们建立自己的shader类,并能够从外部读取shader的内容。
在入门05中我们了解了有关材质的内容
在入门06中,我们尝试用旋转矩阵以及四元数的方法组成变换矩阵,让它动起来,并对四元数的理解进行了一定阐述。
在入门07中我们学习了坐标系统,了解了从3D空间得到2D平面的过程
前面的教程中我们讨论了观察矩阵以及如何使用观察矩阵移动场景(我们向后移动了一点)。OpenGL本身没有摄像机(Camera)的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。
本节我们将会讨论如何在OpenGL中配置一个摄像机,并且将会讨论FPS风格的摄像机,让你能够在3D场景中自由移动。我们也会讨论键盘和鼠标输入,最终完成一个自定义的摄像机类。
1 摄像机/观察空间概述
2 基础摄像机类实现
新建C++类Camera,我们先在Camera.h中把最基础的参数定义了
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
class Camera
{
public:
~Camera();
//position 相机所在位置 target 相机指向的位置 worldup 世界向上的向量
Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldup);
//摄影机位置
glm::vec3 Position;
//Forward 摄影机的“方向”(一个和朝向相反的向量)
glm::vec3 Forward;
glm::vec3 Right;
//摄影机的上方向
glm::vec3 Up;
//世界的上方向
glm::vec3 WorldUp;
};
在cpp写出对应的构造方法
Camera::Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldup)
{
Position = position;
WorldUp = worldup;
//normalize 归一化变成单位向量
//Forward 摄影机的“方向”(一个和朝向相反的向量)
Forward = glm::normalize(position - target);
//Right 它代表Camera的右方,用世界的上方向与摄影机朝向叉乘
Right = glm::normalize(glm::cross(WorldUp, Forward));
//UP 摄像机的上方向
Up = glm::cross(Forward, Right);
}
之前的摄影机矩阵也叫作lookat矩阵,GLM已经提供了这些支持。
我们要做的只是定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(我们计算右向量使用的那个上向量)。
接着GLM就会创建一个LookAt矩阵,可以把它当作我们的观察矩阵。
定义一个叫做glm::mat4 GetViewMatrix();
的方法
glm::mat4 Camera::GetViewMatrix()
{
//glm::LookAt函数需要一个摄像机位置、一个目标位置和表示世界空间中的上向量的向量。
//它会创建一个观察矩阵。
return glm::lookAt(Position, Position - Forward, WorldUp);
}
我们现在可以尝试一下,在main中#include "Camera.h"
,然后把view改成相机
//实例化相机
Camera camera(glm::vec3(0, 0, 3.0f), glm::vec3(0, 0, 0), glm::vec3(0, 1.0f, 0));
//model
//glm::mat4 modelMat;
//modelMat = glm::rotate(modelMat, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
//view
glm::mat4 viewMat;
// 注意,我们将矩阵向我们要进行移动场景的反方向移动。
//viewMat = glm::translate(viewMat, glm::vec3(0.0f, 0.0f, -3.0f));
viewMat = camera.GetViewMatrix();
//projection
glm::mat4 projMat;
projMat = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
得到和以前一样的结果就可以啦
3 欧拉角摄像机
为了能够改变视角,我们需要改变方向向量,下面先介绍欧拉角的方式。
欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值。
一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:
- 俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。
- 第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。
- 滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。
- 每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。
对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。
给定一个俯仰角和偏航角,我们可以把它们转换为一个代表新的方向向量的3D向量。
把斜边边长定义为1,俯仰角P和偏航角Y推导得到Forward向量
这样我们就有了一个可以把俯仰角和偏航角转化为用来自由旋转视角的摄像机的3维方向向量了。
在相机头文件中加上
//pitch俯仰角和yaw偏航角
Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldup);
float Pitch;
float Yaw;
写出构造方法
Camera::Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldup)
{
Position = position;
WorldUp = worldup;
Pitch = pitch;
Yaw = yaw;
Forward.x = cos(glm::radians(Pitch)) * sin(glm::radians(Yaw));
Forward.y = sin(glm::radians(Pitch));
Forward.z = cos(glm::radians(Pitch)) * cos(glm::radians(Yaw));
Forward = glm::normalize(Forward);
//Right 它代表摄像机空间的x轴的正方向
Right = glm::normalize(glm::cross(WorldUp, Forward));
//UP 一个指向摄像机的正y轴向量
Up = glm::cross(Forward, Right);
}
在main函数中可以改角度试试,pitch yaw都是15度,即看向左下方
//实例化相机 position 相机所在位置 pitch yaw worldup 世界向上的向量
Camera camera(glm::vec3(0, 0, 3.0f),15.0f,15.0f, glm::vec3(0, 1.0f, 0));
接下来我们用鼠标控制视角以及前后左右上下移动,先进行键盘控制前后左右上下,注释掉之前的相机,这时候相机会有个默认位置
//建立camera
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);
Camera camera(cameraPos, cameraFront, cameraUp);
在相机里写一下前后左右上下的方法与移动速度
float cameraPosSpeed;
void PosUpdateForward();
void PosUpdateBackward();
void PosUpdateLeft();
void PosUpdateRight();
void PosUpdateUp();
void PosUpdateDown();
写出对应构造方法
当我们按下WASDQE键的任意一个,摄像机的位置都会相应更新。如果我们希望向前/后/上/下移动,我们就把位置向量加上或减去方向向量。
如果我们希望向左右移动,我们使用叉乘来创建一个右向量(Right Vector),并沿着它相应移动就可以了。
这样就创建了使用摄像机时熟悉的横移(Strafe)效果。
//-Forward是当前相机的朝向
//朝前
void Camera::PosUpdateForward()
{
Position += cameraPosSpeed * -Forward;
}
//朝后
void Camera::PosUpdateBackward()
{
Position -= cameraPosSpeed * -Forward;
}
//朝上
void Camera::PosUpdateUp()
{
Position += cameraPosSpeed * WorldUp;
}
//朝下
void Camera::PosUpdateDown()
{
Position -= cameraPosSpeed * WorldUp;
//左边
void Camera::PosUpdateLeft()
{
Position -= glm::normalize(glm::cross(-Forward, Up)) * cameraPosSpeed;
}
//右边
void Camera::PosUpdateRight()
{
Position += glm::normalize(glm::cross(-Forward, Up)) * cameraPosSpeed;
}
在main.cpp的main函数中写一下鼠标隐藏
//关闭鼠标显示
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
接下里写一下键盘控制,我们想保证移动的匀速,但是因为根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数。
结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。我们必须确保它在所有硬件上移动速度都一样。
图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。
我们把摄像机速度都去乘以deltaTime值。
结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的移动速度需要变得更高来平衡渲染所花去的时间。
使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。
在建立camera之后加上
float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间
在渲染循环的末尾写上
//计算时间差保证不同硬件都是原速
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame =