文章目录
前言
本文为GAMES101现代计算机图形学入门 的学习笔记系列。
我们的系列笔记将分为两部分:
- 课堂笔记
- 作业
原课程为2020年2月闫令琪所教授的 GAMES101 现代计算机图形学入门。
课程主页:https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html
(幻灯片和课程录像均在此处)
课程共计22节。作业共计8次。
针对人群:计算机图形学入门新手
教材:
Steve Marschner and Peter Shirley的"Fundamentals of Computer Graphics"
第三版或更新版本。目前无官方中文版。
民间翻译:https://www.stubbornhuang.com/1812/
2022-6-8
作业要求
本次作业的任务是填写一个旋转矩阵和一个透视投影矩阵。
给定三维下三个点 v0(2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2(−2.0, 0.0, −2.0), 你需要将这三个点的坐标变换为屏幕坐标并在屏幕上绘制出对应的线框三角形。
以下是你需要在 main.cpp 中修改的函数。
旋转矩阵:get_model_matrix(float rotation_angle): 逐个元素地构建模型变换矩阵并返回该矩阵。在此函数中,你只需要实现三维中绕 z 轴旋转的变换矩阵,而不用处理平移与缩放。
透视投影矩阵:get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar): 使用给定的参数逐个元素地构建透视投影矩阵并返回
该矩阵。
预期效果:当你在上述函数中正确地构建了模型与投影矩阵,光栅化器会创建一个窗口显示出线框三角形。由于光栅化器是逐帧渲染与绘制的,所以你可以使用 A 和D 键去将该三角形绕 z 轴旋转 (此处有一项提高作业,将三角形绕任意过原点的轴旋转)。当你按下 Esc 键时,窗口会关闭且程序终止。
评分:
- [5 分] 正确构建模型矩阵。
- [5 分] 正确构建透视投影矩阵。
- [10 分] 你的代码可以在现有框架下正确运行,并能看到变换后的三角形。
- [10 分] 当按 A 键与 D 键时,三角形能正确旋转。或者正确使用命令行得到旋转结果图像。
- [提高项 5 分] 在 main.cpp 中构造一个函数,该函数的作用是得到绕任意过原点的轴的旋转变换矩阵。Eigen::Matrix4f get_rotation(Vector3f axis, float angle)
额外说明:rasterizer.hpp
在本次作业中,因为你并不需要去使用三角形类,所以你需要理解与修改的文件为:rasterizer.hpp 和 main.cpp。其中 rasterizer.hpp 文件作用是生成渲染器界面与绘制。
光栅化器类在该程序系统中起着重要的作用,其成员变量与函数如下。
成员变量:
- Matrix4f model, view, projection: 三个变换矩阵。
- vector frame_buf: 帧缓冲对象,用于存储需要在屏幕上绘制的颜色数据。
成员函数:
- set_model(const Eigen::Matrix4f& m): 将内部的模型矩阵作为参数传递给光栅化器。
- set_view(const Eigen::Matrix4f& v): 将视图变换矩阵设为内部视图矩阵。
- set_projection(const Eigen::Matrix4f& p): 将内部的投影矩阵设为给定矩阵 p,并传递给光栅化器。
- set_pixel(Vector2f point, Vector3f color): 将屏幕像素点 (x, y) 设为 (r, g, b) 的颜色,并写入相应的帧缓冲区位置。
在 main.cpp 中,我们模拟了图形管线。我们首先定义了光栅化器类的实例,然后设置了其必要的变量。然后我们得到一个带有三个顶点的硬编码三角形 (请不要修改它)。在主函数上,我们定义了三个分别计算模型、视图和投影矩阵的函数,每一个函数都会返回相应的矩阵。接着,这三个函数的返回值会被 set_model(),set_view() 和 set_projection() 三个函数传入光栅化器中。最后,光栅化器在屏幕上显示出变换的结果。
在用模型、视图、投影矩阵对给定几何体进行变换后,我们得到三个顶点的正则化空间坐标 (canonical space coordinate)。正则化空间坐标是由三个取值范围在 [-1,1] 之间的 x, y, z 坐标构成。我们下一步需要做的就是视口变换,将坐标映射到我们的屏幕中 (window_width * window_height),这些在光栅化器中都已完成,所以不需要担心。但是,你需要去理解这步操作是如何运作的,这一点十分重要。
如果使用自己的系统,本次程序作业中使用到的库为 Eigen 与 OpenCV,请确保这两个库的配置正确。
解答
这节作业很简单,就写两个矩阵:
- 旋转矩阵
- 投影变换矩阵
所对应的内容为第四节课
课堂笔记4–变换(模型、视图、投影)
还有第五节课的前面一小部分(因为第四节没讲完)
课堂笔记5–光栅化(三角形的离散化)
唯一需要注意的是:函数的参数中所提供的角为角度值,而Eigen默认使用弧度制,所以需要先转化为弧度制
1 旋转矩阵
绕z轴的旋转矩阵给出如下:
填入到给定的函数里即可
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
// TODO: Implement this function
// Create the model matrix for rotating the triangle around the Z axis.
// Then return it.
float a = rotation_angle / 180.0 * MY_PI;;
model << cos(a), -sin(a), 0, 0,
sin(a), cos(a), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
return model;
}
2 投影变换矩阵
透视投影需要两步走:
- 将透视投影转化为正交投影
- 将正交投影转化到正则立方体内(canonical cube)
2.1 将透视投影转化为正交投影
也就是所谓的将一个棱锥挤压乘一个立方体
对应矩阵为
// 1. 透视投影转为正交投影矩阵
float n = zNear;
float f = zFar;
Eigen::Matrix4f M_persp2Ortho;
M_persp2Ortho << n, 0, 0, 0,
0, n, 0, 0,
0, 0, n + f, -n * f,
0, 0, 1, 0;
2.2 将正交投影转化到正则立方体
这里又分为两小步:
- 平移到原点
- 缩放
即上图的两个矩阵相乘。记住:总是从右往左乘。
Eigen::Matrix4f M_ortho, trans, scale;
trans << 1, 0, 0, -(r+l)/2,
0, 1, 0, -(t+b)/2,
0, 0, 1, -(n+f)/2,
0, 0, 0, 1;
scale << 2/(r-l), 0, 0, 0,
0, 2/(t-b), 0, 0,
0, 0, 2/(n-f), 0,
0, 0, 0, 1;
M_ortho = scale * trans;
这里我们会发现,没有r, t, b, l这些值。我们有的只是fov和aspect_ratio,所以要转化一下
2.2.1 将宽高比与视锥角转换为上下左右大小
所谓的r, t, b, l 就是 right, top, bottom, left
也就是
这里参考第五节课的前面一点
课堂笔记5–光栅化(三角形的离散化)
于是就找到了对应关系。
float fov = eye_fov * MY_PI / 180.0;
float t = -n * tan(fov / 2.);
float b = -t;
float r = aspect_ratio * t;
float l = -r;
这里我们要注意的是:
- 我们给出了上和右的算法,其实左和下只需要取个负号就行。(因为我们已经把相机放到了标准位置上,所以一定是对称的)
- 由于我们的相机沿着-z轴的方向(右手系)看去,所以n也要取个负号或者绝对值。
2.3 透视投影的全部代码
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.
// 1. 透视投影转为正交投影矩阵
float n = zNear;
float f = zFar;
Eigen::Matrix4f M_persp2Ortho;
M_persp2Ortho << n, 0, 0, 0,
0, n, 0, 0,
0, 0, n + f, -n * f,
0, 0, 1, 0;
// 2. 正交投影转换到正则立方体
float fov = eye_fov * MY_PI / 180.0;
float t = -n * tan(fov / 2.);
float b = -t;
float r = aspect_ratio * t;
float l = -r;
Eigen::Matrix4f M_ortho, trans, scale;
trans << 1, 0, 0, -(r+l)/2,
0, 1, 0, -(t+b)/2,
0, 0, 1, -(n+f)/2,
0, 0, 0, 1;
scale << 2/(r-l), 0, 0, 0,
0, 2/(t-b), 0, 0,
0, 0, 2/(n-f), 0,
0, 0, 0, 1;
M_ortho = scale * trans;
projection = M_ortho * M_persp2Ortho ;
return projection;
}
结果
按键盘D键可以向右旋转,按A键可以向左旋转