0%

【光栅化渲染器】(五)相机

上一节实现了纹理映射,但观察矩阵和投影矩阵目前还是在主函数中设定的,为此我们需要一个相机类来管理,同时方便之后一些算法的实现。

MVP 矩阵中, M 矩阵负责改变模型在世界空间的位置和姿态,V 矩阵需要相机位置和相机的三个向量(up、right、front)来确定,P 矩阵跟视场大小、宽高比和远近平面位置有关。因此相机类主要负责管理确定 V 和 P 矩阵的参数。

这里相机类的实现可以参考之前光线追踪器中的相机类,只需要给定相机的位置、lookat 位置和在世界空间的 up 方向就可以确定相机的三个向量 up、right 和 front;对于相机旋转,我们使用欧拉角,由于大部分第一人称游戏也不支持滚转角旋转,因此我们也只实现俯仰角和偏航角,具体计算方法也很简单,以(0, 0, -1)为默认观察方向:

  • 对于每一个 front 向量,将其投影到 XOZ 平面(即把 y 置 0 再单位化)
  • front 向量和投影向量的夹角即为俯仰角,需要注意俯仰角不能超过 90 度否则整个视野会倒过来
  • 投影向量与(0, 0, -1)的夹角为偏航角

由此我们可以写出相机类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#pragma once
#ifndef CAMERA_H
#define CAMERA_H

#include "Math.h"

class Camera
{
public:

glm::vec3 Position;
glm::vec3 Front;
glm::vec3 Up;
glm::vec3 Right;
glm::vec3 WorldUp;
// 视场和宽高比
float Fov;
float Aspect;
// 远近平面距离
float Near;
float Far;
// 俯仰和偏航角
float Pitch;
float Yaw;

Camera(
glm::vec3 position = glm::vec3(0.0f, 0.0f, 1.0f),
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f),
glm::vec3 lookat = glm::vec3(0.0f, 0.0f, 0.0f),
float fov = 60.0f,
int w = 800,
int h = 600,
float n = 0.3f,
float f = 100
) :
Position(position), WorldUp(up), Fov(glm::radians(fov)), Aspect((float)w / h), Pitch(0), Yaw(0), Near(n), Far(f)
{
Front = glm::normalize(lookat - Position);
Right = glm::normalize(glm::cross(Front, WorldUp));
Up = glm::normalize(glm::cross(Right, Front));

// 根据 Front 向量计算欧拉角
glm::vec3 WorldFront(0, 0, -1);
glm::vec3 FrontXZ = glm::normalize(glm::vec3(Front.x, 0, Front.z));
float yd = glm::dot(WorldFront, FrontXZ);
float pd = glm::dot(Front, FrontXZ);
if (yd > 1.0)
yd = 1.0;
if (yd < -1)
yd = -1.0;
if (pd > 1.0)
pd = 1.0;
if (pd < -1)
pd = -1.0;
Yaw = glm::degrees(acos(yd));
Pitch = glm::degrees(acos(pd));
}

glm::mat4 ViewMatrix()
{
return GetViewMatrix(Position, Front, Right, Up);
}

glm::mat4 PerspectiveMatrix()
{
return GetPerspectiveMatrix(Fov, Aspect, Near, Far);
}

void UpdateFov(float fov = 60.0f) {
Fov = glm::radians(fov);
}

void UpdateAspect(int w, int h) {
Aspect = (float)w / h;
}

// 更改俯仰角,更改相机姿态后要重新计算三个向量
void RotatePitch(float angle) {
Pitch += angle;
if (Pitch > 89.0)
Pitch = 89.0;
if (Pitch < -89.0)
Pitch = -89.0;
UpdateCameraVectors();
}
// 更改偏航角,更改相机姿态后要重新计算三个向量
void RotateYaw(float angle) {
Yaw += angle;
if (Yaw > 360)
Yaw = 0;
if (Yaw < 0)
Yaw = 360;
UpdateCameraVectors();
}

private:
// 更新相机三个向量,根据俯仰角和偏航角计算
void UpdateCameraVectors()
{
glm::vec3 front;
front.x = -sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
front.y = sin(glm::radians(Pitch));
front.z = -cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
Front = glm::normalize(front);
Right = glm::normalize(glm::cross(Front, WorldUp));
Up = glm::normalize(glm::cross(Right, Front));
}
};

#endif

相机应该作为全局变量,以便于之后的裁剪等算法获取相机相关的参数,因此我们在 Global.h 中加入全局变量的定义,将渲染管线以及之后要实现的材质等也设置为全局对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#pragma once
#ifndef GLOBEL_H
#define GLOBEL_H

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <cstdlib>
#include <cmath>
#include <vector>

// MVP变换矩阵
glm::mat4 ModelMatrix;
glm::mat4 ViewMatrix;
glm::mat4 ProjectMatrix;
// 视口变换矩阵
glm::mat4 ViewPortMatrix;
// 法线变换矩阵
glm::mat3 NormalMatrix;

const float PI = 3.14159265359;
// 环境光
const glm::vec3 Ambient = glm::vec3(0.5, 0.5, 0.5);

// 相机类
class Camera;
// 渲染管线类
class Draw;
// 材质类
class Material;

// 全局变量
Camera* camera;
Draw* dw;
Material* currentMat;

// 平行光
class DirectionLight;
// 聚光灯
class SpotLight;
// 点光源
class PointLight;

#endif

然后修改主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "Global.h"
#include "Draw.h"
#include "Camera.h"

unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const std::string TEXTURE_PATH = "D:\\TechStack\\ComputerGraphics\\RenderEngine\\Assets\\Textures\\box.png";
const std::string OUT_PATH = "D:\\TechStack\\ComputerGraphics\\RenderEngine\\Results\\";
const std::string FILE_NAME = "camera.png";

int main()
{
//初始化相机
camera = new Camera(
glm::vec3(0.0f, 0.0f, 5.0f), // 相机位置
glm::vec3(0.0f, 1.0f, 0.0f), // 世界空间的up方向
glm::vec3(0.0f, 0.0f, 4.0f), // 相机lookat
60.0f, // 垂直视场
SCR_WIDTH, // 宽
SCR_HEIGHT, // 高
0.3f, // near
100 // far
);

// 初始化渲染器
dw = new Draw(SCR_WIDTH, SCR_HEIGHT, TEXTURE_PATH);
dw->Init();
dw->ClearBuffer(glm::vec4(0, 0, 0, 1));

// 设置观察矩阵
dw->setViewMatrix(camera->ViewMatrix());
// 设置投影矩阵
dw->setProjectMatrix(camera->PerspectiveMatrix());

// 创建一个立方体
Mesh Box = CreateBox(glm::vec3(0.0, 0.0, 0.0), 0.5);
// 让模型在原点绕着(1,1,0)旋转angle度
float angle = 45.0;
dw->setModelMatrix(glm::rotate(glm::mat4(1.0f), glm::radians(angle), glm::vec3(1.0, 1.0, 0.0)));
// 绘制,渲染管线流程
dw->DrawMesh(Box);

// 写出图片
std::string filepath = OUT_PATH + FILE_NAME;
dw->show(filepath);

return 0;
}

得到和之前一样的结果说明正确:

camera

---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。