计算机图形学games101作业二 ---- 三角形光栅化 超采样抗锯齿 黑边处理

games101作业二 ---- 三角形光栅化 超采样抗锯齿 黑边处理


(本文是在学习计算机图形学时根据课程作业进行整理的笔记,有错误请指出,如果是同课程,请勿复制粘贴,谢谢!

CG_homework1

0. 本次作业 实现函数及简单描述(详细 见后代码描述部分)

以下是这次作业中主要撰写的几个函数,及其简要功能描述,本次作业完整代码在github;链接,可能会有一些细节问题需要在项目中进行微小的设置。

  1. static bool insideTriangle(float x, float y, const Vector3f* _v)

    通过向量叉乘 判断点是否在三角形内部

  2. void rst::rasterizer::rasterize_triangle(const Triangle& t)

    三角形光栅化,超采样super_sample, 用于将一个三角形图形采用超采样的方法 投影到屏幕上的像素并进行渲染优化。最终通过记录每一个点的超采样子像素值进行优化黑边问题。

  3. int rst::rasterizer::get_super_index(int x, int y)

    超采样时候,将记录域扩大四倍,重新定义像素点到深度和颜色缓冲区的索引

  4. rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)

    扩大深度和颜色的空间

1. C++ – CG 基本语法

  • Vector3f : 常见的数据类型,通常用于表示三维空间中的向量或点。
    • Vector3f 中的 “Vector” 意味着它是一个矢量,可以表示方向和大小。
    • “3” 表示这是一个三维矢量,它有三个分量。
    • “f” 表示这些分量是浮点数,通常用于存储小数值。
  • Eigen::Vector2f : 是Eigen库中的数据类型,它表示一个二维的浮点数向量。Eigen库是一个C++模板库,用于进行线性代数运算,特别是矩阵和向量运算.
    • Eigen 是库的命名空间。
    • Vector2f 表示这是一个二维的浮点数向量。
    • 二维向量通常用于表示二维空间中的点、方向或位移。
  • AP = P - A : 计算从点 A 到点 P 的向量。它从点 P 的坐标中减去点 A 的坐标来获得这个向量。
  • eq1 = AB[0] * AP[1] - AB[1] * AP[0] : 是计算在三角形光栅化中用于判断一个点是否在三角形内部的关键步骤。 补充(ABXAP=x1y2-x2y1)
  • std::tie : 创建一个 std::tuple{} 接受后面重心插值的结果

2. 代码描述

A. insideTriangle() 判断点是否在三角形内部

insideTriangle()是一个用于确定给定点(x,y)是否位于三角形内部的常见数学算法的实现。它使用重心坐标的概念来进行判断。

以下是代码如何工作的详细步骤

  1. 首先,定义了三个二维向量,ABC,它们表示三角形的顶点。这些顶点从 _v 数组中提取而来。
  2. 计算了三个向量,APBPCP,它们表示点 P 到三角形的各个顶点 ABC 的向量。
  3. 计算了三个值,eq1eq2eq3,它们本质上是向量 APBPCP 分别与三角形的边 ABBCCA 的叉乘。
  4. 最后,检查 eq1eq2eq3 的符号。如果它们三个都是正数或者都是负数,那意味着点 P 位于三角形内部,函数返回 true。否则,它返回 false

本质上是检查点是否在三角形的每条边的同一侧,这是在三角形内的点所具有的特性。如果三个检查都通过,它会认为点在三角形内部。

计算的时候都是指定三角形的顶点是按逆时针顺序的,右手系法则。

static bool insideTriangle(float x, float y, const Vector3f* _v)
{   
    // TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
    const Eigen::Vector2f P(x, y);
    const Eigen::Vector2f A = _v[0].head(2), B = _v[1].head(2), C = _v[2].head(2);

    const Eigen::Vector2f AP = P - A;
    const Eigen::Vector2f BP = P - B;
    const Eigen::Vector2f CP = P - C;
    const Eigen::Vector2f AB = B - A;
    const Eigen::Vector2f BC = C - B;
    const Eigen::Vector2f CA = A - C;

    float eq1 = AB[0] * AP[1] - AB[1] * AP[0];   // ABXAP
    float eq2 = BC[0] * BP[1] - BC[1] * BP[0];   // BCXBP
    float eq3 = CA[0] * CP[1] - CA[1] * CP[0];   // CAXCP

    if (eq1 > 0 && eq2 > 0 && eq3 > 0)    // 有无在三角形内
        return true;
    else if (eq1 < 0 && eq2 < 0 && eq3 < 0)
        return true;
    else
        return false;
    
}

B.rasterize_triangle(const Triangle& t) 光栅化,超采样super_sample

用于将一个三角形图元投影到 屏幕上的像素并进行渲染

超采样改进函数:

rasterize_triangle(const Triangle& t) : 用于三角形光栅化(Rasterization)的函数,通常用于计算机图形学中将三维图形渲染到二维屏幕上的过程。具体计算步骤如下:

  1. Bounding Box计算:首先,代码计算了包围三角形的边界框(Bounding Box)。这是通过找到三个顶点的x和y坐标的最小和最大值来完成的。然后,这些坐标被取整,以便将Bounding Box转换为整数坐标,以便在之后的循环中使用。
  2. 超采样(Super Sampling):代码定义了一个超采样步骤的集合,用于抗锯齿处理。在每个像素内部,它会采样四个子像素,并检查它们是否在三角形内。如果是的话,就增加一个计数器,以确定超采样过程中有多少个子像素落在了三角形内,最终颜色取四个的平均值
  3. 遍历Bounding Box:然后,代码通过两个嵌套的循环遍历了Bounding Box 内的每个像素。对于每个像素,它检查是否有子像素落在三角形内,如果有,就增加计数器。这部分的目标是确定在像素内的子像素数量,以实现超采样抗锯齿。
  4. 深度测试:如果有子像素在三角形内,代码将执行深度测试。它使用三角形的顶点数据(v)和像素位置(x,y)来计算插值的深度值(z_interpolated)。然后,它与深度缓冲区(depth_buf)中的当前深度值进行比较。如果新的深度值更小(表示该像素离观察者更近),则将该像素的颜色设置为三角形的颜色(使用getColor函数)的加权平均,同时更新深度缓冲区中的深度值。
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

    //1. 找到Bounding Box,也就是最大最小的x,y坐标然后再进行向上向下取整。并将bounding box扩大一点,得到得结果是整数值,方便迭代遍历。
    float xmin = std::min(std::min(v[0].x(), v[1].x()), v[2].x());
    float xmax = std::max(std::max(v[0].x(), v[1].x()), v[2].x());
    float ymin = std::min(std::min(v[0].y(), v[1].y()), v[2].y());
    float ymax = std::max(std::max(v[0].y(), v[1].y()), v[2].y());

    xmin = (int)std::floor(xmin);
    xmax = (int)std::ceil(xmax);
    ymin = (int)std::floor(ymin);
    ymax = (int)std::ceil(ymax);

    // 超采样   super_sample_step 将一个正方形内再细分四个点,计算点的个数,着色的时候取均值t.getColor()*count/4
    std::vector<Eigen::Vector2f> super_sample_step
    {
        {0.25,0.25},
        {0.75,0.25},
        {0.25,0.75},
        {0.75,0.75},
    };
   
    //2. 在Bounding box 内遍历所有元素,判断是否在三角形内部,并且进行超采样判断有几个点落在三角形内
    for (int x = xmin; x <= xmax; x++)
    {
        for (int y = ymin; y <= ymax; y++)
        {
            int count = 0;
            float minDepth = FLT_MAX;
            // 如果超采样有数据,整体的颜色就取均值
            for (int i = 0; i < 4; i++)
            {
                if (insideTriangle(x + super_sample_step[i][0], y + super_sample_step[i][1], t.v))
                {
                    count++;
                }
            }

            //像素的坐标值只是比整数编号值大0.5if (count>0)
            {
                //  求深度值 z_interpolated    

                float alpha, beta, gamma;
                std::tie(alpha, beta, gamma) = computeBarycentric2D(x, y, t.v);

                //  std::tie :  创建一个std::tuple{}接受后面重心插值的结果

                float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
                // z_interpolated : 
                // w_reciprocal :
                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;
                
                //若当前位置深度比depth_buf)更小,则更新颜色值 。
                
                if (z_interpolated < depth_buf[get_index(x, y)])
                {
                    set_pixel(Vector3f(x, y, z_interpolated), t.getColor()*count/4);
                    depth_buf[get_index(x, y)] = z_interpolated;
                }

            }
        }
    }
}

C. 黑边处理(难度升级)

原因分析:

  • 黑边出现是因为原本的深度信息不够精确,渲染绿色三角形的像素点在渲染蓝色三角形之前,导致边缘的颜先与黑色背景进行插值变黑,而蓝色三角形在之后渲染没能覆盖首先渲染的绿色三角形已经差值过的颜色

解决办法:

  • 因此解决这个问题的办法就是把每个像素的四个样本的深度和颜色都记录下来,就是直接将其看作四倍的图像进行处理,然后再分别去进行计算和着色。

改进总结:

超采样测试:在每个像素内部,代码进一步遍历了四个超采样子像素。对于每个子像素,它调用 insideTriangle 函数来检查是否在三角形内部。如果在三角形内部,它执行以下操作:

  • 计算重心坐标(alpha、beta、gamma)以及插值深度值(z_interpolated),这部分代码似乎使用了未定义的变量 v,应该使用 t.v
  • 计算 z_interpolated 的深度值。
  • 更新超采样深度缓冲区 super_depth_buf 和超采样帧缓冲区 super_frame_buf 的对应子像素位置的深度值和颜色。

判断是否需要着色:接下来,代码检查一个名为 judge 的标志,如果其中任何一个子像素通过深度测试,则将 judge 设置为1。这个标志用于确定是否需要在原始像素位置上进行着色。

着色:如果 judge 被设置为1,表示至少一个子像素通过了深度测试,那么代码会计算原始像素位置上的颜色。它将四个超采样子像素的颜色取平均值,并使用 set_pixel 函数将平均颜色设置为原始像素位置的颜色。

// 扩大深度和颜色的空间
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{
    frame_buf.resize(w * h);
    depth_buf.resize(w * h);
    super_frame_buf.resize(w * h * 4);
    super_depth_buf.resize(w * h * 4);
}
// 重新定义像素点到深度和颜色缓冲区的索引
int rst::rasterizer::get_super_index(int x, int y)
{
    //  扩大四倍的域都进行记录
    return (height * 2 - 1 - y) * width * 2 + x;
}
std::vector<Eigen::Vector2f> super_sample_step
{
    {0.25,0.25},
    {0.75,0.25},
    {0.25,0.75},
    {0.75,0.75},
};
// 遍历变量进行深度检测并赋予颜色color值
 for (int x = xmin; x <= xmax; x++)
{
     for (int y = ymin; y <= ymax; y++)
     {
        int judge = 0;
        //具体思路就是记录四倍的数据量,把超采样的数据都记下来
        for (int i = 0; i < 4; i++)
        {
            //  如果在在三角形内部进行深度判断
            if (insideTriangle(x + super_sample_step[i][0], y + super_sample_step[i][1], t.v))
            {
                float alpha, beta, gamma;
std::tie(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;
                // 再次进行 坐标变换
                //  在x,y轴上坐标处理方式不一样,在四个块状正方形 从0到3  x轴会变化四次,y轴变化两次因此如下处理。
                if (super_depth_buf[get_super_index(x*2 + i % 2, y*2 + i / 2)] > z_interpolated)
                {
                    judge = 1;
                    super_depth_buf[get_super_index(x*2 + i % 2, y*2 + i / 2)] = z_interpolated;
                    super_frame_buf[get_super_index(x*2 + i % 2, y*2 + i / 2)] = t.getColor();
                }
            }
        }
        if (judge)
        //若像素的四个样本中有一个通过了深度测试,就需要对该像素进行着色。
        {
            Vector3f point = { (float)x,(float)y,0 };
            Vector3f color = (super_frame_buf[get_super_index(x*2 , y*2)]+ super_frame_buf[get_super_index(x*2+1, y*2)]+ super_frame_buf[get_super_index(x*2, y*2+1)]+ super_frame_buf[get_super_index(x*2+1, y*2+1)])/4;
            set_pixel(point, color);
        }
    }
}

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

herry_drj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值