小白完成GAMES101作业1花费一周多,主要原因在于完成此作业需配置opencv库,较为繁琐。而后发现windows环境不能顺利完成任务,故用easyx.h图形库做了一个轻量的光栅化器替代原本的光栅化器模板。该光栅化器不涉及深度缓冲,但本次作业并不需要所以足够支撑我完成作业。以下是作业复盘。(作业1使用右手系,旋转默认逆时针;复盘内容仅涉及代码问题,不含括数学)
- 必要的前置函数
目前包括一个角度转弧度的函数,因为<cmath>库提供的三角函数只支持弧度制。此前并未注意该问题,遂导致出错。
const float M_PI = 3.1415926;
float ToRadian(float angle) {
return M_PI * (angle / 180.0f);
}
- Model变换矩阵
传入一个浮点型参数angle,该矩阵实现绕Z-axis旋转angle度。
Eigen::Matrix4f get_model_matrix(float angle) {
angle = ToRadian(angle);
Eigen::Matrix4f ret = Eigen::Matrix4f::Identity();
ret(0, 0) = cos(angle);
ret(0, 1) = -sin(angle);
ret(1, 0) = sin(angle);
ret(1, 1) = cos(angle);
return ret;
}
- View变换矩阵
在main.cpp中,相机向量的定义如下:
Eigen::Vector3f eye_fov(0.0f, 0.0f, 5.0f);//相机在(0,0,5),view矩阵用于相对成像
这代表相机位于(0,0,5),所以View变换的目的是将相机移动至原点,方便后续计算。即View变换矩阵本质上是一个平移变换矩阵。实现代码如下:
Eigen::Matrix4f get_view_matrix(Eigen::Vector3f vec) {
Eigen::Matrix4f ret;
ret << 1.f, 0.f, 0.f, -vec.x(),
0.f, 1.f, 0.f, -vec.y(),
0.f, 0.f, 1.f, -vec.z(),
0.f, 0.f, 0.f, 1.f;
return ret;
}
- Projection变换矩阵
投影变换分为透视投影到正交投影的变换与正交投影变换两步。透视投影到正交投影的变换矩阵实现如下:
persp << zNear, 0.f, 0.f, 0.f,
0.f, zNear, 0.f, 0.f,
0.f, 0.f, zNear + zFar, -zNear * zFar,
0.f, 0.f, 1.f, 0.f;
正交变换矩阵略为复杂,需要根据 eye_fov, aspect_ratio, zNear, zFar 得到变换长方体的宽、高以及纵深。代码实现如下:
float w, h, z;
w = abs(zNear) * tan(ToRadian(fovY / 2)) * 2;
h = w / aspect_ratio;
z = zFar - zNear;
ortho << 2 / w, 0.f, 0.f, 0.f,
0.f, 2 / h, 0.f, 0.f,
0.f, 0.f, 2 / z, -(zNear + zFar) / 2,
0.f, 0.f, 0.f, 1.f;
在求完两个关键矩阵后,Projection变换矩阵就等于两个矩阵乘起来。注意矩阵乘法的顺序,写代码的时候写反了调试了几个小时才改过来。
Eigen::Matrix4f get_projection_matrix(float fovY, float aspect_ratio, float zNear, float zFar) {
Eigen::Matrix4f ret = Eigen::Matrix4f::Identity(), ortho, persp;
persp << zNear, 0.f, 0.f, 0.f,
0.f, zNear, 0.f, 0.f,
0.f, 0.f, zNear + zFar, -zNear * zFar,
0.f, 0.f, 1.f, 0.f;
float w, h, z;
w = abs(zNear) * tan(ToRadian(fovY / 2)) * 2;
h = w / aspect_ratio;
z = zFar - zNear;
ortho << 2 / w, 0.f, 0.f, 0.f,
0.f, 2 / h, 0.f, 0.f,
0.f, 0.f, 2 / z, -(zNear + zFar) / 2,
0.f, 0.f, 0.f, 1.f;
ret = ortho * persp * ret;
return ret;
}
- 提高:绕任意轴旋转
数学工具需要用到Rodrigues's Rotation Formula,这里不进行数学证明,仅放出公式:
照着公式完成函数,代码如下:
Eigen::Matrix4f get_rodrigues_matrix(Eigen::Vector3f aixs, float angle) {
Eigen::Matrix4f I = Eigen::Matrix4f::Identity(), ret, N;
N << 0.f, -aixs.z(), aixs.y(), 0.f,
aixs.z(), 0.f, -aixs.x(), 0.f,
-aixs.y(), aixs.x(), 0.f, 0.f,
0.f, 0.f, 0.f, 1.f;
Eigen::Vector4f aff_;
aff_ << aixs(0), aixs(1), aixs(2), 0.f;//向量齐次化第四维为0
angle = ToRadian(angle);//转弧度
ret = cos(angle) * I + (1 - cos(angle)) * aff_ * aff_.transpose() + sin(angle) * N;
ret(3, 3) = 1;//齐次化
return ret;
}
然后在光栅器类中增加接口,并把该矩阵乘在 Model 之前即可,因为该变换属于模型变换。而main函数中增加几行代码实现输入即可:
for (int i = 1; i<=10;i++) {
r.darw(Triangle(v0, v1, v2));
std::string input{ "" };
std::getline(std::cin, input, ':');
Eigen::Vector3f aixs;
if (input == "Rod:") {
for(int j=0;j<=2;j++)
std::cin >> aixs(j);
std::cin >> angle;
r.set_rodrigues(get_rodrigues_matrix(aixs, angle));
}
else {
std::cin >> angle;
r.set_model(get_model_matrix(angle));
}
}
- 运行效果
(注:这是伪光栅化器运行效果,与GAMES101作业1中提供的光栅化器略有差别。)