Games01作业

发布时间 2023-03-26 21:34:20作者: xiaomingcc

GAMES101 作业

作业1

作业1的目的是绘制线框三角形并让其旋转,对于这一目标,框架已经完成了大部分的工作,我们只需要填充MVP变换中的Model和Projection两个个矩阵即可。

首先是Model矩阵:

按照绕z轴旋转的矩阵填入即可。

Eigen::Matrix4f get_model_matrix(float rotation_angle) {
  Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
  float cosa = std::cos(rotation_angle / 180.0 * MY_PI);
  float sina = std::sin(rotation_angle / 180.0 * MY_PI);
  model << cosa, -sina, 0.0, 0.0, sina, cosa, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
      0.0, 0.0, 1.0;
  return model;
}

之后是Projection矩阵:

投影过程分为三个步骤:

  • 第一步将视锥压缩成为长方体
  • 第二部将长方体平移,使其中心位于坐标原点
  • 第三步将长方体压缩为[-1,1]³ 的一个立方体(注意-1到1意味着边长为2)
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
                                      float zNear, float zFar) {
  Eigen::Matrix4f squash;
  squash << zNear, .0, .0, .0, .0, zNear, .0, .0, .0, .0, zNear + zFar,
      -zNear * zFar, .0, .0, 1.0, .0;
  Eigen::Matrix4f translate;
  float t = -zNear * std::tan(eye_fov / 180.0 * MY_PI / 2);
  float b = -t;
  float r = t * aspect_ratio;
  float l = -r;
  translate << 
        1.0, .0, .0, -(l+r)/2,
        .0, 1.0, .0, -(b+t)/2,
        .0, .0, 1.0, -(zNear + zFar) / 2,
        .0, .0, .0, 1.0;

  Eigen::Matrix4f zoom;
  zoom << 
        2 / (r - l), 0., 0., 0.,
        .0, 2 / (t - b), 0., .0, 
        .0, .0, 2 / (zNear - zFar), .0,
        .0, .0, .0, 1.0;
  return zoom * translate * squash;
}

作业2

作业2需要实现的部分有两个,一是判断点是否在三角形内部,二是光栅化。

判断点在三角形内部:

对于三角形ABC与任意点P,判断其是否在三角形内部可以用AB×AP,BC×BP,CA×CP这三项叉乘是否在同一方向上来进行。

鉴于我们只需要考虑xy平面上的三角形,可以使用二维的叉乘来判断,设两个向量分别为(x1,y1)与(x2,y2),即是计算(x1y2 - x2y1)。

static bool insideTriangle(float x, float y,const Vector3f (&v) [3])
{       
    // point A, point B  reutrn AB × AP
    auto cross = [x,y](float x1, float y1, float x2, float y2){
        return (x2 - x1)*(y - y1) -(y2 - y1)*(x - x1); 
    };
    // AB × AP
    float a = cross(v[0][0], v[0][1], v[1][0], v[1][1]);
    float b = cross(v[1][0], v[1][1], v[2][0], v[2][1]);
    float c = cross(v[2][0], v[2][1], v[0][0], v[0][1]);
    return a > 0 ? (b>0 && c>0 ) : (b<0 && c<0);
}

这里cross传入的是两个点A和B,捕获的是P点,计算结果是AB×AP。

计算完成后判断三个结果是否同号即可。这里采用浮点数作为参数可以便于后续的SSAA计算。

光栅化:

如果前面的Projection矩阵有问题,那么这里可能得不到正确的结果(方向和深度关系会与示例不同)

需要完成的工作包括:

  1. 计算bounding box,这一步比较简单,遍历三角形的三个顶点取到最小的x,最小的y,最大的x和最大的y即可。

  2. 判断点是否在三角形内部,以及判断深度是否小于深度缓冲中的值:

    if(insideTriangle(x+.5, y+.5, t.v)){
      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;
    
      if(depth_buf[get_index(x,y)] > z_interpolated){
        depth_buf[get_index(x,y)] = z_interpolated;
        frame_buf[get_index(x, y)] = t.getColor();
      }
    }
    

SSAA优化

2×2 SSAA的做法简单来说就是对于原始的采样,使用4倍的分辨率进行,因此我们需要定义4倍大小的frame_buffer和z_buffer,为便于计算,我们可以将原来的frame_buffer和z_buffer的元素替换成对应的数组:

  std::vector<std::array<float, SSAA*SSAA>> ssaa_depth_buf;
  std::vector<std::array<Eigen::Vector3f, SSAA*SSAA>> ssaa_frame_buf;

然后光栅化的过程与原来的方式几乎没有区别,只需要在每一个(x,y)点处计算4个采样点即可。最后是在绘制之前将ssaa_frame_buffer中的颜色取平均填入到frame_buffer中去。

// 对于每个点x,y,需要采样 n*n次
for(int i = 0; i< SSAA; ++i){
	for(int j = 0;j<SSAA;++j){
        float xx = x+1.0/SSAA/2 + i * 1.0/SSAA;
        float yy = y+1.0/SSAA/2 + j * 1.0/SSAA;
        if(!insideTriangle(xx, yy, t.v)){
            continue;
        }
        auto[alpha, beta, gamma] = computeBarycentric2D(xx, yy, 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;
        int ssaa_index = i*SSAA +j;
        if(ssaa_depth_buf[get_index(x, y)][ssaa_index] > z_interpolated){
            ssaa_depth_buf[get_index(x, y)][ssaa_index] = z_interpolated;
            ssaa_frame_buf[get_index(x,y)][ssaa_index] = t.getColor();
        }   
    }    
}

下图是4×4 SSAA、2×2 SSAA和不使用SSAA的对比:

可以看到效果还是比较明显的