【GAMES101】作业2学习总结

news2025/2/25 15:57:05

本系列博客为记录笔者在学习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.coloradmin.cn/o/520998.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

踩坑集锦之你真的明白Java类路径的含义吗?

踩坑集锦之你真的明白Java类路径的含义吗&#xff1f; 引言前置知识补充故事还要从程序启动讲起...C和Java的桥接类LauncherHelper主类是如何被加载的加餐: 如何利用jdk预留的口子&#xff0c;替换系统类加载器为我们自定义的类加载器 Launcher启动类的初始化启动类加载器类路径…

chatgpt最大的竞争对手-claude

介绍 Claude是Anthropic公司开发的AI聊天机器人&#xff0c;与ChatGPT类似&#xff0c;由OpenAI前副总裁创办。和虽然比不上GPT4&#xff0c;但在连续对话能力、写小说、编写代码、解释概念等方面表现出色。 Claude是Anthropic公司开发的大语言模型(LLM)&#xff0c;主要特点…

网络安全自学误区

一、怎么入门&#xff1f; 如果你把每周要学的内容精细化到这种程度&#xff0c;你还会担心学不会&#xff0c;入不了门吗&#xff0c;其实说到底就是学了两个月&#xff0c;但都是东学一下&#xff0c;西学一下&#xff0c;什么内容都是浅尝辄止&#xff0c;没有深入进去&…

navigation2导航包(ROS2)说明-Smac Planner

Smac Planner SmacPlanner 是 Nav2 Planner 的插件。它目前包括 3 个不同的插件&#xff1a; 1.SmacPlannerHybrid&#xff1a; 高度优化完全可重新配置的 Hybrid-A* 实现&#xff0c;支持 Dubin 和 Reeds-Shepp 模型&#xff08;腿足模型、ackermann 模型和汽车模型&#xff…

sed命令的应用

sed命令的应用 一、sed编辑器sed的工作流程&#xff1a;sed的命令格式于常用选项命令格式常用选项常用操作&#xff1a; 三、实际操作打印内容删除行替换行数内容插入内容字符位置互换 一、sed编辑器 sed是一种流编辑器&#xff0c;流编辑器会在编辑器处理数据之前基于预先提供…

实用的 iPhone 解锁:4Easysoft iPhone Unlocker中文

4Easysoft iPhone Unlocker 是一款Mac平台上的 iPhone 解锁工具&#xff0c;它可以帮助用户解锁 iPhone&#xff0c;删除密码、Touch ID 或 Face ID&#xff0c;以及绕过 iCloud 账户等限制。使用 4Easysoft iPhone Unlocker&#xff0c;用户可以轻松地解锁 iPhone&#xff0c;…

CentOS 8上安装MySQL数据库

CentOS 8上安装MySQL数据库 1、确定您的服务器系统版本和其他信息&#xff1a; cat /etc/os-release可以按照以下步骤操作&#xff1a; 1、更新系统包列表 sudo dnf update2、安装MySQL数据库&#xff1a; sudo dnf install mysql3、启动MySQL服务&#xff1a; sudo syste…

maven创建web工程(图文并茂)

maven的web工程 创建步骤&#xff1a; 1.创建普通的maven工程 ​ 参考&#xff1a;略 2.打成war包 ​ 说明&#xff1a;普通工程打成jar包。web工程打war包。 在pom.xml中书写如下内容&#xff1a; 3.在普通的maven工程上生成web文件夹存放静态页面 ​ 1&#xff09; …

一个00后的自述:不好好学习的我后悔了

普通人家的孩子不读书&#xff0c;以后你能做什么&#xff1f; 以下是一个00后的自述&#xff1a; 我是2000年出生的&#xff0c;父亲是建筑工人&#xff0c;母亲是农民&#xff0c;我就是一个普通人家的孩子。 小时候&#xff0c;其实我的学习成绩也是不错的&#xff0c;但…

软考A计划-真题-分类精讲汇总-第五章(信息安全)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

JavaWeb技术栈

一个网页是怎么运行的&#xff1f; 首先网页想要运行&#xff0c;需要有静态资源他们负责页面的展示&#xff0c;如果我们想要页面产生互动效果&#xff0c;我们需要动态资源进行逻辑处理。同时&#xff0c;我们还需要数据库来存取数据。 进入一个网站&#xff0c;浏览器向服…

物联网时代25大开源IoT框架

相当长一段时间以来&#xff0c;互联网一直被用来连接人类并简化生活&#xff0c;这是21世纪初的启示。今天&#xff0c;我们将讨论各种物联网框架 —— 现在&#xff0c;我们正在进入一个基于互联网技术的新世界&#xff0c;该世界不仅连接人&#xff0c;而且还连接事物。因此…

实现分布式团队协作一体化的方法与技巧

多年来&#xff0c;零工经济平台的迅速兴起通过将自由职业者与支持按任务付费的企业联系起来&#xff0c;创造了多样化的就业机会。然而&#xff0c;能够接受临时工作安排既是福音又是祸根。在亚太地区&#xff0c;84%的招聘经理将工作外包给自由职业者。这背后的一个明显动机是…

Java对象创建和内存分配

Java对象创建流程如下步骤 判断是否加载类 当Java虚拟机执行一条new指令时&#xff0c;首先会检查这个指令的参数是否能在常量池中定位到类的符号引用&#xff0c;并且检查该类是否被加载、验证、准备、解析和初始化过。如果没有则执行加载过程。 给对象分配内存 对象所需的大…

【云原生】K8s管理工具--Kubectl(一)

Kubectl管理 一、陈述式管理1、陈述式管理方式2、Kubernetes相关信息查看3、查看节点状态4、命名空间操作5、deployment/pod操作6、扩缩容7、增加删除label 二、声明式管理1、声明式管理方式2、查看资源配置清单3、解释资源配置清单4、修改资源配置清单并应用5、删除资源配置清…

高级篇十三、事务基础知识

第13章_事务基础知识 1、数据库事务概述 事务是数据库区别文件系统的重要特性之一&#xff0c;当我们有了事务会让数据库始终保持一致性&#xff0c;同时我们还能通过事务的机制恢复到某个时间点&#xff0c;这样可以保证已提交到数据库的修改不会因为系统的崩溃而丢失&#…

网络通信-路由交换基础

目录 一、一个简单网络通信&#xff08;1v1&#xff09; 二、通信介质 三、交换机通信原理&#xff08;3v3、5v5&#xff09; 广播的概念 交换机转发消息时&#xff0c;怎么知道该发给哪个设备 四、路由器通信原理&#xff08;500v500、5000v5000&#xff09; 五、消息分…

App Inventor 2 算法之 - 二分算法(Binary Search)实现,快速查找定位

应用介绍 二分算法&#xff08;Binary Search&#xff09;是生活中非常常用的折半算法&#xff0c;能解决快速查找、快速定位的问题&#xff0c;主要用到数学和逻辑代码块。 本示例程序演示了采用普通遍历的方式和二分的方式分别需要几次能够猜中随机给出的数字。 二分算法&a…

k8s-CKS真题-故障排查Sysdig falco

目录 题目环境搭建安装sysdig创建容器创建目录、文件 解题 - sysdig解题 - falco错误模拟环境参考 题目 Task&#xff1a; 使用运行时检测工具来检测 Pod tomcat123 单个容器中频发生成和执行的异常进程。 有两种工具可供使用&#xff1a;sysdigfalco注&#xff1a;这些工具只…

如何有效提高企业优秀人才的留存率?

对于企业而言&#xff0c;有效的员工入职流程应是一个持续的项目。优秀人才使企业持续性发展的基石&#xff0c;那么该如何提高企业优秀人才的留存率呢&#xff1f; 有调查显示新员工入职后短时间内离职的比率日益上升&#xff0c;因此做好员工入职&#xff0c;短时间内增加员工…