games101 HomeWork 1
说起来我自己写games101的作业也是曲曲折折,虚拟机很卡就拿VS配环境,Windows不会配环境,就装Linux,现在装上了Linux,却因为没有经验把Windows格式化了(我是真的沙比),好在还是开始做了,也挺顺利的,所以再来记录一下作业。
基础部分
这里需要完成两个函数,一个是模型变换矩阵,一个是透视投影矩阵。
模型变换矩阵
逐个元素地构建模型变换矩阵并返回该矩阵。在此函数中,你只需要实现三维中绕 z 轴旋转的变换矩阵,而不用处理平移与缩放。
这个部分的实现非常简单,只需要记住这个公式就好了。这里给出绕三个轴旋转的旋转矩阵:
代码实现:
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
//角度制转弧度制
rotation_angle = rotation_angle / 180.0f * MY_PI;
// TODO: Implement this function
// Create the model matrix for rotating the triangle around the Z axis.
// Then return it.
model << cos(rotation_angle), -sin(rotation_angle), 0, 0,
sin(rotation_angle), cos(rotation_angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
return model;
}
透视投影矩阵
使用给定的参数逐个元素地构建透视投影矩阵并返回
该矩阵。
这个题目的参数定义其实不是很明显(至少位蒙b了很久),先看一下函数原型:
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
来解读一下前两个参数,eye_fov
指的是摄像机的垂直可视角度,aspect_ratio
指的是摄像机的长宽比。使用这四个值也能算出正交投影矩阵。(下图只供参考)
我们从透视投影矩阵开始:
根据课堂上的推导,我们已经知道透视投影矩阵的最终结果,并且所需要的值只有zNear
和zFar
,于是我们能直接写出这个矩阵:
P << zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear + zFar, -zNear * zFar,
0, 0, 1, 0;
然后是我们的正交投影部分了,正交投影需要用到的数据有六个,分别是长方体的参数。
$$[l,r]\times[b,t]\times[f,n]$$
先把正的数据处理掉,直接给出答案,再证明
- \(t=zNear*tan(eye\_fov/2)\)(弧度化后)
- \(r=t*aspect\_ratio\)
另外的l
和b
分别等于r
和t
的相反数。
最后还剩下n
和f
,这两个数和他们的名字差别很大,分别是后和前,赋值的时候需要小心,而且他们的值并不是zNear和zFar分别赋值,而是相反数赋值。如下
f = -zNear;
n = -zFar;
有了数据,我们就可进行投影矩阵的实现了,正交投影矩阵如下:
eye_fov = eye_fov / 180 * MY_PI;
float l, r, b, t, n, f;
//注意这里的f和n代表的意义
f = -zNear;
n = -zFar;
t = -zNear * tan(eye_fov/2);
b = -t;
r = t * aspect_ratio;
l = -r;
S << 2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (n - f), 0,
0, 0, 0, 1;
M << 1, 0, 0, -(r + l) / 2,
0, 1, 0, -(t + b) / 2,
0, 0, 1, -(n + f) / 2,
0, 0, 0, 1;
//S*M得到正交投影矩阵
普通要求代码汇总
//main.cpp
bool ProjectMode=true;//这是一个控制模式的参数
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
rotation_angle = rotation_angle / 180.0f * MY_PI;
// TODO: Implement this function
// Create the model matrix for rotating the triangle around the Z axis.
// Then return it.
model << cos(rotation_angle), -sin(rotation_angle), 0, 0,
sin(rotation_angle), cos(rotation_angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
return model;
}
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.
if (!ProjectMode)
{
Eigen::Matrix4f S, M, P;
eye_fov = eye_fov / 180 * MY_PI;
float l, r, b, t, n, f;
//注意这里的f和n代表的意义
f = -zNear;
n = -zFar;
t = -zNear * tan(eye_fov/2);
b = -t;
r = t * aspect_ratio;
l = -r;
S << 2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (n - f), 0,
0, 0, 0, 1;
M << 1, 0, 0, -(r + l) / 2,
0, 1, 0, -(t + b) / 2,
0, 0, 1, -(n + f) / 2,
0, 0, 0, 1;
P << zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear + zFar, -zNear * zFar,
0, 0, 1, 0;
return S * M * P;
}
else//这是精简版本
{
eye_fov = eye_fov / 180 * MY_PI;
projection << 1 / (aspect_ratio * tan(eye_fov / 2.0f)), 0, 0, 0,
0, 1 / tan(eye_fov / 2.0f), 0, 0,
0, 0, -(zFar + zNear) / (zFar - zNear), 2 * zFar * zNear / (zNear - zFar),
0, 0, -1, 0;
return projection;
}
}
提升部分
提升部分要求:在 main.cpp 中构造一个函数,该函数的作用是得到绕任意
过原点的轴的旋转变换矩阵。根据101中的推导,我们需要计算的东西并不多,按要求写好就行了。我选择重载get_model_matrix
函数,来实现任意旋转轴的旋转操作。
Eigen::Matrix4f get_rotation(Vector3f axis, float rotation_angle)
{
rotation_angle = rotation_angle / 180.0f * MY_PI;
Eigen::Matrix4f Result = Eigen::Matrix4f::Identity();
Eigen::Matrix3f I = Eigen::Matrix3f::Identity();//单位矩阵
Eigen::Matrix3f N = Eigen::Matrix3f::Identity();
Eigen::Matrix3f ResultMat3 = Eigen::Matrix3f::Identity();
N << 0, -axis[2], axis[1],
axis[2], 0, -axis[0],
-axis[1], axis[0], 0;
ResultMat3 = I * cos(rotation_angle) + (1 - cos(rotation_angle)) * axis * axis.transpose() + sin(rotation_angle) * N;
Result << ResultMat3(0, 0), ResultMat3(0, 1), ResultMat3(0, 2), 0,
ResultMat3(1, 0), ResultMat3(1, 1), ResultMat3(1, 2), 0,
ResultMat3(2, 0), ResultMat3(2, 1), ResultMat3(2, 2), 0,
0, 0, 0, 1;
return Result;
}
其余代码和普通要求一致。得到结果如下:
结果
因为是第一次作业,所以我这里给出编译的操作:
先来到作业目录
创建build文件夹
来到build文件夹
使用上级目录创建项目文件
构建编译,这里的-j8
是表示调用的核心数量,最后一句target Rasterizer
表示可执行文件为Rasterizer
运行
按下Esc或者Ctrl+C停止
全部指令汇总
mkdir build
cd build
cmake ..
make -j8
./Rasterizer
普通要求
运行指令与结果
./Rasterizer
不带参数运行
./Rasterizer -r 0 output.png
无旋转保存图片
./Rasterizer -r 90 output90.png
旋转保存图片
提升要求
先介绍一下main函数的两个参数argc
和argv
argc
全称arugment count,表示调用程序的时候,传入的参数的个数。argv
全称argument vector,表示调用程序的时候,传入的参数向量,类型为字符串
默认的,argv[0]
是调用程序的完整路径,然后argv[0]-argv[argc-1]
都是可以访问的字符串。在程序中,我们可以使用标准库定义的stof
函数,把字符串转成浮点型,或者使用stod
把字符串转化成整形。知道这一点之后,我们就可以设计一个main
函数来把我们自定义的旋转轴作为参数传入程序了。
这里是我自己设计的一个传参方案,有兴趣的可以读一下程序。
main.cpp
#include "Triangle.hpp"
#include "rasterizer.hpp"
#include <eigen3/Eigen/Eigen>
#include <iostream>
#include <opencv2/opencv.hpp>
constexpr double MY_PI = 3.1415926;
bool ProjectMode=true;
Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos)
{
Eigen::Matrix4f view = Eigen::Matrix4f::Identity();
Eigen::Matrix4f translate;
translate << 1, 0, 0, -eye_pos[0], 0, 1, 0, -eye_pos[1], 0, 0, 1,
-eye_pos[2], 0, 0, 0, 1;
view = translate * view;
return view;
}
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
rotation_angle = rotation_angle / 180.0f * MY_PI;
// TODO: Implement this function
// Create the model matrix for rotating the triangle around the Z axis.
// Then return it.
model << cos(rotation_angle), -sin(rotation_angle), 0, 0,
sin(rotation_angle), cos(rotation_angle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
return model;
}
// Rodrigues rotation formula
Eigen::Matrix4f get_rotation(Vector3f axis, float angle)
{
angle = angle / 180.0f * MY_PI;
Eigen::Matrix4f Result = Eigen::Matrix4f::Identity();
Eigen::Matrix3f E = Eigen::Matrix3f::Identity();
Eigen::Matrix3f N = Eigen::Matrix3f::Identity();
Eigen::Matrix3f ResultMat3 = Eigen::Matrix3f::Identity();
N << 0, -axis[2], axis[1],
axis[2], 0, -axis[0],
-axis[1], axis[0], 0;
ResultMat3 = E * cos(angle) + (1 - cos(angle)) * axis * axis.transpose() + sin(angle) * N;
Result << ResultMat3(0, 0), ResultMat3(0, 1), ResultMat3(0, 2), 0,
ResultMat3(1, 0), ResultMat3(1, 1), ResultMat3(1, 2), 0,
ResultMat3(2, 0), ResultMat3(2, 1), ResultMat3(2, 2), 0,
0, 0, 0, 1;
return Result;
}
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.
if (!ProjectMode)
{
Eigen::Matrix4f S, M, P;
eye_fov = eye_fov / 180 * MY_PI;
float l, r, b, t, n, f;
//注意这里的f和n代表的意义
f = -zNear;
n = -zFar;
t = -zNear * tan(eye_fov/2);
b = -t;
r = t * aspect_ratio;
l = -r;
S << 2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (n - f), 0,
0, 0, 0, 1;
M << 1, 0, 0, -(r + l) / 2,
0, 1, 0, -(t + b) / 2,
0, 0, 1, -(n + f) / 2,
0, 0, 0, 1;
P << zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear + zFar, -zNear * zFar,
0, 0, 1, 0;
return S * M * P;
}
else
{
std::cout<<"true answer"<<std::endl;
eye_fov = eye_fov / 180 * MY_PI;
projection << 1 / (aspect_ratio * tan(eye_fov / 2.0f)), 0, 0, 0,
0, 1 / tan(eye_fov / 2.0f), 0, 0,
0, 0, -(zFar + zNear) / (zFar - zNear), 2 * zFar * zNear / (zNear - zFar),
0, 0, -1, 0;
return projection;
}
}
int main(int argc, const char **argv)
{
float angle = 0;
bool command_line = false;
Eigen::Vector3f axis = Eigen::Vector3f(0.f, 1.f, 0.f);
std::string filename = "output.png";
if (argc >= 3)
{
std::cout<<argc<<std::endl;
angle = std::stof(argv[2]); // -r by default
if (argc == 4)
{
command_line = true;
filename = std::string(argv[3]);
}
else if(argc==6){//DIY(
axis.x()=std::stof(argv[3]);
axis.y()=std::stof(argv[4]);
axis.z()=std::stof(argv[5]);
axis.normalize();
}
//
}
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;
if (command_line)
{
r.clear(rst::Buffers::Color | rst::Buffers::Depth);
r.set_model(get_model_matrix(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::imwrite(filename, image);
return 0;
}
while (key != 27)
{
r.clear(rst::Buffers::Color | rst::Buffers::Depth);
r.set_model(get_model_matrix(angle));
//DIY
if (argc == 6){
r.set_model(get_rotation(axis, angle));
std::cout<<"axis x:"<<axis.x()<<" y:"<<axis.y()<<" z:"<<axis.z()<<std::endl<<"angle:"<<angle<<std::endl;
}
//
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;
}
部分效果如下:
games101 Hw1 到此就结束啦!