作业任务:

填写并调用函数 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(): 测试点是否在三角形内。你可以修改此函

    数的定义,这意味着,你可以按照自己的方式更新返回类型或函数参数。

开始实现:

在进行光栅化的时候,我们需要判断某个点是否在triangle内,来决定是否着色。

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

  1. x:表示观测点的x
  2. y:表示观测点的y
  3. _v:是一个vector<Vector3f>,内含三角形的三个点,每个点存有xyz
  4. static bool insideTriangle(double x, double y, const Vector3f* _v)
  5. {
  6. Eigen::Vector2f p; //检测目标
  7. p << x, y;
  8. //.head(2)指这个点的前两个数值,即x,y
  9. Eigen::Vector2f a, b, c; //被检测的三角形三边向量
  10. a = _v[0].head(2) - _v[1].head(2); //a = A - B 即B->A
  11. b = _v[1].head(2) - _v[2].head(2); //b = B - C 即C->B
  12. c = _v[2].head(2) - _v[0].head(2); //c = C - A 即A->C
  13. Eigen::Vector2f AP, BP, CP;
  14. AP = p - _v[0].head(2);
  15. BP = p - _v[1].head(2);
  16. CP = p - _v[2].head(2);
  17. //由于我这里的向量方向都是逆时针的,所以如果点p在内,那么所有边向量叉乘对应的XP都应该为正值,指向z的正半轴。
  18. return AP[0] * c[1] - AP[1] * c[0] > 0 && BP[0] * a[1] - BP[1] * a[0] > 0 && CP[0] * b[1] - CP[1] * b[0] > 0;
  19. }

现在来实现rasterize_triangle(const Triangle& t)

分析:
  • 第一步 确定bounding box

    给定一个三角形,我们先要扫描出这个三角占据了哪些格子(像素单元),即定义bounding box,取得这个三角形的x和y轴上的边界值,使得这个bounding box尽可能的小,但又必须包裹住这个三角形。

  • 第二步 扫描bounding box

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

这里很容易就能想到,如果这个单元尺寸越小,就能更加精确的确定边界。也就是MSAA,多重采样抗锯齿,这个并不是从物理上将单元切割成更小的,而是将一个单元看成多个单元组合,然后对这个更小单元进行采样。每个单元的尺寸越小,成像的边界锯齿效果就越虚,即成像效果越好,但这样会使效率降低,将一个单元切割成2x2的四个小单元,会使原来一个单位的工作量升至四个单位的工作量。

  1. //Screen space rasterization
  2. void rst::rasterizer::rasterize_triangle(const Triangle& t) {
  3. auto v = t.toVector4();//这里是把三角形结构体换成三个顶点如(x1,y1,z1,1)
  4. //还记得嘛?最后的1表示它是一个点,用于齐次坐标
  5. //bounding box
  6. float min_x = std::min(v[0][0], std::min(v[1][0], v[2][0]));
  7. float max_x = std::max(v[0][0], std::max(v[1][0], v[2][0]));
  8. float min_y = std::min(v[0][1], std::min(v[1][1], v[2][1]));
  9. float max_y = std::max(v[0][1], std::max(v[1][1], v[2][1]));
  10. min_x = static_cast<int>(std::floor(min_x));
  11. min_y = static_cast<int>(std::floor(min_y));
  12. max_x = static_cast<int>(std::ceil(max_x));
  13. max_y = static_cast<int>(std::ceil(max_y));
  14. //左边界小数部分全部直接舍,右边界小数部分直接入,确保单元边界坐标都是整数,三角形一定在bounding box内。
  15. bool MSAA = true;//MSAA是否启用
  16. if (MSAA)
  17. {
  18. std::vector<Eigen::Vector2f> pos
  19. { //对一个像素分割四份 当然你还可以分成4x4 8x8等等甚至你还可以为了某种特殊情况设计成不规则的图形来分割单元
  20. {0.25,0.25}, //左下
  21. {0.75,0.25}, //右下
  22. {0.25,0.75}, //左上
  23. {0.75,0.75} //右上
  24. };
  25. for (int i = min_x; i <= max_x; ++i)
  26. {
  27. for (int j = min_y; j <= max_y; ++j)
  28. {
  29. int count = 0;
  30. float minDepth = FLT_MAX;
  31. for (int MSAA_4 = 0; MSAA_4 < 4; ++MSAA_4)
  32. {
  33. if (insideTriangle(static_cast<float>(i+pos[MSAA_4][0]), static_cast<float>(j+pos[MSAA_4][1]),t.v))
  34. {
  35. auto[alpha, beta, gamma] = computeBarycentric2D(static_cast<float>(i + pos[MSAA_4][0]), static_cast<float>(j + pos[MSAA_4][1]), t.v);
  36. float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
  37. float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
  38. z_interpolated *= w_reciprocal;
  39. minDepth = std::min(minDepth, z_interpolated);
  40. ++count;
  41. }
  42. }
  43. if (count)
  44. {
  45. if (depth_buf[get_index(i, j)] > minDepth)
  46. {
  47. depth_buf[get_index(i, j)] = minDepth;//更新深度
  48. Eigen::Vector3f color = t.getColor() * (count / 4.0);//对颜色进行平均,使得边界更平滑,也是一种模糊的手段
  49. Eigen::Vector3f point;
  50. point << static_cast<float>(i), static_cast<float>(j), minDepth;
  51. set_pixel(point, color);//设置颜色
  52. }
  53. }
  54. }
  55. }
  56. }
  57. else
  58. {
  59. for (int i = min_x; i <= max_x; ++i)
  60. {
  61. for (int j = min_y; j <= max_y; ++j)
  62. {
  63. if (insideTriangle(static_cast<float>(i+0.5), static_cast<float>(j+0.5),t.v))
  64. {
  65. auto [alpha, beta, gamma] = computeBarycentric2D(static_cast<float>(i + 0.5), static_cast<float>(j + 0.5), t.v);
  66. float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
  67. float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
  68. z_interpolated *= w_reciprocal;
  69. if (depth_buf[get_index(i, j)] > z_interpolated)
  70. {
  71. depth_buf[get_index(i, j)] = z_interpolated;//更新深度
  72. Eigen::Vector3f color = t.getColor();
  73. Eigen::Vector3f point;
  74. point << static_cast<float>(i), static_cast<float>(j), z_interpolated;
  75. set_pixel(point, color);//设置颜色
  76. }
  77. }
  78. }
  79. }
  80. }
  81. }

根据三角形三点求得观测点的z插值,此时视频还没有讲解,是直接使用的框架中的代码。


MSAA = true


MSAA = false


细节对比



还是比较明显的吧!

GAMES101作业2的更多相关文章

  1. 【UE4】GAMES101 图形学作业2:光栅化和深度缓存

    总览 在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣.所以这一次我们继续推进一步--在屏幕上画出一个实心三角形,换言之,栅格化一个三角形.上一次作业中,在视口变化之后,我 ...

  2. 【UE4】GAMES101 图形学作业5:光线与物体相交(球、三角面)

    总览 在这部分的课程中,我们将专注于使用光线追踪来渲染图像.在光线追踪中最重要的操作之一就是找到光线与物体的交点.一旦找到光线与物体的交点,就可以执行着色并返回像素颜色. 在这次作业中,我们要实现两个 ...

  3. 【UE4】GAMES101 图形学作业4:贝塞尔曲线

    总览 Bézier 曲线是一种用于计算机图形学的参数曲线. 在本次作业中,你需要实现de Casteljau 算法来绘制由4 个控制点表示的Bézier 曲线(当你正确实现该算法时,你可以支持绘制由更 ...

  4. 【UE4】GAMES101 图形学作业3:Blinn-Phong 模型与着色

    总览 在这次编程任务中,我们会进一步模拟现代图形技术.我们在代码中添加了Object Loader(用于加载三维模型), Vertex Shader 与Fragment Shader,并且支持了纹理映 ...

  5. 【UE4】GAMES101 图形学作业1:mvp 模型、视图、投影变换

    总览 到目前为止,我们已经学习了如何使用矩阵变换来排列二维或三维空间中的对象.所以现在是时候通过实现一些简单的变换矩阵来获得一些实际经验了.在接下来的三次作业中,我们将要求你去模拟一个基于CPU 的光 ...

  6. 【UE4】GAMES101 图形学作业0:矩阵初识

    作业描述 给定一个点P=(2,1), 将该点绕原点先逆时针旋转45◦,再平移(1,2), 计算出变换后点的坐标(要求用齐次坐标进行计算). UE4 知识点 主要矩阵 FMatrix FBasisVec ...

  7. python10作业思路及源码:类Fabric主机管理程序开发(仅供参考)

    类Fabric主机管理程序开发 一,作业要求 1, 运行程序列出主机组或者主机列表(已完成) 2,选择指定主机或主机组(已完成) 3,选择主机或主机组传送文件(上传/下载)(已完成) 4,充分使用多线 ...

  8. SQLServer2005创建定时作业任务

    SQLServer定时作业任务:即数据库自动按照定时执行的作业任务,具有周期性不需要人工干预的特点 创建步骤:(使用最高权限的账户登录--sa) 一.启动SQL Server代理(SQL Server ...

  9. 使用T-SQL找出执行时间过长的作业

        有些时候,有些作业遇到问题执行时间过长,因此我写了一个脚本可以根据历史记录,找出执行时间过长的作业,在监控中就可以及时发现这些作业并尽早解决,代码如下:   SELECT sj.name , ...

随机推荐

  1. PWA & TWA

    PWA & TWA https://www.bilibili.com/video/av68082979/ Service Worker workbox.js https://developer ...

  2. Inspect Network Activity In Chrome DevTools

    Inspect Network Activity In Chrome DevTools https://developers.google.com/web/tools/chrome-devtools/ ...

  3. 蓝牙鼠标 & 罗技 M337

    蓝牙鼠标 & 罗技 M337 蓝牙鼠标,有哪些不需要适配器的 https://www.logitech.com.cn/zh-cn/product/bluetooth-mouse-m337 ht ...

  4. nasm astrcat函数 x86

    xxx.asm %define p1 ebp+8 %define p2 ebp+12 %define p3 ebp+16 section .text global dllmain export ast ...

  5. 揭秘高倍矿币 Baccarat BGV,为何NGK DeFi的财富效应如此神奇?

    作为区块链4.0代表的NGK公链,这次也将借助它自己的DeFi版块NGK Baccarat,开启属于它自己的千倍财富之旅. 如果说,比特币能让没有银行账户的人,可以在全球任何时间.地点都能自由进行交易 ...

  6. JDBC概念理解

    ##JDBC: 概念:Java DataBase Connectivity  Java 数据库连接  Java语言操作数据库 JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则 ...

  7. 小白养成记——MySQL中的排名函数

    1.ROW_NUMBER() 函数 依次排序,没有并列名次.如 SELECT st.ID '学号', st.`NAME` '姓名', sc.SCORE '成绩', ROW_NUMBER() OVER( ...

  8. 使用 xunit 编写测试代码

    使用 xunit 编写测试代码 Intro xunit 是 .NET 里使用非常广泛的一个测试框架,有很多测试项目都是在使用 xunit 作为测试框架,不仅仅有很多开源项目在使用,很多微软的项目也在使 ...

  9. DS线段树优化最短路&&01bfs浅谈

    1简介 为什么需要?原因很简单,当需要有大量的边去连时,用线段树优化可以直接用点连向区间,或从区间连向点,或从区间连向区间,如果普通连边,复杂度是不可比拟的.下面简单讲解一下线段树(ST)优化建图. ...

  10. go 在crontab里面运行报错 解决方案

    问题背景 你高高兴兴的写好了一个go脚本,放到你的服务器上,打算定期运行这个脚本,你打开crontab -e, 然后输入: */1 * * * * go run /root/test/main.go ...