综述
今天做了GAMES101的作业1。
作业1的主要任务是
- 构建模型矩阵
- 构建透视投影矩阵
- 代码在已有框架下运行,能看到变换后的三角形
- (提高)构造一个函数,函数作用是得到绕任意过原点的轴旋转变换矩阵
代码框架见:计算机图形学与混合现实研讨会-games101往期作业汇总帖
做题前提
我认为做这道题,应该先看lecture4:变换(模型、视图、投影)以及lecture5:光栅化(三角形的离散化)。lecture5中只用看使用垂直可视角度(fovY)和长宽比(aspect ratio)来计算l,r,b,t。
解决思路
函数get_model_matrix
先看函数get_model_matrix
,在作业介绍的pdf中,这个函数的作用应该是实现三维中绕 z 轴旋转的变换矩阵,而不用处理平移与缩放。
如果是实现围绕z轴旋转,在lecture4中就已经提及了在三维空间中的变换:
这个函数需要返回的正是这个矩阵。而且传入的参数中,也传入了旋转角度rotation_angle
代码框架中已经建立了这个矩阵,并对它进行了初始化(Eigen::Matrix4f::Identity()即用单位矩阵对x变量进行了初始化)。
C++中计算cos和sin,括号里边应该给出的是弧度而不是角度,又知道弧度、角度的变换公式应该如下:
则代码只需要这么补充:
model << cosf((rotation_angle * MY_PI) / 180), -sinf((rotation_angle * MY_PI) / 180), 0, 0,
sinf((rotation_angle * MY_PI) / 180), cosf((rotation_angle * MY_PI) / 180), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
如果觉得这样太乱,先将radians以变量形式表现出来,再放入矩阵model中就好了。
则get_model_matrix整个函数代码如下:
// 实现三维中绕 z 轴旋转的变换矩阵,而不用处理平移与缩放。
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
// 参数rotation_angle为旋转角度
// Identity的作用:在定义变量时使用Eigen::Matrix4f x = Eigen::Matrix4f::Identity();即用单位矩阵对x变量进行了初始化。
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.
/*矩阵形式为:
[cos(angle),-sin(angle),0,0]
[sin(angle),cos(angle),0,0]
[0,0,1,0]
[0,0,0,1]*/
model << cosf((rotation_angle * MY_PI) / 180), -sinf((rotation_angle * MY_PI) / 180), 0, 0,
sinf((rotation_angle * MY_PI) / 180), cosf((rotation_angle * MY_PI) / 180), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
return model;
}
函数get_projection_matrix
这个函数的作用是使用给定的参数逐个元素地构建透视投影矩阵并返回该矩阵
而构建透视投影矩阵的方法是,我们需要将透视投影所呈现的大的背景压到与前边所看到的平面一样大(透视转正交),然后再进行正交投影。
构建透视投影矩阵必备知识
1、l,r,b,t的概念和计算方式
概念和计算方式如下:
如图所示,通过最下面的图右侧已经给出了对应的公式。而由定义又可以知道b=-t,l=-r
。
2、正交投影如何使用矩阵表示
只需要两步:先将图形的中心移动至原点,然后压缩到222的立方体中。
3、如何实现透视投影转正交投影?
闫令琪老师的视频里给出了详细的推理,我这里直接放用于实现透视投影转正交投影的矩阵:
以上,就是所有需要的知识。
对于本函数,先看给出的参数:float eye_fov, float aspect_ratio,float zNear, float zFar
其中,eve_fov(对应forY)、aspect_ratio(对应aspect)、zNear(对应n)给的这些参数是lecture5开头计算l,r,b,t所需的条件。
而zNear(对应n)和zFar(对应f)是进行透视投影转正交投影的条件。
使用代码计算l,r,b,t如下(同必备知识第1点,此处将公式转换为代码):
float t = tanf(eye_fov / 2) * abs(zNear);
float b = -t;
float r = aspect_ratio * t;
float l = -r;
构建正交投影矩阵(同必备知识第2点,此处将矩阵转换为代码):
Eigen::Matrix4f ortho = Eigen::Matrix4f::Identity();
Eigen::Matrix4f scale = Eigen::Matrix4f::Identity();
Eigen::Matrix4f transformation = Eigen::Matrix4f::Identity();
// scale矩阵,将原图形缩放为标准长度为2的立方体
scale << 2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (zNear - zFar), 0,
0, 0, 0, 1;
// 将立方体的中心移至源点
transformation << 1, 0, 0, -((r + l) / 2),
0, 1, 0, -((t + b) / 2),
0, 0, 1, -((zNear + zFar) / 2),
0, 0, 0, 1;
ortho = scale * transformation;
构建透视投影转正交投影矩阵(同必备知识第3点,此处将矩阵转换为代码):
Eigen::Matrix4f persp2ortho = Eigen::Matrix4f::Identity();
persp2ortho << zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear + zFar, -(zNear * zFar),
0, 0, 1, 0;
最后,只需要将ortho矩阵和persp2ortho相乘:
projection = ortho * persp2ortho;
总结起来,这个函数的完整代码如下:
// 使用给定的参数逐个元素地构建透视投影矩阵并返回该矩阵。
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 persp->ortho,透视投影转换为正交投影
Eigen::Matrix4f persp2ortho = Eigen::Matrix4f::Identity();
persp2ortho << zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear + zFar, -(zNear * zFar),
0, 0, 1, 0;
//M ortho
Eigen::Matrix4f ortho = Eigen::Matrix4f::Identity();
Eigen::Matrix4f scale = Eigen::Matrix4f::Identity();
Eigen::Matrix4f transformation = Eigen::Matrix4f::Identity();
// t means top, b means bottom,l means left,r means right.
float t = tanf(eye_fov / 2) * abs(zNear);
float b = -t;
float r = aspect_ratio * t;
float l = -r;
// scale矩阵,将原图形缩放为标准长度为2的立方体
scale << 2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (zNear - zFar), 0,
0, 0, 0, 1;
// 将立方体的中心移至源点
transformation << 1, 0, 0, -((r + l) / 2),
0, 1, 0, -((t + b) / 2),
0, 0, 1, -((zNear + zFar) / 2),
0, 0, 0, 1;
ortho = scale * transformation;
projection = ortho * persp2ortho;
return projection;
}
所需补充函数总代码
// 实现三维中绕 z 轴旋转的变换矩阵,而不用处理平移与缩放。
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
// 参数rotation_angle为旋转角度
// Identity的作用:在定义变量时使用Eigen::Matrix4f x = Eigen::Matrix4f::Identity();即用单位矩阵对x变量进行了初始化。
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.
/*矩阵形式为:
[cos(angle),-sin(angle),0,0]
[sin(angle),cos(angle),0,0]
[0,0,1,0]
[0,0,0,1]*/
model << cosf((rotation_angle * MY_PI) / 180), -sinf((rotation_angle * MY_PI) / 180), 0, 0,
sinf((rotation_angle * MY_PI) / 180), cosf((rotation_angle * MY_PI) / 180), 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.
//M persp->ortho,透视投影转换为正交投影
Eigen::Matrix4f persp2ortho = Eigen::Matrix4f::Identity();
persp2ortho << zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear + zFar, -(zNear * zFar),
0, 0, 1, 0;
//M ortho
Eigen::Matrix4f ortho = Eigen::Matrix4f::Identity();
Eigen::Matrix4f scale = Eigen::Matrix4f::Identity();
Eigen::Matrix4f transformation = Eigen::Matrix4f::Identity();
// t means top, b means bottom,l means left,r means right.
float t = tanf(eye_fov / 2) * abs(zNear);
float b = -t;
float r = aspect_ratio * t;
float l = -r;
// scale矩阵,将原图形缩放为标准长度为2的立方体
scale << 2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (zNear - zFar), 0,
0, 0, 0, 1;
// 将立方体的中心移至源点
transformation << 1, 0, 0, -((r + l) / 2),
0, 1, 0, -((t + b) / 2),
0, 0, 1, -((zNear + zFar) / 2),
0, 0, 0, 1;
ortho = scale * transformation;
projection = ortho * persp2ortho;
return projection;
}
运行效果
传不了视频和动图,大概通过图片感受一下。。
动图如下,但是看得不是很清楚
提高题
使用罗格里德斯函数来计算旋转矩阵,同时如果想要在原来的代码框架中实现,还要修改函数get_model_matrix,这里我先给出提高题指出函数的代码,对于一些细节尚有疑问(在x轴旋转到+50°角时会报错。。),之后解决了再来补充:
// 得到绕任意过原点的轴的旋转变换矩阵
Eigen::Matrix4f get_rotation(Vector3f axis, float angle) {
// 使用罗德里格斯公式(lecture4-3)
// R(n,α)=cos(α)*I+(1-cos(α))*n*n(T)+sin(α)*N
// 矩阵N
// [0,-n(z),n(y)]
// [n(z),0,-n(x)]
// [-n(y),n(x),0]
Eigen::Matrix4f rotation = Eigen::Matrix4f::Identity();
float radians = (angle * MY_PI) / 180;
Eigen::Matrix3f N, R;
N << 0, -axis[2], axis[1],
axis[2], 0, -axis[0],
-axis[1], axis[0], 0;
// 所需的单位矩阵
Eigen::Matrix3f I = Eigen::Matrix3f::Identity();
R = cosf(radians) * I + (1 - cosf(radians)) * N * N.transpose() + sinf(radians) * N;
rotation << R(0, 0), R(0, 1), R(0, 2), 0,
R(1, 0), R(1, 1), R(1, 2), 0,
R(2, 0), R(2, 1), R(2, 2), 0,
0, 0, 0, 1;
return rotation;
}