GAMES101 作业1

文章目录

  • 作业内容
  • 构建视图矩阵(View)
  • 构建模型矩阵 (Model)
  • 构建透视矩阵(Projection)
  • 视口变换(Viewport transform)
  • 提高:将三角形绕任意过原点的轴旋转
    • 旋转过程中报错

作业内容

本次作业的任务是填写一个旋转矩阵和一个透视投影矩阵。给定三维下三个
点 v0(2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2(−2.0, 0.0, −2.0), 你需要将这三个点的坐
标变换为屏幕坐标并在屏幕上绘制出对应的线框三角形 (在代码框架中,我们已
经提供了 draw_triangle 函数,所以你只需要去构建变换矩阵即可)。简而言之,
我们需要进行模型、视图、投影、视口等变换来将三角形显示在屏幕上。在提供
的代码框架中,我们留下了模型变换和投影变换的部分给你去完成。

需要在main.cpp中修改的函数

// 逐个元素地构建模型变换矩阵并返回该矩阵。在此函数中,你只需要实现三维中绕 z 轴旋转的变换矩阵,
// 而不用处理平移与缩放。
get_model_matrix(float rotation_angle):
//使用给定的参数逐个元素地构建透视投影矩阵并返回该矩阵。
get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar);

构建视图矩阵(View)

构建模型矩阵 (Model)

这里只需要考虑绕Z轴旋转的矩阵, 绕z轴旋转的矩阵在课程中如下
GAMES101 作业1_第1张图片

Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
    Eigen::Matrix4f model = Eigen::Matrix4f::Identity();  //Unit Matrix

    // TODO: Implement this function
    // Create the model matrix for rotating the triangle around the Z axis.
    // Then return it.
    float rotation_angle_radian = rotation_angle * MY_PI / 180.0;
    Eigen::Matrix4f rotation_around_z;
    
    float c = cosf(rotation_angle_radian);
    float s = sinf(rotation_angle_radian)
    rotation_around_z << c, -s, 0, 0,
                         s, c, 0, 0,
                         0, 0, 1, 0,
                         0, 0, 0, 1;
    model = model * rotation_around_z;

    //std::cout << model << std::endl;

    return model;
}

构建透视矩阵(Projection)

GAMES101 作业1_第2张图片
计算投影矩阵,需要两步:

  1. 计算正射投影矩阵
    GAMES101 作业1_第3张图片

  2. 计算挤压(squish)矩阵
    计算挤压矩阵关键就是算A和B
    GAMES101 作业1_第4张图片
    GAMES101 作业1_第5张图片


Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
                                      float zNear, float zFar)
{
    // Students will implement this function
    
    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();

    // TODO: Implement this function
    // Create the projection matrix for the given parameters.
    // Then return it.

    // Step 1: convert from eye_fov(field of view) and aspect_ration to l, r, b, t
    float t = tan((eye_fov*MY_PI/180.0) / 2) * fabs(zNear);
    float r = aspect_ratio * t;
    float l = -r;
    float b = -t;

    // Step 2: Create Matrix for Orthographic Projection
    // First Create Translate matrix for move center to the origin
    Eigen::Matrix4f trans =  Eigen::Matrix4f::Identity();
    trans(0, 3) = -(r + l) / 2;
    trans(1, 3) = -(t + b) / 2;
    trans(2, 3) = -(zNear + zFar) / 2;

    // Scale to [-1,1]^3
    Eigen::Matrix4f scale = Eigen::Matrix4f::Identity();
    scale(0, 0) = 2.0 / (r - l);
    scale(1, 1) = 2.0 / (t - b);
    scale(2, 2) = 2.0 / (zNear - zFar);

    // Get Ortho matrix
    Eigen::Matrix4f ortho = scale * trans;
    //std::cout << "Orthographic:" << std::endl << ortho << std::endl;

     Step 3: Create Perspective to Orthographic matrix
    float A = zNear + zFar;
    float B = -zNear * zFar;
    Eigen::Matrix4f pers2ortho;
    pers2ortho << zNear, 0, 0, 0,
                0, zNear, 0, 0,
                0, 0, A, B,
                0, 0, 1, 0;

    projection = ortho * pers2ortho;
    //std::cout << projection << std::endl;

    return projection;
}

视口变换(Viewport transform)

Learn OpenGL 坐标系统
GAMES101 作业1_第6张图片
视口变换的部分是在rasterizer::draw接口内做的,视口变化的计算如下:
GAMES101 作业1_第7张图片

void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive type)
{
    if (type != rst::Primitive::Triangle)
    {
        throw std::runtime_error("Drawing primitives other than triangle is not implemented yet!");
    }
    auto& buf = pos_buf[pos_buffer.pos_id];
    auto& ind = ind_buf[ind_buffer.ind_id];

    float f1 = (100 - 0.1) / 2.0;
    float f2 = (100 + 0.1) / 2.0;

    Eigen::Matrix4f mvp = projection * view * model;
    for (auto& i : ind)
    {
        Triangle t;
		
		// MVP 变换,并转换为齐次坐标,这一步就是将三角形转换到[1,1]*3的立方体中
        Eigen::Vector4f v[] = {
                mvp * to_vec4(buf[i[0]], 1.0f),
                mvp * to_vec4(buf[i[1]], 1.0f),
                mvp * to_vec4(buf[i[2]], 1.0f)
        };
		
		// 把w归一化
        for (auto& vec : v) {
            vec /= vec.w();
        }
		
		// 视口变化部分计算
        for (auto & vert : v)
        {
            vert.x() = 0.5*width*(vert.x()+1.0);  //width/2*x (Scale) + width/2*1.0 
            vert.y() = 0.5*height*(vert.y()+1.0); //height/2*x + height/2*1.0
            vert.z() = vert.z() * f1 + f2;        //对z做的特殊处理
        }

        for (int i = 0; i < 3; ++i)
        {
            t.setVertex(i, v[i].head<3>());
            t.setVertex(i, v[i].head<3>());
            t.setVertex(i, v[i].head<3>());
        }

        t.setColor(0, 255.0,  0.0,  0.0);
        t.setColor(1, 0.0  ,255.0,  0.0);
        t.setColor(2, 0.0  ,  0.0,255.0);

        rasterize_wireframe(t);
    }
}

其中一开始对Z的特殊处理不太明白,看到其他博客里面说

可能是因为,近平面设置在0.1,远平面设置在50,用这个式子可以把转移到[-1,1]区间的z值映射到近平面和远平面之间。

参考x和y方向上的操作,实际上z方向也做了scale 和 transport 操作

  • 将z方向从[-1,1] 变换到[0.1, 100]
  • 将[0,0]平移到透视体的中点
    以下就是对z轴的变换做的特殊处理的理解,但是可能不对,是我自己画的示意图。下一节会讲解到Z-buffer,应该会有更好的理解
    GAMES101 作业1_第8张图片

提高:将三角形绕任意过原点的轴旋转

用到了PPT中的罗德里格斯公式,如何构造一个罗德里格斯公式呢

// converter angle to model matrix
Eigen::Matrix4f get_rotation(Vector3f axis, float angle)
{
    float a = angle * PI / 180.0;
    // Return for float value  
    float cosa = cosf(a), sina = sinf(a);
    Eigen::Matrix3f  I = Eigen::Matrix3f::Identity();
    axis = axis.normalized();

    // Get N matrix 
    Eigen::Matrix3f nhat;
    nhat << 0, -axis.z(), axis.y(),
             axis.z(), 0, -axis.x(),
            -axis.y(), axis.x(), 0;
    
    // Rodrigues’ Rotation Formula
    Eigen::Matrix3f rodrigues_rotation;
    rodrigues_rotation = I + (nhat * nhat) * (1 - cosa) + sina * nhat;

    // Contruct to Matrix4f
    Eigen::Matrix4f tRet = Eigen::Matrix4f::Zero();
    tRet.block<3, 3>(0, 0) = rodrigues_rotation;
    tRet.row(3) = Eigen::Vector4f{ 0,0,0,1 };

    return tRet;
}

Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
    Eigen::Matrix4f model;
    // any axis 
    Vector3f axis{ 1, 1, 0 };
    Eigen::Matrix4f t = Eigen::Matrix4f::Identity();
    
    model = get_rotation(axis, rotation_angle);
       
    return model * t;
}

旋转过程中报错

随着按A或者D键旋转三角形,大概旋转了10次左右,程序会崩溃, 报错内容:

expression vector subscript out of range

定位发现是:

void rst::rasterizer::set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color)
{
    //old index: auto ind = point.y() + point.x() * width;
    if (point.x() < 0 || point.x() >= width ||
        point.y() < 0 || point.y() >= height) return;
    auto ind = (height-point.y())*width + point.x();
    // frame_buf size 为490000, 但是旋转过程中,ind出现大于490000的情况,此时超出了frame_buf的范围,所以会报上述错误
    // 额外增加的
    if (ind > frame_buf.size())
    {
        return;
    }
    frame_buf[ind] = color;
}

另外论坛上的方法是
如果 x 和 y 同时为 0,ind 此时等于 height * width,但是数组范围为 [0, height * width – 1],因此出现了越界

解决方法是不允许 x 和 y 同时为 0,也就是把小于号改成小于等于号

void rst::rasterizer::set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color)
{
    //old index: auto ind = point.y() + point.x() * width;
    if (point.x() <= 0 || point.x() >= width ||
        point.y() <= 0 || point.y() >= height) return;
    // if point.x() == 0 and point.y() == 0
    // then get ind == height * width
    // but size of frame_buf == height * width
    // so available index of frame_buf is [0, height * width - 1]
    // so don't allow point.x() == 0 and point.y() == 0
    auto ind = (height-point.y())*width + point.x();
    frame_buf[ind] = color;
}

你可能感兴趣的:(GAMES101,c++,图形渲染)