作业 1
作业1是对变换章节所学知识的应用和练习。作业提供了一个基于 Eigen 和 OpenCV 库的光栅化程序框架。该框架定义了一个等腰三角形,需要我们在 main.cpp 中实现以下三个任务:
实现 get_model_matrix
函数中的旋转变换矩阵,让等腰三角形绕 Z 轴旋转;
实现 get_projection_matrix
函数中的投影变换矩阵,使等腰三角形正确投影到显示器上;
实现一个 get_rotation
函数, 使等腰三角形可以绕任意轴旋转;
1. 绕 Z 轴旋转
这个功能点直接使用绕 Z 轴的旋转矩阵即可。
需要注意的是,C++ 中三角函数输入值是弧度值,而 get_model_matrix
的形参输入是角度值,所以我们需要转换。 \[
1^\circ=\frac{\pi}{180}\mathbf{rad}
\] 代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Eigen::Matrix4f get_model_matrix (float rotation_angle) { Eigen::Matrix4f model = Eigen::Matrix4f::Identity (); rotation_angle *= MY_PI / 180 ; float c = cos (rotation_angle); float s = sin (rotation_angle); Rotate around Z-axis model << c, -s, 0 , 0 , s, c, 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 ; return model; }
2. 投影变换矩阵
get_projection_matrix
函数的形参是由 eye_fov(垂直视角)、aspect_ratio(纵横比)、dNear(近平面距离)、dFear(远平面距离)构成。所以需要利用视角、纵横比和 zNear 的关系才能得到近平面的 l、r、t、b 参数。具体公式推导见 笔记
这个部分代码如下:
1 2 3 4 eye_fov *= MY_PI / 180 ;float height = abs (zNear) * tan (eye_fov * 0.5f ) * 2 ;float width = height * aspect_ratio;
这里代码没有用 l、r、t、b 是因为 height = t - b,width = r - l,而作业 1 相机在做完视图变换后,位置已经在原点,所以 l+r = 0,t + b = 0。
所以正交投影矩阵可以简化为: \[
\mathbf M_{ortho}=
\begin{bmatrix}
\frac{2}{w}&0&0&0\\
0&\frac{2}{h}&0&0\\
0&0&\frac{2}{n-f}&\frac{n+f}{f-n}\\
0&0&0&1
\end{bmatrix}
\] 而透视截锥体到正交长方体的变换为 \[
\mathbf M_{frustum}=
\begin{bmatrix}
n&0&0&0\\
0&n&0&0\\
0&0&n+f&-nf\\
0&0&1&0
\end{bmatrix}
\] 最后代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Eigen::Matrix4f get_projection_matrix (float eye_fov, float aspect_ratio, float dNear, float dFar) { Eigen::Matrix4f projection = Eigen::Matrix4f::Identity (); float zNear = -dNear, zFar = -dFar; Eigen::Matrix4f frustum; frustum << zNear, 0 , 0 , 0 , 0 , zNear, 0 , 0 , 0 , 0 , zNear+zFar, -zNear * zFar, 0 , 0 , 1 , 0 ; eye_fov *= MY_PI / 180 ; float height = abs (zNear) * tan (eye_fov * 0.5f ) * 2 ; float width = height * aspect_ratio; Eigen::Matrix4f ortho; ortho << 2 / width, 0 , 0 ,0 , 0 , 2 / height, 0 , 0 , 0 , 0 , 2 / (zNear - zFar), (zNear + zFar) / (zFar-zNear), 0 , 0 , 0 , 1 ; projection = ortho * frustum; return projection; }
2.1 关于三角形上下颠倒的问题
这个问题是由于代码框架输入的 n,f(即 zNear 和 zFar)实际上是距离而不是我们推到矩阵时所用的坐标,所以我们需要为其加上负号。为了避免歧义,我已经将形参的 zNear 和 zFar 改为了 dNear 和 dFar。
1 2 3 4 5 6 7 Eigen::Matrix4f get_projection_matrix (float eye_fov, float aspect_ratio, float dNear, float dFar) { Eigen::Matrix4f projection = Eigen::Matrix4f::Identity (); float zNear = -dNear, zFar = -dFar;
3. 绕任意轴旋转
这个问题就要用到罗德里格旋转公式(Rodrigues’ rotation formula)。 \[
\mathbf R(\boldsymbol n,\alpha)=\cos(\alpha)\mathbf I+
(1-\cos\alpha)\boldsymbol{nn^{\mathbf T}}+
\sin\alpha
\underbrace{
\begin{bmatrix}
0&-n_z&n_y\\
n_z&0&-n_x\\
-n_y&n_x&0
\end{bmatrix}
}_{\mathbf N}
\] 需要注意的是该公式是基于 \(3\times 3\) 矩阵的,所以最后我们需要讲计算结果转换为齐次坐标的 \(4\times4\) 矩阵。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Eigen::Matrix4f get_rotation (Vector3f axis, float angle) { float c = cos (angle); float s = sin (angle); axis.normalize (); Eigen::Matrix3f n; n << 0 , -axis.z (), axis.y (), axis.z (), 0 , -axis.x (), -axis.y (), axis.x (), 0 ; Eigen::Matrix3f rot3f = c * Eigen::Matrix3f::Identity () + (1 - c) * axis * axis.transpose () + s * n; Eigen::Matrix4f rot4f = Eigen::Matrix4f::Identity (); rot4f.block <3 , 3 >(0 , 0 ) = rot3f; return rot4f; }
这里没有使用弧度转换是因为该函数是被 get_model_matrix
调用,而 get_model_matrix
已经转换过弧度了。
4. Bug:数组越界(Segmentfault 0xc0000005)
当绕某些轴旋转时(如 X 轴),会遇到数组越界的问题,导致程序崩溃。Debug 后发现问题出在 Rasterizer.cpp,set_pixel
函数中的判断语句。
1 2 3 4 5 6 7 8 void rst::rasterizer::set_pixel (const Eigen::Vector3f& point, const Eigen::Vector3f& color) { if (point.x () < 0 || point.x () >= width || point.y () < 0 || point.y () >= height) return ; auto ind = (height-point.y ())*width + point.x (); frame_buf[ind] = color; }
该函数的功能是判断像素点在不在投影范围内,如果在其中,就将改像素信息写入 frame_buf
中,否则跳过该像素。
frame_buf 是将二维像素信息用一维数组保存,大小为 700 x 700 即 490000。像素点坐标到数组序号的转换公式是
1 auto ind = (height-point.y ())*width + point.x ();
如果按照判断条件,当出现如点 \((664, 0)\) 时,改点的判断为 false
从而按照公式计算出序号 ind
= 490664,从而导致数组越界。
因此判断条件包含 y 为 0 的情况。改为,
1 2 if (point.x () < 0 || point.x () >= width || point.y () <= 0 || point.y () >= height) return ;
5. 最终效果
绕向量 \((3, 3, 0)^\mathbf T\) 旋转:
相关链接
笔记:计算机图形学笔记(1):变换
完整源码:Github