计算机图形学 作业二:Triangles and Z-buffering
文章目录
1.题目概述:
本次作业的任务是画一个实心三角形,即栅格化一个三角形,所以需要完成一个栅格化三角形的函数,该函数工作流程如下:
而我们还需要完成的一个函数判断测试点是否在三角形内;综上,我们需要完成如下函数:
提高:光栅化后的图像放大后边缘会有锯齿感,因此需要采用多重采样反走样(MSAA),即对每个像素进行 2 * 2 采样,并比较前后的结果 (这里 并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个 sample list。
2.知识储备:
2.1 如何判断点是否在三角形内
对顶点和目标点在边的同一侧(如下图),用数学表示就是三个顶点与目标点连线向量与对边向量的点乘同符号,因为点乘结果的正负与两向量夹角大小有关。
2.2 如何栅格化三角形
题目已经给了提示:
2.3 MSAA
2.3.1. 何为走样,为何走样:
几何体走样,即为几何物体的边缘有锯齿,几何走样由于对几何边缘采样不足导致。
2.3.2 多重采样反走样法:
只计算每个像素的颜色,而对于那些子采样点只计算一个覆盖信息(coverage)和遮挡信息(occlusion)来把像素的颜色信息写到每个子采样点里面,最终根据子采样点里面的颜色值来通过某个重建过滤器来降采样生成目标图像。
2.4 Z-Buffer算法:
基本思想: 对于每一个像素,保存当前最小的|z|值作为该像素所绘制的物体的深度需要一个额外的Buffer来记录每一个像素的当前深度值|z|
• 帧缓冲(frame buffer)存储像素的颜色的值(color values)
• 深度缓冲(z-buffer)存储像素的深度值(depth)
注:本题中的z都为正数,所以可以直接用z, z越小,距离越近;z越大,距离越远
3.解答过程:
3.1.insideTriangle函数实现:
- 利用传入的参数计算出三角形三边的向量;
- 计算出目标点与对顶点的连线组成的向量;
- 分别将上边各向量点乘然后判断正否;
- 最后输出比较结果,同符号即为在三角形内,否则则不在三角形内。
代码如下:
static bool insideTriangle(double x, double y, const Vector3f* _v)
{ //这里的int变成了double
// TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
//测试点向量
Eigen::Vector2f p;
p << x, y;
//三角形各边的向量
Eigen::Vector2f AB = _v[1].head(2) - _v[0].head(2);
Eigen::Vector2f BC = _v[2].head(2) - _v[1].head(2);
Eigen::Vector2f CA = _v[0].head(2) - _v[2].head(2);
//测试点与三角形顶点连线的向量
Eigen::Vector2f AP = p - _v[0].head(2);
Eigen::Vector2f BP = p - _v[1].head(2);
Eigen::Vector2f CP = p - _v[2].head(2);
// 三角形各边的向量叉乘测试点和顶点连线向量,
//然后叉乘结果是否有相同的符号
return (AB[0] * AP[1] - AB[1] * AP[0] > 0
&& BC[0] * BP[1] - BC[1] * BP[0] > 0
&& CA[0] * CP[1] - CA[1] * CP[0] > 0)||
(AB[0] * AP[1] - AB[1] * AP[0] < 0
&& BC[0] * BP[1] - BC[1] * BP[0] < 0
&& CA[0] * CP[1] - CA[1] * CP[0] < 0);
}
3.2.三角形栅格化:
-
找出当前三角形的边界框;
-
遍历像素并找出当前像素是否在三角形内
-
如果是这样,请使用以下代码获得插值的z值;
-
如果要绘制三角形,请将当前像素(使用set\ pixel函数)设置为三角形的颜色(使用getColor函数)。
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;
PS:
寻找三角形的bounding box:根据三角形的三个坐标,找出最大x,最小x,最大y,最小y坐标即可,注意最小x,y坐标需要取比原数小,最大x,y坐标需要取比原数。
3.3.MSAA
MSAA 4X 深度:把一个点看成一个格子,判断里面4个小点是否落在三角形内,然后找到其中插值的最小z值,和帧缓冲中的z值进行比较替换即可。
代码如下:
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();
// TODO : Find out the bounding box of current triangle.
// iterate through the pixel and find if the current pixel is inside the triangle
// If so, use the following code to get the interpolated z value.
//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;
// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.
// 用矩形将三角形包围起来,找到矩形的四个顶点,构建三角形边界框
float min_x = std::min(v[0][0], std::min(v[1][0], v[2][0]));
float max_x = std::max(v[0][0], std::max(v[1][0], v[2][0]));
float min_y = std::min(v[0][1], std::min(v[1][1], v[2][1]));
float max_y = std::max(v[0][1], std::max(v[1][1], v[2][1]));
min_x = (int)std::floor(min_x);
max_x = (int)std::ceil(max_x);
min_y = (int)std::floor(min_y);
max_y = (int)std::ceil(max_y);
bool MSAA = false;
//用MSAA方法实现
if (MSAA) {
// 格子里的细分四个小点坐标
std::vector<Eigen::Vector2f> pos
{
{0.25,0.25},
{0.75,0.25},
{0.25,0.75},
{0.75,0.75},
};
for (int x = min_x; x <= max_x; x++) {
for (int y = min_y; y <= max_y; y++) {
// 记录最小深度
float minDepth = FLT_MAX;
// 四个小点中落入三角形中的点的个数
int count = 0;
// 对四个小点坐标进行判断
for (int i = 0; i < 4; i++) {
// 小点是否在三角形内
if (insideTriangle((float)x + pos[i][0], (float)y + pos[i][1], t.v)) {
// 如果在,对深度z进行插值
auto tup = computeBarycentric2D((float)x + pos[i][0], (float)y + pos[i][1], t.v);
float alpha;
float beta;
float gamma;
std::tie(alpha, beta, gamma) = tup;
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;
minDepth = std::min(minDepth, z_interpolated);
count++;
}
}
if (count != 0) {
if (depth_buf[get_index(x, y)] > minDepth) {
Vector3f color = t.getColor() * count / 4.0;
Vector3f point(3);
point << (float)x, (float)y, minDepth;
// 替换深度
depth_buf[get_index(x, y)] = minDepth;
// 修改颜色
set_pixel(point, color);
}
}
}
}
}
else {
//遍历三角形边界框中的所有测试点
for (int x = min_x; x <= max_x; x++) {
for (int y = min_y; y <= max_y; y++) {
//像素点中心是否在三角形内部
if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v)) {
//是,则插值
auto tup = computeBarycentric2D((float)x + 0.5, (float)y + 0.5, t.v);
float alpha;
float beta;
float gamma;
std::tie(alpha, beta, gamma) = tup;
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;
///如果x,y所在点的深度小于z-buffer的深度
if (depth_buf[get_index(x, y)] > z_interpolated) {
Vector3f color = t.getColor();
Vector3f point(3);
point << (float)x, (float)y, z_interpolated;
depth_buf[get_index(x, y)] = z_interpolated;
set_pixel(point, color);
}
}
}
}
}
}
运行结果:
起初未反走样是这样:
更改了get_view_matrix()之后:
反走样后:
对比:
4.总结思考:
计算机学科真的是与数学学科紧密相连啊!!
——一个学习了机器学习和计算机图形学之后的软工学生的内心独白