【GAMES101】作业2学习总结

本系列博客为记录笔者在学习GAMES101课程时遇到的问题与思考。

  • GAMES101:课程官网
  • GAMES101:B站视频
  • GAMES101:相关文件下载(百度网盘)

一、基础题

本次作业的目的是为了让我们熟悉三角形栅格化的相关操作,通过Assignment2.pdf可以知道本次作业的任务是填充完整两个函数

  • static bool insideTriangle():测试点是否在三角形内。
  • rasterize_triangle():执行三角形栅格化算法
  1. 首先我们要将作业1的get_projection_matrix() 函数拷贝至作业2的对应函数中去,其中需要注意一个点是要把zNear = -zNear;zFar = -zFar;给注释掉,否则最后得到的三角形会是相反的;究其原因是因为闫教授上课说到的,做深度测试的时候我们需要转换一个观念,那就是深度值小的离我们近,而深度值大的算是离我们远,但是作业0的相关函数都是我们朝向z轴负方向看去的,也就是说深度值越大的数其实是离我们越近的。

  2. 再讲解insideTriangle() 函数,照例先分析函数参数含义:

    • x/y:表示需要测试点的x/y坐标
    • _v:通过观察Triangle.cpp文件可知,_v是一个三维矢量,但是每一维都是一个三维矢量,表示三角形的三个顶点坐标
      Triangle::Triangle() {
          v[0] << 0,0,0;
          v[1] << 0,0,0;
          v[2] << 0,0,0;
      
          color[0] << 0.0, 0.0, 0.0;
          color[1] << 0.0, 0.0, 0.0;
          color[2] << 0.0, 0.0, 0.0;
      
          tex_coords[0] << 0.0, 0.0;
          tex_coords[1] << 0.0, 0.0;
          tex_coords[2] << 0.0, 0.0;
      }
      

    闫教授讲课时提到过,判断一个点是否在三角形能有一种方法就是,用这个点与三角形三个顶点相连形成三个向量,再让这三个顶点依次相连也会形成三个向量,再让对应顶点的向量相互叉乘,会得到三个数,若这三个数符号相同则表示这个点在三角形内部,由此可以直接写出insideTriangle() 函数:

    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]
        Eigen::Vector3f point(x, y, 0);
        Eigen::Vector3f side1, side2, side3;
        side1 << _v[1] - _v[0];
        side2 << _v[2] - _v[1];
        side3 << _v[0] - _v[2];
        // calculate the cross of two vector
        float z1 = ((point - _v[0]).cross(side1)).z();
        float z2 = ((point - _v[1]).cross(side2)).z();
        float z3 = ((point - _v[2]).cross(side3)).z();
        // Determine if the sybol is the same
        if ((z1 > 0 && z2 > 0 && z3 > 0) || (z1 < 0 && z2 < 0 && z3 < 0))
            return true;
        return false;
    }
    
  3. 再分析rasterize_triangle() 函数,其中形参tTriangle类型,也就是上面提到过的Triangle.cpp文件中的代码。
    闫教授说栅格化的时候有一种办法就是,框出这个三角形所占空间的一个立方体,也就是这个这个立方体是恰好包围住这个三角形,然后遍历这个立方体内的每一个像素进行深度测试,来决定是否对这个像素进行染色。

    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.
        Eigen::Vector2f min_p, max_p;
        min_p.x() = MIN(MIN(v[0].x(), v[1].x()), v[2].x());
        min_p.y() = MIN(MIN(v[0].y(), v[1].y()), v[2].y());
        max_p.x() = MAX(MAX(v[0].x(), v[1].x()), v[2].x());
        max_p.y() = MAX(MAX(v[0].y(), v[1].y()), v[2].y());
        
        for (int i = min_p.x(); i <= max_p.x(); i++) {
            for (int j = min_p.y(); j <= max_p.y(); j++) {
                if(insideTriangle(i, j, t.v)) {
                    auto[alpha, beta, gamma] = computeBarycentric2D(i, j, 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;
                    if (z_interpolated < depth_buf[get_index(i, j)]) {
                        set_pixel(Eigen::Vector3f((float)i, (float)j, z_interpolated), t.getColor());
                        depth_buf[get_index(i, j)] = z_interpolated;
                    }
                }
            }
        }
    }
    

    其中min_pmax_p分别代表三角形中x/y的最小值和最大值,因此取出这两个点之后围成的立方体可以恰好包围整个三角形。由此可以遍历整个立方体,判断空间中的每一个像素。

    上述代码中的双层for循环就是遍历了整个空间,而每当遍历一个像素点时需要判断这个点是否在三角形内,如果不在三角形内的话就根本不需要进行染色操作,因为我们的目的只是染色整个三角形。

    随后这四行代码是用差值的方法得到了其深度值,因为有关的内容尚未在课程中涉及,所以框架已经处理好了这一部分,直接调用即可。

    auto[alpha, beta, gamma] = computeBarycentric2D(i, j, 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;
    

    最后是判断深度值是否小于该像素点的深度,若小于说明该三角形离我们更近,会遮蔽其后的三角形,所以需要更新该像素点的颜色,再更新该像素点的深度,对应

    if (z_interpolated < depth_buf[get_index(i, j)]) {
    	set_pixel(Eigen::Vector3f((float)i, (float)j, z_interpolated), t.getColor());
    	depth_buf[get_index(i, j)] = z_interpolated;
    }
    

    若一切进行顺利的话,运行run2.sh函数应该会出现如下图像:
    在这里插入图片描述
    如果放大该图片可以发现有明显的锯齿状,这就是闫教授上课提到的Jaggies!,至此基础题完成,提高题就是让我们完成Antialiasing反锯齿。
    在这里插入图片描述

二、提高题

1、使用SMAA消除锯齿

MSAA的原理(详情见GAMES101_Lecture_06.pdf第63页)就是将一个像素点分为2×2四个点,一个像素点的颜色不该由像素的中心是否在三角形内而全盘否定,而是看四个点中有几个点在三角形内而进行色彩的平均,这样的话就可以模糊三角形的边界,达到反锯齿的目的。

if (MSAA) {
    std::vector<Eigen::Vector2f> super_sample_step {
        {0.25, 0.25},
        {0.75, 0.25},
        {0.25, 0.75},
        {0.75, 0.75},
    };
    for (int i = min_p.x(); i <= max_p.x(); i++) {
        for (int j = min_p.y(); j <= max_p.y(); j++) {
            int cnt = 0;
            for (int k = 0; k < 4; k++) {
                if (insideTriangle((float)i + super_sample_step[k][0], (float)j + super_sample_step[k][1], t.v)) {
                    cnt++;
                }
            }
            if (cnt != 0) {
                auto[alpha, beta, gamma] = computeBarycentric2D(i, j, 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;
                if(z_interpolated < depth_buf[get_index(i, j)]){
                    set_pixel(Eigen::Vector3f((float)i, (float)j, z_interpolated), t.getColor() * cnt / 4.0);
                    depth_buf[get_index(i, j)] = z_interpolated;
                }

            }
        }
    }
}

运行此代码可以发现,确实做到了抗锯齿的效果,但是出现了不正常黑边。通过长时间的查阅资料以及分析才知道,因为黑边的出现是因为刚开始渲染绿色三角形的时候,在边缘时是用绿色跟黑色进行色彩平均的,在边缘时黑色像素点占比比较大,所以绿色三角形的边缘会出现比较暗淡的黑边,当渲染蓝色三角形时,由于蓝色三角形距离比较远,没有通过深度测试,所以最后看起来会是绿色三角形的边缘有黑边。
在这里插入图片描述

2、优化SMAA操作

其实也怪自己没有仔细阅读Assignment2.pdf中的内容,闫教授已经提示我们了对于像素内的每一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个 sample list。这样子的话最后在渲染蓝色三角形的时候就会与绿色三角形的黑边进行平均,使得色彩的过渡更加平滑。

首先对于每一个像素点都创建一个二维的vector矢量数组用于维护这个像素点的sample list,在rasterizer.hpp中创建相应的二维vector矢量数组:

bool MSAA = true;
std::vector<Eigen::Vector3f> frame_buf;
std::vector<std::vector<Eigen::Vector3f>> sample_list_frame_buf;

std::vector<float> depth_buf;
std::vector<std::vector<float>> sample_list_depth_buf;

其中frame_bufdepth_buf是原来框架中有的,sample_list_frame_bufsample_list_depth_buf是自己创建的维护数组。创建完成之后还需要更改rasterizer.cpp中对应的初始化函数rasterizer()和清理函数clear()

void rst::rasterizer::clear(rst::Buffers buff)
{
    if ((buff & rst::Buffers::Color) == rst::Buffers::Color)
    {
        std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{0, 0, 0});
        if (MSAA) {
            std::fill(sample_list_frame_buf.begin(), sample_list_frame_buf.end(), std::vector<Eigen::Vector3f>(4, {0, 0, 0}));
        }
    }
    if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)
    {
        std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());
        if (MSAA) {
            std::fill(sample_list_depth_buf.begin(), sample_list_depth_buf.end(), std::vector<float>(4, std::numeric_limits<float>::infinity()));
        }
    }
}

rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{
    frame_buf.resize(w * h);
    depth_buf.resize(w * h);
    if (MSAA) {
        sample_list_frame_buf.resize(w * h);
        for (auto& row : sample_list_frame_buf) {
            row.resize(4);
        }
        sample_list_depth_buf.resize(w * h);
        for (auto& row : sample_list_depth_buf) {
            row.resize(4);
        }
    }
}

最后再更改MSAA代码,使得每次判断四个点时都进行深度测试,更新sample_list_frame_bufsample_list_depth_buf矢量数组,最后再进行四个点的色彩平均,这样就可以实现较为平滑的边缘过渡。

    if (MSAA) {
        std::vector<Eigen::Vector2f> super_sample_step {
            {0.25, 0.25},
            {0.75, 0.25},
            {0.25, 0.75},
            {0.75, 0.75},
        };
        for (int i = min_p.x(); i <= max_p.x(); i++) {
            for (int j = min_p.y(); j <= max_p.y(); j++) {
                int cnt = 0;
                float minDepth = FLT_MAX;
                for (int k = 0; k < 4; k++) {
                    if (insideTriangle((float)i + super_sample_step[k][0], (float)j + super_sample_step[k][1], t.v)) {
                        auto[alpha, beta, gamma] = computeBarycentric2D(i, j, 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;
                        if (z_interpolated < minDepth) {
                            minDepth = z_interpolated;
                        }
                        if (z_interpolated < sample_list_depth_buf[get_index(i, j)][k]) {
                            sample_list_depth_buf[get_index(i, j)][k] = z_interpolated;
                            sample_list_frame_buf[get_index(i, j)][k] = t.getColor();
                        }
                        cnt++;
                    }
                }
                if (cnt != 0) {
                    Eigen::Vector3f color = {0, 0, 0};
                    for (int k = 0; k < 4; k++) {
                        color += sample_list_frame_buf[get_index(i, j)][k];
                    }
                    set_pixel(Eigen::Vector3f((float)i, (float)j, minDepth), color / 4.0);
                    depth_buf[get_index(i, j)] = minDepth;
                }
            }
        }
    }

运行后得到结果:
在这里插入图片描述
至此作业2完成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/20305.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

白嫖chatgpt的Edge插件,很难不爱啊

目录 &#x1f341;1.常见的Edge浏览器界面 &#x1f341;二.安装WebTab插件 &#x1f341;三.WebTab插件的各种功能 &#x1f341;1.支持免费的chatgpt&#xff0c;不限次数​编辑 &#x1f341;2.有几个休闲的小游戏可以玩耍&#xff0c;点击即玩。 &#x1f341;3.支…

618前夕,淘宝天猫大变革,探索电商天花板之上的价值

2023年淘宝天猫618商家大会&#xff0c;恰逢淘宝20周年&#xff0c;也是阿里“16N”组织架构改革&#xff0c;淘宝天猫“独立”经营后&#xff0c;管理和运营团队的首次亮相。除了淘宝天猫618的具体策略&#xff0c;最受关注的&#xff0c;还有淘宝天猫的大变革——涉及淘宝天猫…

AD9680+JESD204B接口+FPGA FMC高速率数据采集板卡

板卡概述&#xff1a; 【FMC_XM155】 FMC_XM155 是一款基于 VITA57.1 标准的&#xff0c;实现 2 路 14-bit、500MSPS/1GSPS/1.25GSPS 直流耦合 ADC 同步采集 FMC 子卡模 块。 该模块遵循 VITA57.1 规范&#xff0c;可直接与 FPGA 载卡配合使用&#xff0c;板 卡 ADC 器件采用…

CN学术期刊《西部素质教育》简介及投稿邮箱

《西部素质教育》&#xff08;半月刊&#xff09;创刊于2015年&#xff0c;是由青海人民出版社有限责任公司主管/主办的教育类学术期刊&#xff0c;本刊恪守“追踪教育研究前沿&#xff0c;关注教育实践热点&#xff0c;探索创新教育理念&#xff0c;传播教育教学信息&#xff…

Linux相关问题

中英文切换 super空格切换中英文&#xff1b;super指键盘上的Win键&#xff1b; 开机自启动服务设置 可视化方式&#xff1a;输入setup命令进入自启动服务配置&#xff1b;通过上下键选中服务&#xff0c;通过空格选择是否自启动该服务&#xff1b; 开启不同的终端 CTRLALT…

audioop.rms函数解读和代码例子

该audioop模块包含对声音片段的一些有用操作。它对由8,16或32位宽的有符号整数样本组成的声音片段进行操作&#xff0c;并以Python字符串存储。这与al和sunaudiodev模块使用的格式相同。所有标量项都是整数&#xff0c;除非另有规定。 audioop.rms 即 sqrt(sum(S_i^2)/n) 这个公…

10个你从未想过的 ChatGPT 有趣用途

这篇文章向我们展示了ChatGPT的有趣用途&#xff0c;如创作独特的故事、写作协助、模拟对话和游戏等。这些应用展示了ChatGPT的强大功能和灵活性。通过这些有趣的例子&#xff0c;我们可以看到ChatGPT作为一种人工智能技术在生活中的实际应用和潜力。无论是娱乐还是实用&#x…

我和C++的故事---第一次见面.

&#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石. &#x1f3e0;学习社区&#xff1a;夏目友人帐. 文章目录 前言一、第一个C程序二、C 关键字(C98)三、命名空间1、命名空间的定义2、命名空间的使用3、命名空间的三种展开方式 四、C输入&&输出&&换行1、…

三极管的几点应用

三极管有三个工作状态&#xff1a;截止、放大、饱和&#xff0c;放大状态很有学问也很复杂&#xff0c;多用于集成芯片&#xff0c;比如运放&#xff0c;现在不讨论。其实&#xff0c;对信号的放大&#xff0c;我们通常用运放处理&#xff0c;三极管更多的是当做一个开关管来使…

蚁群算法ACS处理旅行商问题TSP【Java实现】

1. 介绍 蚁群算法是一种群体智能算法&#xff0c;模拟了蚂蚁寻找食物时的行为&#xff0c;通过蚂蚁之间的信息交流和合作&#xff0c;最终实现全局最优解的寻找【是否找得到和迭代次数有关】。 蚁群算法的基本思想是将搜索空间看作一个由节点组成的图&#xff0c;每个节点代表…

【软件开发】Memcached(理论篇)

Memcached&#xff08;理论篇&#xff09; 1.Memcached 简介 Memcached 是一个开源的&#xff0c;支持高性能&#xff0c;高并发的分布式内存缓存系统&#xff0c;由 C 语言编写&#xff0c;总共 2000 多行代码。从软件名称上看&#xff0c;前 3 个字符 Mem 就是内存的意思&am…

港科夜闻|香港科大与香港科大(广州)管理层联席会议顺利召开

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与香港科大(广州)管理层联席会议顺利召开。这是自内地和香港全面恢复通关以来&#xff0c;两校的高级管理团队首次举行线下的联席会议&#xff0c;面对面交流、讨论有关两校协同发展的重要议题。两校持续深入推进…

「——全部文章专栏汇总——」

欢迎来到我的博客 天喜Studio 在这里&#xff0c;我会分享我在 c语言、操作系统、计算机网络等方面的学习和经验&#xff0c;希望能对读者有所帮助。以下是我写的所有专栏 如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 如有疑问欢迎大家指正讨论…

SQL注入(一)联合查询 报错注入

目录 1.sql注入漏洞是什么 2.联合查询&#xff1a; 2.1注入思想 2.2 了解information_schema 数据库及表 3.可替代information_schema的表 3.1 sys库中重要的表 4. 无列名注入 利用 join-using 注列名。 4. 报错注入 4.1 常用函数&#xff1a;updatexml、extractvalue…

C/C++每日一练(20230517) 排序问题、查找小值、寻找峰值

目录 1. 排序问题 &#x1f31f; 2. 查找小值 &#x1f31f; 3. 寻找峰值 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 排序问题 输入10个数&#…

【Linux学习笔记】设备驱动模型详解——总线、设备、驱动和类

学习内容 设备驱动模型视频讲解 简介 设备驱动是计算机系统中的重要组成部分&#xff0c;它们允许操作系统与硬件交互。设备驱动模型是一种通用的抽象框架&#xff0c;用于描述操作系统如何管理硬件设备。这里我们将介绍设备驱动模型中的四个关键概念&#xff1a;总线、设备…

条款1:理解模板类型推导

现代C中被广泛应用的auto是建立在模板类型推导的基础上的。而当模板类型推导规则应用于auto环境时&#xff0c;有时不如应用于模板中那么直观。由于这个原因&#xff0c;真正理解auto基于的模板类型推导的方方面面非常重要。 在c中声明一个模板函数的伪代码基本如下&#xff1…

六、IDEAJ同一个服务启动多台服务器的方法

目录 1、打开启动类配置窗口--->选择Edit Configurations进入配置窗口 2、从左侧Springboot应用选择需要启动的多台服务器&#xff08;服务只要启动一次就会在此窗口有显示&#xff09;--->勾选Allow parallel run菜单&#xff08;默认不勾选&#xff0c;则只能启动一台…

Springboot +Flowable,会签、或签简单使用(一)

一.简介 **会签&#xff1a;**在一个流程中的某一个 Task 上&#xff0c;这个 Task 需要多个用户审批&#xff0c;当多个用户全部审批通过&#xff0c;或者多个用户中的某几个用户审批通过&#xff0c;就算通过。 例如&#xff1a;之前的请假流程&#xff0c;假设这个请假流程…

板材激光切割机切割穿孔时注意的几个问题

激光切割设备广泛应用于钣金、五金制品、钢结构、汽车配件、广告、工艺品等行业&#xff0c;成为加工行业不可缺少的环节。在厚板加工中穿孔时间占很大比重&#xff0c;随着加工板材越来越厚&#xff0c;板材激光切割机切割穿孔也会相应地增加难度。 激光切割机两种常见的穿孔方…