任务清单
复用作业1中的投影矩阵
实现三角形栅格化算法
正确测试点是否在三角形内
(提高作业)使用 MSAA 抗锯齿
任务详解
任务1
求包围盒, 枚举包围盒内像素点, 判断是否在三角形内,
再根据插值的结果判断是否需要更新深度缓冲及帧缓冲.
代码如下:
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 31 32 33 34 35 36 37 38 39 40 41 void rst::rasterizer::rasterize_triangle (const Triangle& t) { auto v = t.toVector4 (); auto get_z_interpolated = [t, v](float x, float y) -> float { auto [alpha, beta, gamma] = computeBarycentric2D (x, y, t.v); float w_reciprocal = 1.0 / (alpha / v[0 ].w () + beta / v[1 ].w () + gamma / v[2 ].w ()); float z_interpolated = alpha * v[0 ].z () / v[0 ].w () + beta * v[1 ].z () / v[1 ].w () + gamma * v[2 ].z () / v[2 ].w (); z_interpolated *= w_reciprocal; return z_interpolated; }; int min_x = std::min ({ v[0 ].x (), v[1 ].x (), v[2 ].x () }) - 1 ; min_x = std::max (0 , min_x); int max_x = std::max ({ v[0 ].x (), v[1 ].x (), v[2 ].x () }) + 1 ; max_x = std::min (width - 1 , max_x); int min_y = std::min ({ v[0 ].y (), v[1 ].y (), v[2 ].y () }) - 1 ; min_y = std::max (0 , min_y); int max_y = std::max ({ v[0 ].y (), v[1 ].y (), v[2 ].y () }) + 1 ; max_y = std::min (height - 1 , max_y); Vector3f color = t.getColor (); for (int x = min_x; x <= max_x; x++) { for (int y = min_y; y <= max_y; y++) { if (insideTriangle (x + .5 f, y + .5 f, t.v)) { float depth = get_z_interpolated (x + .5 f, y + .5 f); float &origin_depth = depth_buf[get_index (x, y)]; if (depth <= origin_depth) { Vector3f point (x, y, depth) ; origin_depth = depth; set_pixel (point, color); } } } } }
调试了半天也不对, 然后发现是作业1中的变换矩阵求错了, 附录 对此进行了修正.
任务2
判断叉积是否同号就行了
代码如下(这里对代码框架进行了修改,
参数改为float
类型方便后续MSAA的引入):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static bool insideTriangle (float x, float y, const Vector3f* _v) { Vector3f ver (x, y, 0 ) ; int cnt = 0 ; for (int i = 0 ; i < 3 ; i++) { Vector3f ver1 = _v[(i + 1 ) % 3 ] - _v[i]; ver1.z () = 0 ; Vector3f ver2 = ver - _v[i]; ver2.z () = 0 ; if (ver1.cross (ver2).z () < 0 ) { cnt++; } } return (cnt == 0 ) || (cnt == 3 ); }
任务3
单独维护子像素的缓冲队列, 这样能避免黑边问题.
添加以下成员:
1 2 3 const int sampling_rate = 2 ;std::vector<Eigen::Vector3f> frame_sample; std::vector<float > depth_sample;
更新以下函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void rst::rasterizer::clear (rst::Buffers buff){ if ((buff & rst::Buffers::Color) == rst::Buffers::Color) { std::fill (frame_buf.begin (), frame_buf.end (), Eigen::Vector3f{0 , 0 , 0 }); std::fill (frame_sample.begin (), frame_sample.end (), Eigen::Vector3f{ 0 , 0 , 0 }); } if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth) { std::fill (depth_buf.begin (), depth_buf.end (), std::numeric_limits<float >::infinity ()); std::fill (depth_sample.begin (), depth_sample.end (), std::numeric_limits<float >::infinity ()); } } rst::rasterizer::rasterizer (int w, int h) : width (w), height (h) { frame_buf.resize (w * h); depth_buf.resize (w * h); frame_sample.resize (w * h * sampling_rate * sampling_rate); depth_sample.resize (w * h * sampling_rate * sampling_rate); }
然后就可以开始写MSAA版的函数了, 代码如下:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 void rst::rasterizer::rasterize_triangle (const Triangle& t) { auto v = t.toVector4 (); auto get_z_interpolated = [t, v](float x, float y) -> float { auto [alpha, beta, gamma] = computeBarycentric2D (x, y, t.v); float w_reciprocal = 1.0 / (alpha / v[0 ].w () + beta / v[1 ].w () + gamma / v[2 ].w ()); float z_interpolated = alpha * v[0 ].z () / v[0 ].w () + beta * v[1 ].z () / v[1 ].w () + gamma * v[2 ].z () / v[2 ].w (); z_interpolated *= w_reciprocal; return z_interpolated; }; int min_x = std::min ({ v[0 ].x (), v[1 ].x (), v[2 ].x () }) - 1 ; min_x = std::max (0 , min_x); int max_x = std::max ({ v[0 ].x (), v[1 ].x (), v[2 ].x () }) + 1 ; max_x = std::min (width - 1 , max_x); int min_y = std::min ({ v[0 ].y (), v[1 ].y (), v[2 ].y () }) - 1 ; min_y = std::max (0 , min_y); int max_y = std::max ({ v[0 ].y (), v[1 ].y (), v[2 ].y () }) + 1 ; max_y = std::min (height - 1 , max_y); Vector3f color = t.getColor (); float step = 1.f / sampling_rate; for (int x = min_x; x <= max_x; x++) { for (int y = min_y; y <= max_y; y++) { int idx = get_index (x, y); int eid = idx * sampling_rate * sampling_rate; float _depth = 0 ; Vector3f _color(0 , 0 , 0 ); for (int a = 0 ; a < sampling_rate; a++) { for (int b = 0 ; b < sampling_rate; b++) { int bias = a * sampling_rate + b; float sub_x = x + (a + .5 f) * step; float sub_y = y + (b + .5 f) * step; if (insideTriangle (sub_x, sub_y, t.v)) { float depth = get_z_interpolated (sub_x, sub_y); if (depth <= depth_sample[eid + bias]) { depth_sample[eid + bias] = depth; frame_sample[eid + bias] = color; } } _depth += depth_sample[eid + bias]; _color += frame_sample[eid + bias]; } } _depth /= sampling_rate * sampling_rate; _color /= sampling_rate * sampling_rate; Vector3f point (x, y, _depth) ; depth_buf[idx] = _depth; set_pixel (point, _color); } } }
附录
透视投影变换矩阵的简单推导
透视投影变换可以分成两步, 先把远平面压缩, 再做正交投影变换.
正交投影变换矩阵
因为透视投影变换通常认为远近平面的中心点是 \((0, 0, z)\) ,
所以可以简化一下正交投影的变换矩阵(这里假定 \(n, f\) 的意义是到原点的距离且看向 \(-z\) 方向, 下同):
\[
\begin{bmatrix}
\frac{2}{w} & 0 & 0 & 0 \\
0 & \frac{2}{h} & 0 & 0 \\
0 & 0 & \frac{2}{n-f} & \frac{n+f}{n-f} \\
0 & 0 & 0 & 1
\end{bmatrix}
\]
\(x,y\)
坐标的变换
根据三角形相似很容易得到: \(\frac{y'}{-n}=\frac{y}{z}\) , 其中 \(y'\) 是近平面上的点的坐标, 从而 \(y' = -\frac{n}{z}y\)
\(x\) 坐标也同理
据此以及可以构造出初步的矩阵(考虑齐次坐标, 把分母上的 \(z\) 消去)了:
\[
\begin{bmatrix}
-n & 0 & 0 & 0 \\
0 & -n & 0 & 0 \\
0 & 0 & ? & ? \\
0 & 0 & 1 & 0 \\
\end{bmatrix} *
\begin{bmatrix}
x \\
y \\
z \\
1 \\
\end{bmatrix} =
\begin{bmatrix}
-nx \\
-ny \\
? \\
z \\
\end{bmatrix}
\]
\(z\)
坐标的变换
考虑 \((x, y, -n, 1)^{T}\) 和 \((x, y, -f, 1)^{T}\) 变换后 \(z\) 坐标不变:
\[
\begin{bmatrix}
-n & 0 & 0 & 0 \\
0 & -n & 0 & 0 \\
0 & 0 & A & B \\
0 & 0 & 1 & 0 \\
\end{bmatrix} *
\begin{bmatrix}
x \\
y \\
-n \\
1 \\
\end{bmatrix} =
\begin{bmatrix}
-nx \\
-ny \\
n^{2} \\
-n \\
\end{bmatrix}, \qquad
\begin{bmatrix}
-n & 0 & 0 & 0 \\
0 & -n & 0 & 0 \\
0 & 0 & A & B \\
0 & 0 & 1 & 0 \\
\end{bmatrix} *
\begin{bmatrix}
x \\
y \\
-f \\
1 \\
\end{bmatrix} =
\begin{bmatrix}
-fx \\
-fy \\
f^{2} \\
-f \\
\end{bmatrix}
\]
解下列方程组:
\[
\begin{cases}
-nA+B=n^{2} \\
-fA+B=f^{2}
\end{cases} \quad \Rightarrow \quad
\begin{cases}
A=-(n+f) \\
B=-nf
\end{cases}
\]
透视投影变换矩阵
把两个矩阵乘起来(注意先后顺序, 先压缩,
再正交投影)就得到了透视投影变换矩阵:
\[
\begin{bmatrix}
\frac{2}{w} & 0 & 0 & 0 \\
0 & \frac{2}{h} & 0 & 0 \\
0 & 0 & \frac{2}{n-f} & \frac{n+f}{n-f} \\
0 & 0 & 0 & 1
\end{bmatrix} *
\begin{bmatrix}
-n & 0 & 0 & 0 \\
0 & -n & 0 & 0 \\
0 & 0 & -(n+f) & -nf \\
0 & 0 & 1 & 0 \\
\end{bmatrix} =
\begin{bmatrix}
-\frac{2n}{w} & 0 & 0 & 0 \\
0 & -\frac{2n}{h} & 0 & 0 \\
0 & 0 & -\frac{n+f}{n-f} & -\frac{2nf}{n-f} \\
0 & 0 & 1 & 0 \\
\end{bmatrix}
\]