GAMES101 作业2 ——三角形光栅化

举报
lutianfei 发表于 2022/05/07 19:27:26 2022/05/07
【摘要】 作业任务:填写并调用函数 rasterize_triangle(const Triangle& t)。即实现光栅化该函数的内部工作流程如下:创建三角形的 2 维 bounding box。遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。如果在内部,则将其位置处的插值深度值 (interpolated depth v...

作业任务:
填写并调用函数 rasterize_triangle(const Triangle& t)。

即实现光栅化

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

创建三角形的 2 维 bounding box。
遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中
心的屏幕空间坐标来检查中心点是否在三角形内。
如果在内部,则将其位置处的插值深度值 (interpolated depth value) 与深度
缓冲区 (depth buffer) 中的相应值进行比较。
如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区 (depth buffer)。
你需要修改的函数如下:
• rasterize_triangle(): 执行三角形栅格化算法
• static bool insideTriangle(): 测试点是否在三角形内。你可以修改此函
数的定义,这意味着,你可以按照自己的方式更新返回类型或函数参数。

所以我们第一步实现static bool insideTriangle()

static bool insideTriangle(int x, int y, const Vector3f *_v) {
    //已知测试点 point (x, y)
    // 三角形顶点 _v[0], _v[1], _v[2]
    // 向量叉乘公式(x1,y1)x(x2,y2) = x1*y2 - y1*x2

    Eigen::Vector2f p;
    p << x, y;

    //1. 准备三角形各边的向量
    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);

    //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);

    //3. 判断叉乘结果
    float z1 = AB.x() * AP.y() - AB.y() * AP.x();
    float z2 = BC.x() * BP.y() - BC.y() * BP.x();
    float z3 = CA.x() * CP.y() - CA.y() * CP.x();

    if ((z1 > 0 && z2 > 0 && z3 > 0) || (z1 < 0 && z2 < 0 && z3 < 0)) {
        return true;
    } else {
        return false;
    }
}

第二步:逐一扫描这个bounding box内的所有单元,以每个单元的中心位置代替此单元,将坐标传入insideTriangle函数中,如果不在,则扫描下一个,如果在,则以三角形三点做插值得到这个中心点的z值,进一步判断,这个z值是否比深度缓存中对应位置的值要小,不小则不处理,小则替换深度缓存中的值为当前值。随后设置这个单元的颜色值。

//Screen space rasterization
void rst::rasterizer::rasterize_triangle(const Triangle &t) {
    auto v = t.toVector4(); //将3D坐标转为4D齐次坐标

    // 1. 找到三角形的bounding box(最小包裹矩形框)的坐标
    float min_x = std::min(v[0][0], std::min(v[1][0], v[2][0])); //获取三个顶点中最小的x坐标,这里也可以用v[0/1/2].x()表示
    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]));


    // 2. 遍历三角形bbox中的所有测试点
    for (int x = min_x; x <= max_x; x++) {
        for (int y = min_y; y <= max_y; y++) {

            //2.1 判断(x,y)是否在三角形内
            if (insideTriangle(x, y, t.v)) {
                float min_depth = FLT_MAX; //默认值为无穷大(远)

                // 如果在三角形内部,计算该位置的插值深度值(interpolated depth 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;

                min_depth = std::min(min_depth, z_interpolated);

                // 与深度缓冲区(depth buffer)对应值进行比较
                if (depth_buf[get_index(x, y)] > min_depth) {
                    // set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.
                    Vector3f color = t.getColor();
                    Vector3f point;
                    point << x, y, min_depth;
                    depth_buf[get_index(x, y)] = min_depth;
                    set_pixel(point, color);
                }
            }


        }
    }


}

输出结果:

image.png

注意:如果遇到下图三角形旋转180°的情况,说明是Y轴反了,因为教程里的公式是向-Z看的,但程序里nera和far给的数值却是[0.1, 50]

image.png

更新get_projection_matrix中的 上截面结果为负即可。

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
...
    auto t = -zNear * tan(angle / 2); //上截面, 前面增加负号
...
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。