avatar

Catalog
GAMES101 - Assignment2

虽然老师说作业2比作业1简单,但是我觉得作业2比较难。

主要作业2的各个变量和主体框架比较难懂,需要花比较多的时间阅读、弄懂;

两个函数实现的时候一拿到手会感觉比较难以下手,所以我是参考着别人的代码写出来的;

还有实现MSAA的时候,会遇到黑线问题,需要自己跟着代码走一遍渲染过程才能知道怎么去掉黑线。

Assignment

实现rasterizer.cpp中的两个函数:

  • static bool insideTriangle(): 测试点是否在三角形内。你可以修改此函数的定义,这意味着,你可以按照自己的方式更新返回类型或函数参数。

  • rasterize_triangle(): 执行三角形栅格化算法

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

    1. 创建三角形的2 维bounding box。
    2. 遍历此bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。
    3. 如果在内部,则将其位置处的插值深度值(interpolated depth value) 与深度缓冲区(depth buffer) 中的相应值进行比较。
    4. 如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区(depth buffer)。

提高项:

用super-sampling 处理Anti-aliasing :

你可能会注意到,当我们放大图像时,图像边缘会有锯齿感。我们可以用super-sampling来解决这个问题,即对每个像素进行2 * 2 采样,并比较前后的结果(这里并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个sample list。最后,如果你实现正确的话,你得到的三角形不应该有不正常的黑边。

为了方便同学们写代码,我们将z 进行了反转,保证都是正数,并且越大表示离视点越远。

Tips about C++

帮助理解作业框架的一些C++知识点

迭代器std::begin()std::end()

  • 迭代器是一个行为类似于指针的模板类对象。只需要迭代器iter指向一个有效对象,就可以通过使用*iter解引用的方式来获取一个对象的引用。

  • 通常会使用一对迭代器来定义一段元素(任意支持迭代器对象的元素)。一段元素通过起始迭代器指向第一个元素,通过结束迭代器指向最后一个元素的后一个位置的元素序列。一般使用std::begin()和std::end()来获取容器的迭代器。

    c++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include < numeric >
    #include < iostream >
    #include < iterator >
    using namespace std;
    int main(){
    double data[] {2.5,4.5,6.5,5.5,8.5};
    cout<<"The array contains: \n";
    for(auto iter = std::begin(data) ;iter != std::end(data) ; ++iter){
    cout << *iter << " ";
    }
    double total = std::accumulate(std::begin(data),std::end(data),0.0); //std::accumulate()表示计算std::begin()到std::end()之间的元素的总和,并且起始值设置为0
    cout <<"\n"<< total << endl;
    return 0;
    }

std::transform

在指定的范围内应用于给定的操作,并将结果存储在指定的另一个范围内。要使用std::transform函数需要包含头文件。

声明如下:

c++
1
2
3
4
5
6
7
8
9
template <class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator transform (InputIterator first1, InputIterator last1,
OutputIterator result, UnaryOperation op);

template <class InputIterator1, class InputIterator2,
class OutputIterator, class BinaryOperation>
OutputIterator transform (InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, OutputIterator result,
BinaryOperation binary_op);
  1. 一元操作

    c++
    1
    2
    3
    4
    5
    //op的一个实现,将[first1, last1)范围内的每个元素加5,然后依次存储到result中
    int op_increase(int i) {return (i + 5)};

    //调用std::transform的方式如下
    std::transform(first1, last1, result, op_increase);
  2. 二元操作

    c++
    1
    2
    3
    4
    5
    //binary_op的一个实现,将first1和first2开头的范围内的每个元素相加,然后依次存储到result中
    int op_add(int, a, int b) {return (a + b)};

    //调用std::transform的方式如下
    std::transform(first1, last1, first2, result, op_add);
  • std::transform支持in place,即result和first1指向的位置可以是相同的。std::transform的主要作用应该就是省去了我们自己写for循环实现。

Eigen的vector取头尾或切片

这里的vector指的是Vector3d这种或者Matrix这种的列向量

获取向量的前n个元素:vector.head(n);
获取向量尾部的n个元素:vector.tail(n);
获取从向量的第i个元素开始的n个元素:vector.segment(i,n);

std::vector

须添加头文件

c++
1
2
3
std::vector<int> nVec;     // 空对象
std::vector<int> nVec(5,-1);  // 创建了一个包含5个元素且值为-1的vector
std::vector<std::string> strVec{"a", "b", "c"};  // 列表初始化

要注意“()”和“{}”这样的初始化情况,比如:

c++
1
2
3
std::vector<int> nVec(101);    // 包含10个元素,且值为1 

std::vector<int> nVec{101};    // 包含2个元素,值分别为10,1

然而,一般在程序中,并不会知道vector的元素个数,故使用以上方式倒显得繁琐,所以可以使用push_back,它会负责将一个值当成vector对象的尾元素“压到(push)”vector对象的“尾端(back)”。比如:

c++
1
2
3
4
5
std::vector<int> nVec;
for(int i = 0; i < 5; ++i)
  nVec.push_back(i);    // 压入元素
for(size_t i = 0; i < nVec.size(); ++i)
  std::cout << nVec[i] << std::endl; // 输出元素

其中size()是获取vector元素的个数,另外vector中可使用empty()来返回vector中是否存在元素,如果为空,则返回true,否则返回false。同时,针对nVec[i]是通过下标运算符来获取对应的vector数值的,千万注意,针对于空的vector,万不可通过下标运算符来添加元素,比如:

c++
1
2
3
std::vector<int> nVec;
for(int i = 0; i < 5; ++i)
  nVec[i] = i;    // error

这样编写代码是错误的,nVec是空的,不包含任何对象。当然也就不可能通过下标来添加或访问任何元素。若要添加请使用push_back

当然,针对于输出,可使用迭代器iterator来表示,比如上面的例子可写成:

c++
1
2
3
std::vector<int>::iterator itr = nVec.begin();
for(; itr != nVec.end(); ++itr)
  std::cout << (*itr) << std::endl;

针对于iterator有两种标准库类型:iteratorconst_iterator

两者的区别主要是后者类似于常量指针,只能读取不能修改。如果vector对象不是常量,两者均可使用。

更多可见:https://blog.csdn.net/tpriwwq/article/details/80609371

std::array

简介

C++有一种大小固定的特殊容器std::array,这个容器在头文件中定义。它基本上是对C风格的数组进行了简单包装。

用std::array替代C风格的数组会带来很多好处。它总是知道自身的大小;不会自动转换为指针,从而避免了某些类型的bug;具有迭代器,可以方便地遍历元素。

array 容器的用法

array 第一个参数表示数组中元素的类型,第二个表示数组的大小。

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include < iostream >

#include < array>

using namespace std;

int main()

{undefined
array<int, 3> arr = {9,8,7};

cout <<"Array size = "<< arr.size() << endl;

cout << "Element 2 = "<1] << endl;

return 0;

}

注意:C风格的数组和std::array 都具有固定的大小,在编译时必须知道这一点。在运行时数组不会增大或缩小。

如果希望数组的大小是动态的,推荐使用std::Vector 。在vector中添加新元素时,vector 会自动增加其大小。

函数实现

rasterize_triangle

c++
1
2
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();

看框架代码,Triangle对象中有public变量:

c++
1
Vector3f v[3]; /*the original coordinates of the triangle, v0, v1, v2 in counter clockwise order*/

表示逆时针的三个三角形顶点。

toVector4()函数定义:

c++
1
2
3
4
5
6
    std::array< Vector4f, 3> Triangle::toVector4() const
{
std::array< Eigen::Vector4f, 3> res;
std::transform(std::begin(v), std::end(v), res.begin(), [](auto& vec) { return Eigen::Vector4f(vec.x(), vec.y(), vec.z(), 1.f); });
return res;
}

所以v是三角形三个顶点的齐次坐标

创建三角形的2维bounding box

思路

现在有三个顶点的坐标,所以可以根据顶点坐标,得到x和y的min和max。

注意!!比较容易忽略的点:由于x、y的min和max一般是小数,所以我们要向上或向下取整。

实现

std::min求最小值:比较三个顶点的x、y,所以可以用嵌套的std::min实现。std::max同理。

std::floor&std::ceil:向下取整,向上取整。

c
1
2
3
4
5
6
7
8
9
   float x_min = std::min(v[0][0], std::min(v[1][0], v[2][0]));
float x_max = std::max(v[0][0], std::max(v[1][0], v[2][0]));
float y_min = std::min(v[0][1], std::min(v[1][1], v[2][1]));
float y_max = std::max(v[0][1], std::max(v[1][1], v[2][1]));

x_min = std::floor(x_min);
x_max = std::ceil(x_max);
y_min = std::floor(y_min);
y_max = std::ceil(y_max);

上面四个变量围成的矩形就是bounding box。

遍历像素,检查中心点是否在三角形内

遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。

遍历

直接用bounding box 的四个变量,嵌套两层for循环即可

insideTriangle()

可以联想到之前学的叉乘

  • 首先用减法得到从三角形三个顶点出发的六个向量。

  • 已知顶点数据逆时针分布,可以知道如果点在三角形内部,那么向量叉乘的结果应是全都指向自己。这个指向如何表示呢?写代码的时候困扰了我很久,后来才想明白,因为函数中的Triangle t是经过MVP变换后的t(从main函数可以看出来),光栅化时,创建bounding box 和 检查点是否在三角形 都不考虑z值,把三角形推在一个平面上(个人理解),在这样的前提下算叉乘,就是相当于计算平面上两个二维向量(x1, y1(x2, y2)的叉乘(z=0的两个三维向量),最终计算出来的叉积根据公式可得x1y2-x2y1,那么叉积的坐标表示(0, 0, x1y2-x2y1),根据x1y2-x2y1>0或是<0,可以知道z时指向自己还是指向对面。

  • 一开始我想着用Eigen中的cross函数,但是它返回的是一个向量,比较繁琐(并且考虑了z值(但其实根据三维向量计算的公式,得到的向量的z值和二维的去算的值大小一样的))。

所以,总结来说就是先用减法得到六个向量,然后再用公式算出两两叉乘的z值,从而判断方向,知道点是否在三角形内。

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static bool insideTriangle(float x, float y, const Vector3f* _v)
{
Vector2f P;
P << x , y;
// TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
Vector2f v01 = _v[1].head(2) - _v[0].head(2);
Vector2f v12 = _v[2].head(2) - _v[1].head(2);
Vector2f v20 = _v[0].head(2) - _v[2].head(2);

Vector2f _0p = P - _v[0].head(2);
Vector2f _1p = P - _v[1].head(2);
Vector2f _2p = P - _v[2].head(2);

float z1 = v01[0] * _0p[1] - v01[1] * _0p[0];
float z2 = v12[0] * _1p[1] - v12[1] * _1p[0];
float z3 = v20[0] * _2p[1] - v20[1] * _2p[0];

return z1 > 0 && z2 > 0 && z3 > 0;
}

Z-buffer

  1. 如果在内部,则将其位置处的插值深度值(interpolated depth value) 与深度缓冲区(depth buffer) 中的相应值进行比较。
  2. 如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区(depth buffer)。

到了这一步其实已经很简单了,只要了解一下框架中的get_index(x,y),getColor(),set_pixel(point, color)这三个函数怎么用的就可以了。(刚开始做的时候没看懂框架不知道要用这些函数,看了别人的作业才知道的)

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
if (insideTriangle(x + 0.5, y + 0.5, t.v))
{
//get depth
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;
//z-buffer & color
if (z_interpolated < depth_buf[get_index(x,y)])
{
Vector3f color = t.getColor();
depth_buf[get_index(x,y)] = z_interpolated;
Vector3f point(3);
point << (float)x, (float)y, z_interpolated;
set_pixel(point, color);
}
}
}
}

MSAA

然后我们的重头戏——MSAA,来了!

用super-sampling 处理Anti-aliasing :

你可能会注意到,当我们放大图像时,图像边缘会有锯齿感。我们可以用super-sampling来解决这个问题,即对每个像素进行2 * 2 采样,并比较前后的结果(这里并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个sample list。最后,如果你实现正确的话,你得到的三角形不应该有不正常的黑边。

Common Setting

  • 首先,我们可以设定一个bool变量MSAA,用它和if-else的结构来决定我们用已经完成的普通方式还是MSAA的方式光栅化三角形。(和别人代码学的)

  • 如何表示一个像素四个采样点的坐标?用std::vecor√(也是和别人代码学的)

    c++
    1
    2
    3
    4
    5
    6
    std::vector pos{
    {0.25, 0.25},
    {0.75, 0.25},
    {0.25, 0.75},
    {0.75, 0.75},
    };

    嵌套0~3的i循环,采样点坐标表示为(x+pos[i][0], y+pos[i][1])

MSAA code

通过学习别人代码,了解到有三种MSAA的实操方法:

minDepth & count

MSAA 4X 深度:把一个点看成一个格子,判断里面4个小点是否落在三角形内,然后找到其中插值的最小z值,和帧缓冲中的z值进行比较替换即可。
MSAA 4X 颜色:判断有4个小点中有几个小点落入三角形,然后按比例对颜色进行采样即可。

c++
1
2
3
4
// 记录最小深度
float minDepth = FLT_MAX;
// 四个小点中落入三角形中的点的个数
int count = 0;

循环一个像素得到四个采样点的最小深度和count数值,然后:

c++
1
2
3
4
5
6
7
8
9
10
11
12
if (count != 0) {
if (depth_buf[get_index(x, y)] > minDepth) {
Vector3f color = t.getColor() * count / 4.0;
Vector3f point(3);
point << (float)x, (float)y, minDepth;
// 替换深度
depth_buf[get_index(x, y)] = minDepth;
// 修改颜色
set_pixel(point, color);
}
}
}

https://blog.csdn.net/qq_36242312/article/details/105758619

结果:尝试画三角形可以发现,是先画绿三角形再画蓝色三角形,由于这个方法对像素点颜色的设计仅是简单的t.getColor() * count / 4.0,绿色三角形的边缘处像素只会是绿色和黑色的混合颜色,所以绿色三角形和蓝色三角形交界处会有一条黑线。更详细的有图分析:

https://blog.csdn.net/qq_41030550/article/details/104709523

如果想要没有黑线,也就是需要蓝绿三角形交界处是蓝绿色,那么我们就不能简单地对一个像素用它采样点的最小深度值作为它的深度值获取颜色,我们需要的是,一个像素的四个采样点有蓝色有绿色,也就是说,我们需要对每个采样点的深度值进行分析获取颜色。

更直白一点,就是画完绿色三角形后,我们需要可以把绿色三角形边缘像素的黑色采样点画成蓝色,由于蓝色三角形在绿色三角形后面,所以对于深度值的分析,必须是以采样点为单位,所以——我们的深度缓存要扩展为原来的4倍大小。这样,似乎也需要同样大小的空间储存采样点颜色。(有点增加采样率的感觉,只不过四个采样点必须同一个颜色)

4倍的depth_buf,4倍的color_buf

rasterzier.hpp新增如下代码:

c++
1
std::vector< Eigen::Vector3f> color_buf;

rasterzier.cpp改为:

c++
1
2
3
4
5
6
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{
frame_buf.resize(w * h);
depth_buf.resize(w * h * 4);
color_buf.resize(w* h * 4);
}

后面的个人觉得原作者写的不够好,虽然结果是对的,但是它每判断一个采样点,就要计算一次颜色,设置一次像素,个人觉得这些操作以像素为单位更好,原博客:

https://blog.csdn.net/weixin_43399489/article/details/120995939?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-5.pc_relevant_paycolumn_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-5.pc_relevant_paycolumn_v3&utm_relevant_index=9

以下是我自己根据这个方法改进的代码,算是结合了前两个dalao的代码:

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.
bool MSAA = true;
if (MSAA)
{
//4 samples
std::vector pos{
{0.25, 0.25},
{0.75, 0.25},
{0.25, 0.75},
{0.75, 0.75},
};
//initialize color_buf, 0 means black
//std::fill(color_buf.begin(), color_buf.end(), Eigen::Vector3f{0, 0, 0}); why?
//for all samples
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
//record min depth
float minDepth = FLT_MAX;
//have sample in triangle
bool flag = 0;

for(int i = 0; i<4;i++){

if(insideTriangle(x+pos[i][0], y+pos[i][1], t.v)){

//get depth
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;

//update minDepth
if(z_interpolated < depth_buf[get_index(x, y)*4 + i]){
depth_buf[get_index(x, y)*4+i] = z_interpolated;
color_buf[get_index(x, y)*4 + i] = t.getColor();
flag = 1;
}

}
}
if(flag){
Vector3f color(0.0f, 0.0f, 0.0f);
for(int i = 0; i<4; i++){
color += color_buf[get_index(x, y)*4 + i];
if(depth_buf[get_index(x, y)*4 + i] < minDepth)
{
minDepth = depth_buf[get_index(x, y)*4 + i];
}
}
color = color/4.0f;
set_pixel(Vector3f(x, y, minDepth), color);
}
}
}
}

这样光栅化出来的三角形就有漂亮的蓝绿像素边啦o( ̄▽ ̄)ブ~

不过我没想明白,为什么当我照葫芦画瓢对color_buf初始化std::fill(color_buf.begin(), color_buf.end(), Eigen::Vector3f{0, 0, 0});之后,得到的结果反而不对 = =

我不初始化反而成功了

很怪,留给更大佬的自己破案(•_•)

这段代码属于是看起来简单,实际上自己写起来磕磕绊绊地才得到自己想要的结果。

对本次编写代码帮助最大的一点是,假设有一个 3绿采样点&1蓝色采样点 的像素,假设按顺序(上到下,左到右)绿色采样点的深度分别为123,被覆盖的蓝色采样点深度是2345。

按照自己写的代码顺序执行,画出各个变量的值、color_buf还有depth_buf的具体存储情况,比如,先画绿色三角形时,depth_buf: | 1 | 2 | ∞ | 3 |,color_buf:| G | G | 0 | G |

再画蓝色三角形,按照自己写的代码,得到结果是,depth_buf: | 1 | 2 | 4 | 3 |,color_buf:| G | G | B | G |,就是正确的结果,如果不是,那么也可以比较容易发现代码哪里的逻辑有问题√。

Only 4 倍的depth_buf(no color_buf)

因为我又看到有bigOld写的代码没有用到color_buf,刚好第一个方法的博客下面评论说把颜色公式改为t.getColor()*count/4.0f + frame_buf[get_index(x,y)] /4.0f可以消除黑边(实践过了,没用,因为必须有4倍的深度缓存才能实现蓝绿混合,不过这给了我一点提示)。我开始思考不用颜色缓存的可能性。

https://blog.csdn.net/qq_41030550/article/details/104709523

在本题的情况下,观察(set_pixel函数)可以知道框架有一个frame_buf,储存的是每个像素我们说好要在屏幕上画的颜色。那么当我们画蓝色三角形的时候,是否可以尝试利用frame_buf中的值知道交界处像素点绿色值,再根据蓝色采样点的情况,算出像素的混合值呢?

假设现在我们知道交界处的蓝色采样点有x个,那么有(4-x)个绿色采样点,这个像素的颜色值就应该是(x*B + (4-x)*G) / 4;又知道frame_buf中该点之前值为((4-x)*G)/4,所以计算color的公式应该是frame_buf(get_index(x, y)) + x*B/4

我们可以用1中的count变量得到x,代码实现如下:

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
bool MSAA = true;
if (MSAA)
{
//4 samples
std::vector pos{
{0.25, 0.25},
{0.75, 0.25},
{0.25, 0.75},
{0.75, 0.75},
};
//initialize color_buf, 0 means black
//std::fill(color_buf.begin(), color_buf.end(), Eigen::Vector3f{0, 0, 0}); why?

//for all samples
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
//record min depth
float minDepth = FLT_MAX;
//have sample in triangle
//bool flag = 0;
int count = 0;

for(int i = 0; i<4;i++){

if(insideTriangle(x+pos[i][0], y+pos[i][1], t.v)){

//get depth
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;

//update minDepth
if(z_interpolated < depth_buf[get_index(x, y)*4 + i]){
depth_buf[get_index(x, y)*4+i] = z_interpolated;
//color_buf[get_index(x, y)*4 + i] = t.getColor();
//flag = 1;
count++;
}
//minDepth = std::min(minDepth, z_interpolated);
}
}
if(count){
//calculate color
Vector3f color = t.getColor()*count/4.0f + frame_buf[get_index(x,y)] ;
for(int i = 0; i<4; i++){
if(depth_buf[get_index(x, y)*4 + i] < minDepth)
{
minDepth = depth_buf[get_index(x, y)*4 + i];
}
}
//color = color/4.0f;
set_pixel(Vector3f(x, y, minDepth), color);
}
}
}
}

Total Code

*4 depth_buf, *4 color_buf

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//Screen space rasterization
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();

// TODO : Find out the bounding box of current triangle.

float x_min = std::min(v[0][0], std::min(v[1][0], v[2][0]));
float x_max = std::max(v[0][0], std::max(v[1][0], v[2][0]));
float y_min = std::min(v[0][1], std::min(v[1][1], v[2][1]));
float y_max = std::max(v[0][1], std::max(v[1][1], v[2][1]));

x_min = std::floor(x_min);
x_max = std::ceil(x_max);
y_min = std::floor(y_min);
y_max = std::ceil(y_max);

// 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;
//采样点坐标(i+0.5 , j+0.5 )

// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.
bool MSAA = true;
if (MSAA)
{
//4 samples
std::vector pos{
{0.25, 0.25},
{0.75, 0.25},
{0.25, 0.75},
{0.75, 0.75},
};
//initialize color_buf, 0 means black
//std::fill(color_buf.begin(), color_buf.end(), Eigen::Vector3f{0, 0, 0}); why?

//for all samples
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
//record min depth
float minDepth = FLT_MAX;
//have sample in triangle
bool flag = 0;

for(int i = 0; i<4;i++){

if(insideTriangle(x+pos[i][0], y+pos[i][1], t.v)){

//get depth
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;

//update minDepth
if(z_interpolated < depth_buf[get_index(x, y)*4 + i]){
depth_buf[get_index(x, y)*4+i] = z_interpolated;
color_buf[get_index(x, y)*4 + i] = t.getColor();
flag = 1;
}

}
}
if(flag){
Vector3f color(0.0f, 0.0f, 0.0f);
for(int i = 0; i<4; i++){
color += color_buf[get_index(x, y)*4 + i];
if(depth_buf[get_index(x, y)*4 + i] < minDepth)
{
minDepth = depth_buf[get_index(x, y)*4 + i];
}
}
color = color/4.0f;
set_pixel(Vector3f(x, y, minDepth), color);
}
}
}
}
else {
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
if (insideTriangle(x + 0.5, y + 0.5, t.v))
{
//get depth
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;
//z-buffer & color
if (z_interpolated < depth_buf[get_index(x,y)])
{
Vector3f color = t.getColor();
depth_buf[get_index(x,y)] = z_interpolated;
Vector3f point(3);
point << (float)x, (float)y, z_interpolated;
set_pixel(point, color);
}
}
}
}
}
}

*4 depth_buf, no color_buf

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//Screen space rasterization
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
float x_min = std::min(v[0][0], std::min(v[1][0], v[2][0]));
float x_max = std::max(v[0][0], std::max(v[1][0], v[2][0]));
float y_min = std::min(v[0][1], std::min(v[1][1], v[2][1]));
float y_max = std::max(v[0][1], std::max(v[1][1], v[2][1]));

x_min = std::floor(x_min);
x_max = std::ceil(x_max);
y_min = std::floor(y_min);
y_max = std::ceil(y_max);
//bounding box

// 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;
//采样点坐标(i+0.5 , j+0.5 )

// TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.
bool MSAA = true;
if (MSAA)
{
//4 samples
std::vector pos{
{0.25, 0.25},
{0.75, 0.25},
{0.25, 0.75},
{0.75, 0.75},
};
//initialize color_buf, 0 means black
//std::fill(color_buf.begin(), color_buf.end(), Eigen::Vector3f{0, 0, 0}); why?

//for all samples
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
//record min depth
float minDepth = FLT_MAX;
//have sample in triangle
//bool flag = 0;
int count = 0;

for(int i = 0; i<4;i++){

if(insideTriangle(x+pos[i][0], y+pos[i][1], t.v)){

//get depth
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;

//update minDepth
if(z_interpolated < depth_buf[get_index(x, y)*4 + i]){
depth_buf[get_index(x, y)*4+i] = z_interpolated;
//color_buf[get_index(x, y)*4 + i] = t.getColor();
//flag = 1;
count++;
}
//minDepth = std::min(minDepth, z_interpolated);
}
}
if(count){
//calculate color
Vector3f color = t.getColor()*count/4.0f + frame_buf[get_index(x,y)] ;
for(int i = 0; i<4; i++){
if(depth_buf[get_index(x, y)*4 + i] < minDepth)
{
minDepth = depth_buf[get_index(x, y)*4 + i];
}
}
//color = color/4.0f;
set_pixel(Vector3f(x, y, minDepth), color);
}
}
}
}
else {
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
if (insideTriangle(x + 0.5, y + 0.5, t.v))
{
//get depth
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;
//z-buffer & color
if (z_interpolated < depth_buf[get_index(x,y)])
{
Vector3f color = t.getColor();
depth_buf[get_index(x,y)] = z_interpolated;
Vector3f point(3);
point << (float)x, (float)y, z_interpolated;
set_pixel(point, color);
}
}
}
}
}
}

./Rasterizer img.png 可以保存图像, 放大比较好看到抗锯齿效果噢~

完成作业2 MSAA 后的第二大不解的疑问:为什么说用四个采样点中最小的深度值作为像素的深度值更加准确??(看别人博客这么说的,可能这个问题的前提也不对)

第三个疑问为什么代码尖括号的转义字符<后面必须跟个空格才能显示出来!!!!

Author: Crux
Link: http://cruxssssss.github.io/2022/03/13/4GAMES101-2/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶