GAMES101-作业1

这一系列博客主要是为了记录自己对GAMES101课程的作业完成情况.

作业1: 旋转与投影

作业描述

本次作业的任务是填写一个旋转矩阵和一个透视投影矩阵。

给定三维下三个点 v0(2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2(−2.0, 0.0, −2.0),

你需要将这三个点的坐标变换为屏幕坐标并在屏幕上绘制出对应的线框三角形.

需要修改的函数:

get_model_matrix(float rotation_angle): 逐个元素地构建模型变换矩阵并返回该矩阵。在此函数中,你只需要实现三维中绕 z 轴旋转的变换矩阵,而不用处理平移与缩放。

get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar): 使用给定的参数逐个元素地构建透视投影矩阵并返回该矩阵。

• [Optional] main(): 自行补充你所需的其他操作。


框架代码解读

1. main.cpp

1.1 初始化流程:

rst::rasterizer r(700, 700): 声明一个栅格(600 x 600)的栅格(这个栅格可以理解为我们看到的屏幕)

Eigen::Vector3f eye_pos = {0, 0, 5}: 设置摄像机的位置(可以简单理解为我们从空间中的哪个位置看这个空间, )

接下来声明了一个三角形. 注: 一般三角形由三个点和这三个点所对应的序列分别指出

1.2 对每一帧的操作:

r.clear(rst::Buffers::Color | rst::Buffers::Depth);` // 清空当前帧的缓存

设置当前帧的: 仿射变换矩阵(旋转变换矩阵 平移矩阵)View旋转矩阵透视投影矩阵

1
2
3
r.set_model(get_rotation(axis, angle));
r.set_view(get_view_matrix(eye_pos));
r.set_projection(get_projection_matrix(45, 1, 0.1, 50));

r.draw(pos_id, ind_id, rst::Primitive::Triangle); // 画出该帧. 该函数是该框架中最重要的部分, 后面专门介绍

1.3 调用Opencv画出栅格

下面粘贴main.cpp代码

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
int main(int argc, const char** argv)
{
float angle = 20;
Vector3f axis(0.0, 1.0, 0.0);

bool command_line = false;
std::string filename = "output.png";

rst::rasterizer r(700, 700);

Eigen::Vector3f eye_pos = {0, 0, 5};

std::vector<Eigen::Vector3f> pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}};

std::vector<Eigen::Vector3i> ind{{0, 1, 2}};

auto pos_id = r.load_positions(pos);
auto ind_id = r.load_indices(ind);

int key = 0;
int frame_count = 0;

while (key != 27) {
r.clear(rst::Buffers::Color | rst::Buffers::Depth);

r.set_model(get_rotation(axis, angle));
r.set_view(get_view_matrix(eye_pos));
r.set_projection(get_projection_matrix(45, 1, 0.1, 50));

r.draw(pos_id, ind_id, rst::Primitive::Triangle);

cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
image.convertTo(image, CV_8UC3, 1.0f);
cv::imshow("image", image);
key = cv::waitKey(10);

std::cout << "frame count: " << frame_count++ << '\n';

if (key == 'a') {
angle += 10;
}
else if (key == 'd') {
angle -= 10;
}
}

return 0;
}

2. rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive type)

我个人认为该函数比较重要的是其主要涉及了四个变换:

Eigen::Matrix4f mvp = projection * view * model;

这句主要包含了三个变换, 其中就包含我们要写的两个变换. 简单来说就是:

通过 model 矩阵, 来实现我们需要的物体的相应变换, 比如让该物体旋转或平移或缩放

通过 view 矩阵, 来将我们的视线给移动到世界坐标系处

通过 projection 矩阵, 来实现透视投影的过程, 并将整个空间给缩放到[-1 1]内的一个小cube

  1. model: model Trans, 对应我们作业0写的旋转和平移变换, 绕某轴旋转的公式,可以参考PPT.

    讲课过程中也提到了绕任意轴旋转的问题, 这也是提高题需要解决的问题.

  2. View Transform: 旋转原理是同上的

  3. Projection: 透视投影矩阵: 透视投影矩阵主要包含两部分: 先将透视投影过程转换为正交投影的过程(M_persp->ortho) 以及将正交投影给转化为[-1 1]的小cube(M_otrho)

此外, 该过程还涉及一个变换:

该变换主要实现将[-1 1]的小cube给映射到我们的屏幕中去, 也就是我们的有一定长宽的栅格上.

此代码中并未用矩阵形式表述该变换

1
2
3
vert.x() = 0.5*width*(vert.x()+1.0);
vert.y() = 0.5*height*(vert.y()+1.0);
vert.z() = vert.z() * f1 + f2; // 这一步没太看懂, 为什么要这样转换z轴.

进行变换后, 调用相应函数, 画出对应的三角形.

该函数整体代码展示如下

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
void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive type)
{
if (type != rst::Primitive::Triangle)
{
throw std::runtime_error("Drawing primitives other than triangle is not implemented yet!");
}
auto& buf = pos_buf[pos_buffer.pos_id];
auto& ind = ind_buf[ind_buffer.ind_id];

float f1 = (100 - 0.1) / 2.0;
float f2 = (100 + 0.1) / 2.0;

Eigen::Matrix4f mvp = projection * view * model;
for (auto& i : ind)
{
Triangle t;

Eigen::Vector4f v[] = {
mvp * to_vec4(buf[i[0]], 1.0f),
mvp * to_vec4(buf[i[1]], 1.0f),
mvp * to_vec4(buf[i[2]], 1.0f)
};

for (auto& vec : v) {
vec /= vec.w();
}

for (auto & vert : v)
{
vert.x() = 0.5*width*(vert.x()+1.0);
vert.y() = 0.5*height*(vert.y()+1.0);
vert.z() = vert.z() * f1 + f2; // 这一步没太看懂, 为什么要这样转换z轴.
}

for (int i = 0; i < 3; ++i)
{
t.setVertex(i, v[i].head<3>());
t.setVertex(i, v[i].head<3>());
t.setVertex(i, v[i].head<3>());
}

t.setColor(0, 255.0, 0.0, 0.0);
t.setColor(1, 0.0 ,255.0, 0.0);
t.setColor(2, 0.0 , 0.0,255.0);

rasterize_wireframe(t);
}
}

题解

1 Eigen::Matrix4f get_model_matrix(float rotation_angle)

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
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
// Identity_Matrix: 单位矩阵

// TODO: Implement this function
// Create the model matrix for rotating the triangle around the Z axis.
// Then return it.
// 逐元素地构建绕z轴旋转的模型变换矩阵并返回该矩阵
// angle 默认是角度制, 因此需要将其转换为弧度制
float rotation_angle_rad = rotation_angle / 180.0 * acos(-1);
/**
* Rz(alpha) =
* |cos(alpha), -sin(alpha), 0, 0|
* |sin(alpha), cos(alpha), 0, 0|
* |0 , 0 , 1, 0|
* |0 , 0 , 0, 1|
*/
float cos_alpha = cos(rotation_angle_rad);
float sin_alpha = sin(rotation_angle_rad);
model(0, 0) = cos_alpha;
model(1, 1) = cos_alpha;
model(0, 1) = -1 * sin_alpha;
model(1, 0) = sin_alpha;
return model;
}

2 Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)

这里比较欠的是我给两个矩阵给手动乘在一起了, 故可能和课上讲的有些不同

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
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
float zNear, float zFar)
{
// Students will implement this function

Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();

// TODO: Implement this function
// Create the projection matrix for the given parameters.
// Then return it.
// 使用给定的参数逐个元素的构建透视投影矩阵并返回该矩阵.

// M_perse = M_ortho * M_persp->ortho
/**
* M_perse =
* | 2 / (r - l) * n
* | 0 , 2 / (t - b) * n,
* | , , (n + f) / (n - f) , -2 * n * f / (n - f)|
* | , , n , 0 |
*/
eye_fov = eye_fov / 180.0 * acos(-1);
float yTop = zNear * tan(eye_fov / 2);
float xRight = yTop * aspect_ratio;
projection(0, 0) = zNear / xRight;
projection(1, 1) = zNear / yTop;
projection(2, 2) = (zNear + zFar) / (zNear - zFar);
projection(2, 3) = (-2.0 * zNear * zFar) / (zNear - zFar);
projection(3, 2) = 1;
projection(3, 3) = 0;

Eigen::Matrix4f inverseXY = Eigen::Matrix4f::Identity();
inverseXY(3, 3) = -1;
projection = inverseXY * projection;

return projection;
}

提高题: Eigen::Matrix4f get_rotation(Vector3f axis, float angle)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief 得到绕任意过原点的轴的旋转变换矩阵
* @param axis 所绕的轴
* @param angle 所需要绕的角度
* @return 旋转变换矩阵
*/
Eigen::Matrix4f get_rotation(Vector3f axis, float angle){
angle = angle / 180 * MY_PI;
Eigen::Matrix3f crossProduct;
crossProduct << 0 , -1 * axis(2) , axis(1) ,
axis(2) , 0 , -1 * axis(0),
-1 * axis(1), axis(0) , 0 ;

Eigen::Matrix3f temp = (cos(angle) * Eigen::Matrix3f::Identity() + (1 - cos(angle)) * axis * axis.transpose() + sin(angle)*crossProduct);
Eigen::Matrix4f model;
model << temp(0, 0), temp(0, 1), temp(0, 2), 0,
temp(1, 0), temp(1, 1), temp(1, 2), 0,
temp(2, 0), temp(2, 1), temp(2, 2), 0,
0, 0, 0, 1;
return model;
}