作业描述
上一次作业在视口变化之后调用了函数 rasterize_wireframe(const Triangle& t)。这一次需要自己填写并调用函数 rasterize_triangle(const Triangle& t)。
该函数的内部工作流程如下:
- 创建三角形的 2 维 bounding box。
- 遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。
- 如果在内部,则将其位置处的插值深度值 (interpolated depth value) 与深度缓冲区 (depth buffer) 中的相应值进行比较。
- 如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区 (depth buffer)。
需要修改的函数如下:
- rasterize_triangle(): 执行三角形栅格化算法
- static bool insideTriangle(): 测试点是否在三角形内。可以修改此函数的定义,这意味着可以按照自己的方式更新返回类型或函数参数。
此外:
- 要求正确实现z-buffer算法,将三角形按顺序画在屏幕上。
附加题:
- 用 super-sampling 处理 Anti-aliasing : 对每个像素进行 2 * 2 采样,并比较前后的结果 (这里并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个sample list。最后,如果实现正确的话,得到的三角形不应该有不正常的黑边。
解题思路
-
创建三角形的最小包围盒:直接比较坐标,找出四个顶点取整。
-
遍历盒内像素判断是否在三角形内:链接目标点与三角形顶点,使用叉积结果符号判断。
-
处于三角形内部的像素差值深度值与缓冲区相应值比较,用较小值(距离摄像机近)替换。
-
设置像素颜色。
-
提高:将每个像素划分为4个子像素,采样子像素点并记录深度和颜色信息,需要维护一个4倍大小的color_buf和depth_buf,这样边缘信息才不会丢失。实现正确的话得到的三角形不会有不正常的黑边。
代码实现
static bool insideTriangle(float x, float y, const Vector3f* _v) {
// DONE : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
Vector3f P(x, y, 1.0f);
Vector3f AB = _v[1] - _v[0];
Vector3f BC = _v[2] - _v[1];
Vector3f CA = _v[0] - _v[2];
Vector3f AP = P - _v[0];
Vector3f BP = P - _v[1];
Vector3f CP = P - _v[2];
float z1 = AB.cross(AP).z();
float z2 = BC.cross(BP).z();
float z3 = CA.cross(CP).z();
return (z1 > 0 && z2 > 0 && z3 > 0) || (z1 < 0 && z2 < 0 && z3 < 0);
}
//Screen space rasterization
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();
// DONE : Find out the bounding box of current triangle.
float x_min = std::min(v[0][0], std::min(v[1][0], v[2][0]));
float x_max = std::max(v[0][0], std::max(v[1][0], v[2][0]));
float y_min = std::min(v[0][1], std::min(v[1][1], v[2][1]));
float y_max = std::max(v[0][1], std::max(v[1][1], v[2][1]));
// 取整,保证三角形在包围盒内
int left = std::floor(x_min);
int right = std::ceil(x_max);
int bottom = std::floor(y_min);
int top = std::ceil(y_max);
// SSAAx2
bool SSAA = true;
// iterate through the pixel and find if the current pixel is inside the triangle
for (int x = left; x <= right; ++x) {
for (int y = bottom; y <= top;++y) {
if (SSAA) {
float minDepth = std::numeric_limits<float>::infinity();
bool isSetPixel = false;
Vector3f comb_color = Vector3f::Zero();
for (float i = 0.0;i < 1.0; i += 1.0 / SSAA_times) {
for (float j = 0.0;j < 1.0; j += 1.0 / SSAA_times) {
Vector3f subP(x + i, y + j, 0);
int sub_index = get_sub_index(subP.x(), subP.y());
if (insideTriangle(subP.x(), subP.y(), t.v)) {
// 深度值
auto [alpha, beta, gamma] = computeBarycentric2D(subP.x(), subP.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要反向
z_interpolated *= w_reciprocal;
if (z_interpolated < depth_buf[sub_index]) {
depth_buf[sub_index] = z_interpolated;
color_buf[sub_index] = t.getColor();
isSetPixel = true;
}
}
comb_color += (1. / (SSAA_times * SSAA_times)) * color_buf[sub_index];
}
}
if (isSetPixel) {
set_pixel(Vector3f(x, y, 0.0), comb_color);
}
}
else {
if (insideTriangle(x, y, t.v)) {
// 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要反向
z_interpolated *= w_reciprocal;
if (z_interpolated < depth_buf[get_index(x, y)]) {
depth_buf[get_index(x, y)] = z_interpolated;
// DONE : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.
set_pixel(Vector3f(x, y, z_interpolated), t.getColor());
}
}
}
}
}
}
渲染效果
未开启抗锯齿:
曾碰到的问题:开启了抗锯齿却带有黑边(丢失了边缘子像素颜色和深度信息)
问题解决后的抗锯齿模式: