作业描述

上一次作业在视口变化之后调用了函数 rasterize_wireframe(const Triangle& t)。这一次需要自己填写并调用函数 rasterize_triangle(const Triangle& t)。

该函数的内部工作流程如下:

  1. 创建三角形的 2 维 bounding box。
  2. 遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。
  3. 如果在内部,则将其位置处的插值深度值 (interpolated depth value) 与深度缓冲区 (depth buffer) 中的相应值进行比较。
  4. 如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区 (depth buffer)。

需要修改的函数如下:

  • rasterize_triangle(): 执行三角形栅格化算法
  • static bool insideTriangle(): 测试点是否在三角形内。可以修改此函数的定义,这意味着可以按照自己的方式更新返回类型或函数参数。

此外:

  • 要求正确实现z-buffer算法,将三角形按顺序画在屏幕上。

附加题:

  • 用 super-sampling 处理 Anti-aliasing : 对每个像素进行 2 * 2 采样,并比较前后的结果 (这里并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个sample list。最后,如果实现正确的话,得到的三角形不应该有不正常的黑边。

解题思路

  1. 创建三角形的最小包围盒:直接比较坐标,找出四个顶点取整。

  2. 遍历盒内像素判断是否在三角形内:链接目标点与三角形顶点,使用叉积结果符号判断。

  3. 处于三角形内部的像素差值深度值与缓冲区相应值比较,用较小值(距离摄像机近)替换。

  4. 设置像素颜色。

  5. 提高:将每个像素划分为4个子像素,采样子像素点并记录深度和颜色信息,需要维护一个4倍大小的color_buf和depth_buf,这样边缘信息才不会丢失。实现正确的话得到的三角形不会有不正常的黑边。

    SSAAx2

代码实现

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());
                    }
                }
            }
        }
    }
}

渲染效果

未开启抗锯齿:

未开启抗锯齿

曾碰到的问题:开启了抗锯齿却带有黑边(丢失了边缘子像素颜色和深度信息)

开启了抗锯齿却带有黑边

问题解决后的抗锯齿模式:

SSAAx2抗锯齿模式