|
此文章系列源自2020年2月闫令琪所教授的 GAMES101 现代计算机图形学入门课程。课程视频地址如下:
此系列主要包含个人在作业完成过程中遇到的各类问题、解决方案、后续思考,以及……吐槽……
<hr/>作业需求:get_model_matrix函数中,实现绕 z 轴旋转的变换矩阵
在main.cpp文件中的get_model_matrix函数中有一段“TODO”注释,在下面构建一个z轴旋转矩阵并与函数开头定义的model矩阵相乘,最后返回矩阵乘法结果。
绕z轴旋转的矩阵在原课程第4课的课件第8页:
唯一需要注意的点是,get_model_matrix入参rotation_angle是角度值,计算正弦和余弦前需要转为弧度。
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.
Matrix4f rotationZ;
float rad = rotation_angle / 180.0 * MY_PI;
rotationZ << cos(rad), -sin(rad), 0, 0,
sin(rad), cos(rad), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
model = rotationZ * model;
return model;
}作业需求:使用给定的参数逐个元素地构建透视投影矩阵
透视投影矩阵的构建分为两大步:1. 将视锥体变换为长方体;2. 将长方体变换为中心为三维空间原点且边长为2的立方体。
第一步的变换矩阵在第四课的课件29-36页,不过最后没把推导出的矩阵完整结果写出来。这里补充一下:
\begin{pmatrix} n & 0 & 0 & 0 \\ 0 & n & 0 & 0 \\ 0 & 0 & n+f & -nf \\ 0 & 0 & 1 & 0 \end{pmatrix}\\ 在函数get_projection_matrix的入参中,zNear和zFar分辨就是近平面和原平面在z轴方向的值,即n和f,可以直接使用:
float n = zNear, f = zFar;
Matrix4f perspToOrthoMatrix;
perspToOrthoMatrix << n, 0, 0, 0,
0, n, 0, 0,
0, 0, n+f, -n*f,
0, 0, 1, 0;第二部的变换矩阵在第四课的课件24页,由缩放和平移两个矩阵相乘获得:
没有旋转,因为之前view部分的变换已经处理好了。
// tan(eye_fov/2)=t/abs(n)
float t = tan(eye_fov / 2 / 180 * MY_PI) * (-n);
// aspect_ratio=r/t
float r = aspect_ratio * t;
Matrix4f orthoTransMatrix;
Matrix4f orthoScaleMatrix;
float l = -r, b = -t;
orthoTransMatrix << 1, 0, 0, -(r+l)/2,
0, 1, 0, -(t+b)/2,
0, 0, 1, -(n+f)/2,
0, 0, 0, 1;
orthoScaleMatrix << 2/(r-l), 0, 0, 0,
0, 2/(t-b), 0, 0,
0, 0, 2/(n-f), 0,
0, 0, 0, 1;
Matrix4f orthoProjMatrix = orthoScaleMatrix * orthoTransMatrix;这里有个坑,在t的值(即top,视锥体顶部平面的y值)计算时需要求反。原因在于作业框架中将视锥近平面和远平面的值设置为正数,并且远平面的值大于近平面的值,也可以理解为将视锥体放在了视点正z轴上。而课程推导过程中,近平面和远平面在视点负z轴上,近平面的值大于远平面。如果此处计算t时不加负号,最终结果是个大头朝下的三角形(其实也没什么大问题)。
最后,简化及合并一些变量:
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.
float n = zNear, f = zFar;
Matrix4f perspToOrthoMatrix;
perspToOrthoMatrix << n, 0, 0, 0,
0, n, 0, 0,
0, 0, n+f, -n*f,
0, 0, 1, 0;
float t = tan(eye_fov / 2 / 180.0 * MY_PI) * (-n);
float r = aspect_ratio * t;
Matrix4f orthoTransMatrix;
Matrix4f orthoScaleMatrix;
orthoTransMatrix << 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, -(n+f)/2,
0, 0, 0, 1;
orthoScaleMatrix << 1/r, 0, 0, 0,
0, 1/t, 0, 0,
0, 0, 2/(n-f), 0,
0, 0, 0, 1;
Matrix4f orthoProjMatrix = orthoScaleMatrix * orthoTransMatrix;
projection = orthoProjMatrix * perspToOrthoMatrix;
return projection;
}最后绘制结果如图:
绘制结果
作业需求:按 A 键与 D 键时,三角形正确旋转
由于作业框中已经编写了检测按键事件的相关代码,所以在正确完成上面两个需求后,不需要添加其他代码,就已经实现了该需求。不过具体实现的代码值得分析一下(忽略了一部分非关键代码):
float angle = 0;
int key = 0;
while (key != 27) {
/*...*/
key = cv::waitKey(10);
if (key == &#39;a&#39;) {
angle += 10;
}
else if (key == &#39;d&#39;) {
angle -= 10;
}
}while循环保证每10毫秒检测一次键盘按键,如果检测到ESC键被按下就退出循环关闭绘图窗口,如果检测到A或D键被按下就修改变量angle的值。
作业需求:构造函数,得到绕任意过原点的轴的旋转变换矩阵
变换矩阵在第四课课件的第10页,闫教授还手写了个推导过程作为补充材料,可惜我没看懂……
直接按照结果构造一个矩阵即可:
Matrix4f get_rotation_matrix(Vector3f axis, float angle)
{
Vector3f n = axis;
float rad = angle / 180.0 * MY_PI;
Matrix3f rMatrix;
Matrix3f identityMatrix = Matrix3f::Identity();
Matrix3f nMatrix;
nMatrix << 0, -n.z(), n.y(),
n.z(), 0, -n.x(),
-n.y(), n.x(), 0;
rMatrix = cos(rad) * identityMatrix + (1 - cos(rad)) * n * n.transpose() + sin(rad) * nMatrix;
Matrix4f rotationMatrix;
rotationMatrix << rMatrix(0, 0), rMatrix(0, 1), rMatrix(0, 2), 0,
rMatrix(1, 0), rMatrix(1, 1), rMatrix(1, 2), 0,
rMatrix(2, 0), rMatrix(2, 1), rMatrix(2, 2), 0,
0, 0, 0, 1;
return rotationMatrix;
}注意原公式最终结果是个3阶方阵,与作业需求不同。需要重新组织计算结果并构造成4阶方阵。
<hr/>吐槽:填空题式设计
仔细回看了一番作业框架main.cpp中几个计算转换矩阵的函数,发现:都在开头定义一个单位矩阵,接着根据数据推导构建实际的变换矩阵,最后把开头定义的单位矩阵与变换矩阵相乘,并返回最终结果。作业的主要内容是中间的变换矩阵。比如,原本就已经完成的get_view_matrix函数:
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;
}我们知道,单位矩阵与任意矩阵(合法)相乘,都不会改变后者。所以这步计算不影响中间构建的translate矩阵。从计算效率考虑,这样做多构造了一个临时矩阵且多执行了一次矩阵乘法运算,属于性能浪费。如果从返回值安全的角度考量,其实也只需要声明translate矩阵时就进行初始化即可,Eigen的矩阵可以直接往非空矩阵里塞元素。不过这只是个人的浅薄看法,不一定对……
另外,整个作业框架的设计不便调试。前面一大堆矩阵运算只要出一点岔子,体现在最终渲染结果上的问题往往千奇百怪。加上还有Eigen库的各种语法问题,VS在文本编辑模式不报错没波浪线,一编译就跳到Eigen内部去了……个人的做法是,注释掉整个原来的main函数,并新建main函数用于测试编写的函数是否符合预期。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|