# Games101 作业

代码在 GitHub 中开源,包含原版没做过的作业和做过的作业,链接:https://github.com/Maikire/GAMES101-Homework
原版是在 Linux 环境下的项目
我是在 Windows 环境下做的,所以部分配置和代码有改动

# Assignment0

给定一个点 P=(2,1),将该点绕原点先逆时针旋转 45◦,再平移 (1,2),计算出变换后点的坐标(要求用齐次坐标进行计算)

int main()
{
    Vector3f p(2.0, 1.0, 1.0);
    Matrix3f rp;
    rp << cos(-45.0 / 180.0), -sin(-45.0 / 180.0), 1.0,
          sin(-45.0 / 180.0), cos(-45.0 / 180.0), 2.0,
          0, 0, 1.0;
    Matrix3f rp1;
    rp1 << cos(-45.0 / 180.0), -sin(-45.0 / 180.0), 0,
           sin(-45.0 / 180.0), cos(-45.0 / 180.0), 0,
           0, 0, 1.0;
    Matrix3f rp2;
    rp2 << 1.0, 0, 1.0,
           0, 1.0, 2.0,
           0, 0, 1.0;
    cout << rp * p << endl;
    cout << "==============================" << endl;
    cout << rp2 * rp1 * p << endl;
    return 0;
}

# Assignment1

旋转三角形

Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
    Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
    // TODO: Implement this function
    // Create the model matrix for rotating the triangle around the Z axis.
    // Then return it.
    float rad = rotation_angle * MY_PI / 180.0;
    model << cos(rad), -sin(rad), 0, 0,
             sin(rad), cos(rad), 0, 0,
             0, 0, 1, 0,
             0, 0, 0, 1;
    return model;
}
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.
    float rad = eye_fov * MY_PI / 180.0f;
    float t = tan(rad / 2) * abs(zNear);
    float r = t * aspect_ratio;
    float l = -r;
    float b = -t;
    // 透视投影矩阵
    // GAMES101 和 OpenGL 中,相机的观察方向是 -z
    // 如果最后一行是 [0, 0, 1, 0],那么变换后的 w 就等于原坐标的 z,所以 w 也是负值
    // 当 x 和 y 同时除以一个负数 w 时,它们在 NDC 空间中的符号都会反转
    //x 反转 = 左右镜像;y 反转 = 上下颠倒
    // 上下左右同时翻转 = 旋转 180 度
    // 所以用 [0, 0, -1, 0] 修正
    // 但是这样做会让 -z 反转到 +z,这会导致深度反转,所以将第三行整行 * -1 以抵消反转
    Eigen::Matrix4f persp;
    persp << zNear, 0, 0, 0,
             0, zNear, 0, 0,
             0, 0, -(zNear + zFar), zNear* zFar,
             0, 0, -1, 0;
    // 平移
    Eigen::Matrix4f translate;
    translate << 1, 0, 0, -(r + l) / 2,
                 0, 1, 0, -(t + b) / 2,
                 0, 0, 1, -(zNear + zFar) / 2,
                 0, 0, 0, 1;
    // 缩放
    Eigen::Matrix4f scale;
    scale << 2 / (r - l), 0, 0, 0,
             0, 2 / (t - b), 0, 0,
             0, 0, 2 / (zNear - zFar), 0,
             0, 0, 0, 1;
    // 先透视,再正交
    projection = scale * translate * persp;
    return projection;
}

# Assignment2

在屏幕上画出实心三角形

# 基础

1、计算包围盒

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.
    float minX = t.v[0].x();
    float minY = t.v[0].y();
    float maxX = t.v[0].x();
    float maxY = t.v[0].y();
    for (size_t i = 1; i <= 2; i++)
    {
        if (t.v[i].x() < minX)
        {
            minX = t.v[i].x();
        }
        if (t.v[i].y() < minY)
        {
            minY = t.v[i].y();
        }
        if (t.v[i].x() > maxX)
        {
            maxX = t.v[i].x();
        }
        if (t.v[i].y() > maxY)
        {
            maxY = t.v[i].y();
        }
    }
    int minXi = std::floor(minX);
    int maxXi = std::ceil(maxX);
    int minYi = std::floor(minY);
    int maxYi = std::ceil(maxY);
    minXi = std::max(0, minXi);
    minYi = std::max(0, minYi);
    maxXi = std::min(width - 1, maxXi);
    maxYi = std::min(height - 1, maxYi);
    for (int i = minXi; i <= maxXi; i++)
    {
        for (int j = minYi; j <= maxYi; j++)
        {
            //rasterize_triangle_normal(t, Vector3f(i, j, 0), v);
            rasterize_triangle_msaa_2x(t, Vector3f(i, j, 0), v);
        }
    }
}

2、测试点是否在三角形内

void rst::rasterizer::rasterize_triangle_normal(const Triangle& t, const Vector3f& pix, const array<Vector4f, 3>& v)
{
    float x = pix.x() + 0.5f;
    float y = pix.y() + 0.5f;
    Vector3f p(x, y, 0);
    // a = t.v[0]
    // b = t.v[1]
    // c = t.v[2]
    Vector3f ab = t.v[1] - t.v[0];
    Vector3f ap = p - t.v[0];
    Vector3f bc = t.v[2] - t.v[1];
    Vector3f bp = p - t.v[1];
    Vector3f ca = t.v[0] - t.v[2];
    Vector3f cp = p - t.v[2];
    float z1 = ab.cross(ap).z();
    float z2 = bc.cross(bp).z();
    float z3 = ca.cross(cp).z();
     if ((z1 >= 0 && z2 >= 0 && z3 >= 0) ||
         (z1 <= 0 && z2 <= 0 && z3 <= 0))
     {
         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;
         int index = get_index(pix.x(), pix.y());
         if (z_interpolated < depth_buf[index])
         {
             depth_buf[index] = z_interpolated;
             set_pixel(pix, t.getColor());
         }
     }
}

# 提高:msaa2x 抗锯齿

rasterizer.hpp 中声明深度缓冲和计算的索引函数

std::vector<float> super_sampling_buf;
int get_index_msaa_2x(int x, int y);
  • rasterizer.cpp 中实现计算索引的函数 get_index_msaa_2x
  • rasterizer::clear 函数中添加 super_sampling_buf
  • rasterizer::rasterizer 函数中初始化 super_sampling_buf
int rst::rasterizer::get_index_msaa_2x(int x, int y)
{
    return (height * 2 - 1 - y) * width * 2 + x;
}
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 ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)
    {
        std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());
        std::fill(super_sampling_buf.begin(), super_sampling_buf.end(), 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);
    super_sampling_buf.resize(w * h * 4);
}

实现抗锯齿

void rst::rasterizer::rasterize_triangle_msaa_2x(const Triangle& t, const Vector3f& pix, const array<Eigen::Vector4f, 3>& v)
{
    float x = pix.x();
    float y = pix.y();
    float in_pix = 0;
    for (int i = 1; i <= 2; i++)
    {
        x += i / 4.0;
        for (int j = 1; j <= 2; j++)
        {
            y += j / 4.0;
            Vector3f p(x, y, 0);
            Vector3f ab = t.v[1] - t.v[0];
            Vector3f ap = p - t.v[0];
            Vector3f bc = t.v[2] - t.v[1];
            Vector3f bp = p - t.v[1];
            Vector3f ca = t.v[0] - t.v[2];
            Vector3f cp = p - t.v[2];
            float z1 = ab.cross(ap).z();
            float z2 = bc.cross(bp).z();
            float z3 = ca.cross(cp).z();
            if ((z1 >= 0 && z2 >= 0 && z3 >= 0) ||
                (z1 <= 0 && z2 <= 0 && z3 <= 0))
            {
                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;
                int index = get_index_msaa_2x(pix.x() * 2 + i, pix.y() * 2 + j);
                if (z_interpolated < super_sampling[index])
                {
                    super_sampling[index] = z_interpolated;
                    in_pix += 0.25;
                }
            }
        }
        y = pix.y();
    }
    set_pixel(pix, get_pixel(pix) * (1 - in_pix) + t.getColor() * in_pix);
}

# Assignment3

渲染、模型、纹理、法线

1、修改函数 rasterize_triangle() in rasterizer.cpp ,实现与作业 2 类似的插值算法,实现法向量、颜色、纹理颜色的插值

void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos) 
{
    // TODO: From your HW3, get the triangle rasterization code.
    // TODO: Inside your rasterization loop:
    //    * v[i].w() is the vertex view space depth value z.
    //    * Z is interpolated view space depth for the current pixel
    //    * zp is depth between zNear and zFar, used for z-buffer
    // float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
    // float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
    // zp *= Z;
    // TODO: Interpolate the attributes:
    // auto interpolated_color
    // auto interpolated_normal
    // auto interpolated_texcoords
    // auto interpolated_shadingcoords
    // Use: fragment_shader_payload payload( interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
    // Use: payload.view_pos = interpolated_shadingcoords;
    // Use: Instead of passing the triangle's color directly to the frame buffer, pass the color to the shaders first to get the final color;
    // Use: auto pixel_color = fragment_shader(payload);
    auto v = t.toVector4();
    
    float minX = t.v[0].x();
    float minY = t.v[0].y();
    float maxX = t.v[0].x();
    float maxY = t.v[0].y();
    
    for (size_t i = 1; i <= 2; i++)
    {
        if (t.v[i].x() < minX)
        {
            minX = t.v[i].x();
        }
        if (t.v[i].y() < minY)
        {
            minY = t.v[i].y();
        }
        if (t.v[i].x() > maxX)
        {
            maxX = t.v[i].x();
        }
        if (t.v[i].y() > maxY)
        {
            maxY = t.v[i].y();
        }
    }
    
    int minXi = std::floor(minX);
    int maxXi = std::ceil(maxX);
    int minYi = std::floor(minY);
    int maxYi = std::ceil(maxY);
    minXi = std::max(0, minXi);
    minYi = std::max(0, minYi);
    maxXi = std::min(width - 1, maxXi);
    maxYi = std::min(height - 1, maxYi);
    
    for (int i = minXi; i <= maxXi; i++)
    {
        for (int j = minYi; j <= maxYi; j++)
        {
            rasterize_triangle_normal(t, view_pos, Vector2i(i, j), v);
        }
    }
}
void rst::rasterizer::rasterize_triangle_normal(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos, const Vector2i& pix, const array<Eigen::Vector4f, 3>& v)
{
    float x = pix.x() + 0.5f;
    float y = pix.y() + 0.5f;
    Vector3f p(x, y, 0);
    Vector3f a = t.v[0].head<3>();
    Vector3f b = t.v[1].head<3>();
    Vector3f c = t.v[2].head<3>();
    Vector3f ab = b - a;
    Vector3f ap = p - a;
    Vector3f bc = c - b;
    Vector3f bp = p - b;
    Vector3f ca = a - c;
    Vector3f cp = p - c;
    float z1 = ab.cross(ap).z();
    float z2 = bc.cross(bp).z();
    float z3 = ca.cross(cp).z();
    if ((z1 >= 0 && z2 >= 0 && z3 >= 0) ||
        (z1 <= 0 && z2 <= 0 && z3 <= 0))
    {
        auto [alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
        float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
        float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
        zp *= Z;
        int index = get_index(pix.x(), pix.y());
        if (zp < depth_buf[index])
        {
            depth_buf[index] = zp;
            Vector3f interpolated_color = (alpha * t.color[0] / v[0].w() + beta * t.color[1] / v[1].w() + gamma * t.color[2] / v[2].w()) * Z;
            Vector3f interpolated_normal = (alpha * t.normal[0] / v[0].w() + beta * t.normal[1] / v[1].w() + gamma * t.normal[2] / v[2].w()) * Z;
            Vector2f interpolated_texcoords = (alpha * t.tex_coords[0] / v[0].w() + beta * t.tex_coords[1] / v[1].w() + gamma * t.tex_coords[2] / v[2].w()) * Z;
            Vector3f interpolated_shadingcoords = (alpha * view_pos[0] / v[0].w() + beta * view_pos[1] / v[1].w() + gamma * view_pos[2] / v[2].w()) * Z;
            fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
            payload.view_pos = interpolated_shadingcoords;
            Vector3f pixel_color = fragment_shader(payload);
            set_pixel(pix, pixel_color);
        }
    }
}

2、修改函数 get_projection_matrix() in main.cpp 将你自己在之前的实验中实现的投影矩阵填到此处

// 直接复制之前的代码即可

3、修改函数 phong_fragment_shader() in main.cpp 实现 Blinn-Phong 模型计算 Fragment Color

Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);
    auto l1 = light<!--swig0-->;
    auto l2 = light<!--swig1-->;
    std::vector<light> lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};
    Eigen::Vector3f eye_pos{0, 0, 10};
    float p = 150;
    Eigen::Vector3f color = payload.color;
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal.normalized();
    Eigen::Vector3f result_color = {0, 0, 0};
    for (auto& light : lights)
    {
        // TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* 
        // components are. Then, accumulate that result on the *result_color* object.
        Vector3f light_dir = (light.position - point).normalized();
        // 半兰伯特
        Vector3f diffuse = kd * (normal.dot(light_dir) * 0.5 + 0.5);
        // Blinn-Phong
        Vector3f view_dir = (eye_pos - point).normalized();
        Vector3f halfDir = (light_dir + view_dir).normalized();
        Vector3f specular = ks * pow(normal.dot(halfDir), p);
        // 环境光
        //cwiseProduct 逐元素相乘
        Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        
        result_color += diffuse + specular + ambient;
    }
    return result_color * 255.f;
}

4、修改函数 texture_fragment_shader() in main.cpp 在实现 Blinn-Phong 的基础上,将纹理颜色视为公式中的 kd,实现 Texture Shading Fragment Shader

Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f return_color = { 0, 0, 0 };
    if (payload.texture)
    {
        // TODO: Get the texture value at the texture coordinates of the current fragment
        float u = std::clamp(payload.tex_coords.x(), 0.0f, 1.0f);
        float v = std::clamp(payload.tex_coords.y(), 0.0f, 1.0f);
        return_color = payload.texture->getColor(u, v);
    }
    Eigen::Vector3f texture_color;
    texture_color << return_color.x(), return_color.y(), return_color.z();
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = texture_color / 255.f;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);
    auto l1 = light{ {20, 20, 20}, {500, 500, 500} };
    auto l2 = light{ {-20, 20, 0}, {500, 500, 500} };
    std::vector<light> lights = { l1, l2 };
    Eigen::Vector3f amb_light_intensity{ 10, 10, 10 };
    Eigen::Vector3f eye_pos{ 0, 0, 10 };
    float p = 150;
    Eigen::Vector3f color = texture_color;
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;
    Eigen::Vector3f result_color = { 0, 0, 0 };
    for (auto& light : lights)
    {
        // TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* 
        // components are. Then, accumulate that result on the *result_color* object.
        Vector3f light_dir = (light.position - point).normalized();
        // 半兰伯特
        Vector3f diffuse = kd * (normal.dot(light_dir) * 0.5 + 0.5);
        // Blinn-Phong
        Vector3f view_dir = (eye_pos - point).normalized();
        Vector3f halfDir = (light_dir + view_dir).normalized();
        Vector3f specular = ks * pow(normal.dot(halfDir), p);
        // 环境光
        //cwiseProduct 逐元素相乘
        Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        result_color += diffuse + specular + ambient;
    }
    return result_color * 255.f;
}

5、修改函数 bump_fragment_shader() in main.cpp 在实现 Blinn-Phong 的 基础上,实现 Bump mapping

Eigen::Vector3f bump_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);
    auto l1 = light{ {20, 20, 20}, {500, 500, 500} };
    auto l2 = light{ {-20, 20, 0}, {500, 500, 500} };
    std::vector<light> lights = { l1, l2 };
    Eigen::Vector3f amb_light_intensity{ 10, 10, 10 };
    Eigen::Vector3f eye_pos{ 0, 0, 10 };
    float p = 150;
    Eigen::Vector3f color = payload.color;
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;
    float kh = 0.2, kn = 0.1;
    // TODO: Implement bump mapping here
    // Let n = normal = (x, y, z)
    // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
    // Vector b = n cross product t
    // Matrix TBN = [t b n]
    // dU = kh * kn * (h(u+1/w,v)-h(u,v))
    // dV = kh * kn * (h(u,v+1/h)-h(u,v))
    // Vector ln = (-dU, -dV, 1)
    // Normal n = normalize(TBN * ln)
    float x = normal.x();
    float y = normal.y();
    float z = normal.z();
    Vector3f t = Vector3f(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
    Vector3f b = normal.cross(t);
    Matrix3f TBN;
    TBN << t, b, normal;
    float u = std::clamp(payload.tex_coords.x(), 0.0f, 1.0f);
    float v = std::clamp(payload.tex_coords.y(), 0.0f, 1.0f);
    float u1 = std::clamp(u + 1 / width, 0.0f, 1.0f);
    float v1 = std::clamp(v + 1 / height, 0.0f, 1.0f);
    Vector3f h = payload.texture->getColor(u, v);
    Vector3f h1 = payload.texture->getColor(u1, v);
    Vector3f h2 = payload.texture->getColor(u, v1);
    float dU = kh * kn * (h1.x() - h.x());
    float dV = kh * kn * (h2.x() - h.x());
    Vector3f ln = Vector3f(-dU, -dV, 1);
    normal = (TBN * ln).normalized();
    Eigen::Vector3f result_color = { 0, 0, 0 };
    result_color = normal;
    return result_color * 255.f;
}

6、修改函数 displacement_fragment_shader() in main.cpp 在实现 Bump mapping 的基础上,实现 displacement mapping

Eigen::Vector3f displacement_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    Eigen::Vector3f kd = payload.color;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);
    auto l1 = light{ {20, 20, 20}, {500, 500, 500} };
    auto l2 = light{ {-20, 20, 0}, {500, 500, 500} };
    std::vector<light> lights = { l1, l2 };
    Eigen::Vector3f amb_light_intensity{ 10, 10, 10 };
    Eigen::Vector3f eye_pos{ 0, 0, 10 };
    float p = 150;
    Eigen::Vector3f color = payload.color;
    Eigen::Vector3f point = payload.view_pos;
    Eigen::Vector3f normal = payload.normal;
    float kh = 0.2, kn = 0.1;
    // TODO: Implement displacement mapping here
    // Let n = normal = (x, y, z)
    // Vector t = (x*y/sqrt(x*x+z*z),sqrt(x*x+z*z),z*y/sqrt(x*x+z*z))
    // Vector b = n cross product t
    // Matrix TBN = [t b n]
    // dU = kh * kn * (h(u+1/w,v)-h(u,v))
    // dV = kh * kn * (h(u,v+1/h)-h(u,v))
    // Vector ln = (-dU, -dV, 1)
    // Position p = p + kn * n * h(u,v)
    // Normal n = normalize(TBN * ln)
    float x = normal.x();
    float y = normal.y();
    float z = normal.z();
    Vector3f t = Vector3f(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / sqrt(x * x + z * z));
    Vector3f b = normal.cross(t);
    Matrix3f TBN;
    TBN << t, b, normal;
    float u = std::clamp(payload.tex_coords.x(), 0.0f, 1.0f);
    float v = std::clamp(payload.tex_coords.y(), 0.0f, 1.0f);
    float u1 = std::clamp(u + 1 / width, 0.0f, 1.0f);
    float v1 = std::clamp(v + 1 / height, 0.0f, 1.0f);
    Vector3f h = payload.texture->getColor(u, v);
    Vector3f h1 = payload.texture->getColor(u1, v);
    Vector3f h2 = payload.texture->getColor(u, v1);
    float dU = kh * kn * (h1.x() - h.x());
    float dV = kh * kn * (h2.x() - h.x());
    Vector3f ln = Vector3f(-dU, -dV, 1);
    normal = (TBN * ln).normalized();
    Eigen::Vector3f result_color = { 0, 0, 0 };
    for (auto& light : lights)
    {
        // TODO: For each light source in the code, calculate what the *ambient*, *diffuse*, and *specular* 
        // components are. Then, accumulate that result on the *result_color* object.
        Vector3f light_dir = (light.position - point).normalized();
        // 半兰伯特
        Vector3f diffuse = kd * (normal.dot(light_dir) * 0.5 + 0.5);
        // Blinn-Phong
        Vector3f view_dir = (eye_pos - point).normalized();
        Vector3f halfDir = (light_dir + view_dir).normalized();
        Vector3f specular = ks * pow(normal.dot(halfDir), p);
        // 环境光
        //cwiseProduct 逐元素相乘
        Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        result_color += diffuse + specular + ambient;
    }
    return result_color * 255.f;
}

# Assignment4

贝赛尔曲线

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
    // TODO: Implement de Casteljau's algorithm
    
    int num = control_points.size() - 1;
    if (num <= 0)
    {
		return control_points[0];
    }
    std::vector<cv::Point2f> temp_points(num);
    for (size_t i = 0; i < num; i++)
    {
		temp_points[i] = (1 - t) * control_points[i] + t * control_points[i + 1];
    }
    
    return recursive_bezier(temp_points, t);
}
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{
    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's 
    // recursive Bezier algorithm.
    for (float t = 0.0; t <= 1.0; t += 0.0001)
    {
        cv::Point2f line_points = recursive_bezier(control_points, t);
        window.at<cv::Vec3b>(line_points.y, line_points.x)[2] = 255;
    }
}

# Assignment5

光线追踪

1、修改 Render() in Renderer.cpp ,这里你需要为每个像素生成一条对应的光线,然后调用函数 castRay() 来得到颜色,最后将颜色存储在帧缓冲区的相应像素中

void Renderer::Render(const Scene& scene)
{
    std::vector<Vector3f> framebuffer(scene.width * scene.height);
    float scale = std::tan(deg2rad(scene.fov * 0.5f));
    float imageAspectRatio = scene.width / (float)scene.height;
    // Use this variable as the eye position to start your rays.
    Vector3f eye_pos(0);
    int m = 0;
    for (int j = 0; j < scene.height; ++j)
    {
        for (int i = 0; i < scene.width; ++i)
        {
            // generate primary ray direction
            float x;
            float y;
            // TODO: Find the x and y positions of the current pixel to get the direction
            // vector that passes through it.
            // Also, don't forget to multiply both of them with the variable *scale*, and
            // x (horizontal) variable with the *imageAspectRatio*
			// 像素坐标以屏幕中心为原点,x 轴水平向右,y 轴竖直向上
			//x 从 [0, width] 映射到 [-1, 1]
            x = (2 * (i + 0.5) / (float)scene.width - 1) * imageAspectRatio * scale;
			//y 从 [0, height] 映射到 [1, -1]
            y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;
            // Don't forget to normalize this direction!
            Vector3f dir = normalize(Vector3f(x, y, -1));
            framebuffer[m++] = castRay(eye_pos, dir, scene, 0);
        }
        UpdateProgress(j / (float)scene.height);
    }
    // save framebuffer to file
    FILE* fp = fopen("binary.ppm", "wb");
    (void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
    for (auto i = 0; i < scene.height * scene.width; ++i) {
        static unsigned char color[3];
        color[0] = (char)(255 * clamp(0, 1, framebuffer[i].x));
        color[1] = (char)(255 * clamp(0, 1, framebuffer[i].y));
        color[2] = (char)(255 * clamp(0, 1, framebuffer[i].z));
        fwrite(color, 1, 3, fp);
    }
    fclose(fp);    
}

2、修改 rayTriangleIntersect() in Triangle.hppv0, v1, v2 是三角形的三个顶点, orig 是光线的起点, dir 是光线单位化的方向向量。 tnear, u, v 是你需要使用我们课上推导的 Moller-Trumbore 算法来更新的参数

bool rayTriangleIntersect(const Vector3f& v0, const Vector3f& v1, const Vector3f& v2, const Vector3f& orig,
                          const Vector3f& dir, float& tnear, float& u, float& v)
{
    // TODO: Implement this function that tests whether the triangle
    // that's specified bt v0, v1 and v2 intersects with the ray (whose
    // origin is *orig* and direction is *dir*)
    // Also don't forget to update tnear, u and v.
	Vector3f E1 = v1 - v0;
	Vector3f E2 = v2 - v0;
    Vector3f S0 = orig - v0;
    Vector3f S1 = crossProduct(dir, E2);
    Vector3f S2 = crossProduct(S0, E1);
    float k = 1.0f / dotProduct(S1, E1);
    float t = k * dotProduct(S2, E2);
    float b1 = k * dotProduct(S1, S0);
    float b2 = k * dotProduct(S2, dir);
    tnear = t;
    u = b1;
    v = b2;
    bool res = t > 0 && b1 > 0 && b2 > 0 && (1 - b1 - b2) > 0;
    return res;
}

# Assignment6

BVH 加速结构

1、 Render() in Renderer.cpp 将你的光线生成过程粘贴到此处,并且按照新框架更新相应调用的格式

void Renderer::Render(const Scene& scene)
{
    std::vector<Vector3f> framebuffer(scene.width * scene.height);
    float scale = tan(deg2rad(scene.fov * 0.5));
    float imageAspectRatio = scene.width / (float)scene.height;
    Vector3f eye_pos(-1, 5, 10);
    int m = 0;
    for (uint32_t j = 0; j < scene.height; ++j)
    {
        for (uint32_t i = 0; i < scene.width; ++i)
        {
            // generate primary ray direction
            // TODO: Find the x and y positions of the current pixel to get the
            // direction
            //  vector that passes through it.
            // Also, don't forget to multiply both of them with the variable
            // *scale*, and x (horizontal) variable with the *imageAspectRatio*
            float x = (2 * (i + 0.5) / (float)scene.width - 1) * imageAspectRatio * scale;
            float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;
            // Don't forget to normalize this direction!
            Vector3f dir = normalize(Vector3f(x, y, -1));
            framebuffer[m++] = scene.castRay(Ray(eye_pos, dir), 0);
        }
        UpdateProgress(j / (float)scene.height);
    }
    UpdateProgress(1.f);
    // save framebuffer to file
    FILE* fp = fopen("binary.ppm", "wb");
    (void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
    for (auto i = 0; i < scene.height * scene.width; ++i) {
        static unsigned char color[3];
        color[0] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].x));
        color[1] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].y));
        color[2] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].z));
        fwrite(color, 1, 3, fp);
    }
    fclose(fp);    
}

2、 Triangle::getIntersection() in Triangle.hpp 将你的光线 - 三角形相交函数粘贴到此处,并且按照新框架更新相应相交信息的格式。

bool rayTriangleIntersect(
    const Vector3f& v0,
    const Vector3f& v1,
    const Vector3f& v2,
    const Vector3f& orig,
    const Vector3f& dir,
    float& tnear,
    float& u,
    float& v)
{
    const float EPSILON = 1e-8f;
    Vector3f edge1 = v1 - v0;
    Vector3f edge2 = v2 - v0;
    Vector3f pvec = crossProduct(dir, edge2);
    float det = dotProduct(edge1, pvec);
    if (fabs(det) < EPSILON)
        return false;
    float invDet = 1.0f / det;
    Vector3f tvec = orig - v0;
    u = dotProduct(tvec, pvec) * invDet;
    if (u < 0.0f || u > 1.0f)
        return false;
    Vector3f qvec = crossProduct(tvec, edge1);
    v = dotProduct(dir, qvec) * invDet;
    if (v < 0.0f || u + v > 1.0f)
        return false;
    tnear = dotProduct(edge2, qvec) * invDet;
    if (tnear < EPSILON)
        return false;
    return true;
}
inline Intersection Triangle::getIntersection(Ray ray)
{
    Intersection inter;
    if (dotProduct(ray.direction, normal) > 0)
        return inter;
    double u, v, t_tmp = 0;
    Vector3f pvec = crossProduct(ray.direction, e2);
    double det = dotProduct(e1, pvec);
    if (fabs(det) < EPSILON)
        return inter;
    double det_inv = 1. / det;
    Vector3f tvec = ray.origin - v0;
    u = dotProduct(tvec, pvec) * det_inv;
    if (u < 0 || u > 1)
        return inter;
    Vector3f qvec = crossProduct(tvec, e1);
    v = dotProduct(ray.direction, qvec) * det_inv;
    if (v < 0 || u + v > 1)
        return inter;
    t_tmp = dotProduct(e2, qvec) * det_inv;
    // TODO find ray triangle intersection
    float t;
    float b1;
    float b2;
    if (rayTriangleIntersect(v0, v1, v2, ray.origin, ray.direction, t, b1, b2))
    {
        inter.happened = true;
        inter.coords = (1 - b1 - b2) * v0 + b1 * v1 + b2 * v2;
        inter.normal = normal;
        inter.distance = t;
        inter.obj = this;
        inter.m = m;
    }
    return inter;
}

3、 IntersectP() in the Bounds3.hpp 这个函数的作用是判断包围盒 BoundingBox 与光线是否相交,你需要按照课程介绍的算法实现求交过程

inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg) const
{
    // invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z), use this because Multiply is faster that Division
    // dirIsNeg: ray direction(x,y,z), dirIsNeg=[int(x>0),int(y>0),int(z>0)], use this to simplify your logic
    // TODO test if ray bound intersects
    // 时间 = 位移 / 速度
    float tMin_x = (pMin.x - ray.origin.x) * invDir.x;
    float tMax_x = (pMax.x - ray.origin.x) * invDir.x;
    float tMin_y = (pMin.y - ray.origin.y) * invDir.y;
    float tMax_y = (pMax.y - ray.origin.y) * invDir.y;
    float tMin_z = (pMin.z - ray.origin.z) * invDir.z;
    float tMax_z = (pMax.z - ray.origin.z) * invDir.z;
    if (!dirIsNeg[0]) std::swap(tMin_x, tMax_x);
    if (!dirIsNeg[1]) std::swap(tMin_y, tMax_y);
    if (!dirIsNeg[2]) std::swap(tMin_z, tMax_z);
    float tEnter = std::max(tMin_x, std::max(tMin_y, tMin_z));
    float tExit = std::min(tMax_x, std::min(tMax_y, tMax_z));
    return tEnter <= tExit && tExit >= 0;
}

4、 getIntersection() in BVH.cpp 建立 BVH 之后,我们可以用它加速求交过程。该过程递归进行,你将在其中调用你实现的 Bounds3::IntersectP

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    if (node == nullptr)
    {
        return Intersection();
    }
    if (node->bounds.IntersectP(ray, ray.direction_inv, ray.dirIsNeg))
    {
        if (node->left == nullptr && node->right == nullptr)
        {
            return node->object->getIntersection(ray);
        }
        Intersection isect1 = getIntersection(node->left, ray);
        Intersection isect2 = getIntersection(node->right, ray);
        if (isect1.happened && isect2.happened)
        {
            return isect1.distance < isect2.distance ? isect1 : isect2;
        }
        else if (isect1.happened)
        {
            return isect1;
        }
        else
        {
            return isect2;
        }
    }
    else
    {
        return Intersection();
    }
}

# Assignment7

路径追踪

1、你需要从上一次编程练习中直接拷贝以下函数到对应位置: Triangle::getIntersection() in Triangle.hppIntersectP() in the Bounds3.hppgetIntersection() in BVH.cpp

2、在本次实验中,你只需要修改这一个函数 castRay() in Scene.cpp ,在其中实现 Path Tracing 算法

Vector3f Scene::castRay(const Ray& ray, int depth) const
{
    Intersection inter = intersect(ray);
    if (!inter.happened)
    {
        return Vector3f(0.0f, 0.0f, 0.0f);
    }
    // 如果光线直接打在光源上(且是直接从相机射出的光线)
    if (inter.m->hasEmission())
    {
        // 间接光照打到光源的情况已在 L_dir 中采样计算过,所以深度大于 0 时不重复计算
        // 如果有 BSDF 等材质,这样写可能会漏掉一些路径。例如 相机 -> 镜子 -> 光源
        return depth == 0 ? inter.m->getEmission() : Vector3f(0.0f, 0.0f, 0.0f);
    }
    Vector3f L_dir(0.0f, 0.0f, 0.0f);
    Vector3f L_indir(0.0f, 0.0f, 0.0f);
    // 直射光采样 (L_dir)
    Intersection light_pos;
    float pdf_light = 0.0f;
    sampleLight(light_pos, pdf_light);
    Vector3f p = inter.coords;
    Vector3f xx = light_pos.coords;
    Vector3f ws = normalize(xx - p);
    //Vector3f wo = normalize(-ray.direction);
    Vector3f wo = normalize(ray.direction);
    // 加上一个小偏移量避免自交引起的表面噪点 (Shadow acne)
    Vector3f p_shifted = p + inter.normal * 0.0005f;
    if (dotProduct(ray.direction, inter.normal) > 0)
    {
        p_shifted = p - inter.normal * 0.0005f;
    }
    float dist_to_light = (xx - p).norm();
    Intersection test_inter = intersect(Ray(p_shifted, ws));
    // 如果交点距离光源足够近,说明没有被其它物体遮挡
    if (test_inter.happened && dist_to_light - test_inter.distance < 0.01f)
    {
        Vector3f f_r = inter.m->eval(wo, ws, inter.normal);
        float cosn = std::max(0.0f, dotProduct(ws, inter.normal));
        float cosnn = std::max(0.0f, dotProduct(-ws, light_pos.normal));
        L_dir = light_pos.emit * f_r * cosn * cosnn / (dist_to_light * dist_to_light) / pdf_light;
    }
    // 间接光追踪 (L_indir)
    if (get_random_float() <= RussianRoulette)
    {
        Vector3f wi = inter.m->sample(wo, inter.normal).normalized();
        Ray indir_ray(p_shifted, wi);
        Intersection ininter = intersect(indir_ray);
        // 如果打到了物体,并且打到的不是光源(避免 Double Counting)
        if (ininter.happened && !ininter.m->hasEmission())
        {
            float pdf_indir = inter.m->pdf(wo, wi, inter.normal);
            // 避免极端除 0 导致的亮点 (Fireflies)
            if (pdf_indir > 0.0001f) 
            {
                Vector3f f_r_indir = inter.m->eval(wo, wi, inter.normal);
                float cos_indir = std::max(0.0f, dotProduct(wi, inter.normal));
                // 递归调用 castRay 来得到弹射后路径对该点的辐射度
                Vector3f q_radiance = castRay(indir_ray, depth + 1);
                L_indir = q_radiance * f_r_indir * cos_indir / pdf_indir / RussianRoulette;
            }
        }
    }
    return L_dir + L_indir;
}

# Assignment8

质点弹簧系统

# 安装 freetype,配置 CMake

如果已有 freetype,就可以跳过这一步

如果没有 vcpkg,需要先克隆

git clone https://github.com/microsoft/vcpkg

在 vcpkg 目录中打开 cmd 执行以下命令

vcpkg install freetype:x64-windows
vcpkg integrate install

打开 Project -> CMake Settings,编辑 CMakePresets.json,在其中加入 CMAKE_TOOLCHAIN_FILE 项

{
    "version": 3,
    "configurePresets": [
        {
            "name": "x64-debug",
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/build",
            "cacheVariables": {
                "CMAKE_TOOLCHAIN_FILE": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
            }
        }
    ]
}

# 实现

1、在 rope.cpp 中,实现 Rope 类的构造函数

Rope::Rope(Vector2D start, Vector2D end, int num_nodes, float node_mass, float k, vector<int> pinned_nodes)
{
    // TODO (Part 1): Create a rope starting at `start`, ending at `end`, and containing `num_nodes` nodes.
    //Comment-in this part when you implement the constructor
    for (int i = 0; i < num_nodes; i++)
    {
		double x = start.x + (end.x - start.x) * (double)i / (double)(num_nodes - 1);
		double y = start.y + (end.y - start.y) * (double)i / (double)(num_nodes - 1);
		Vector2D position = Vector2D(x, y);
		masses.push_back(new Mass(position, node_mass, false));
    }
    for (int i = 0; i < num_nodes - 1; i++)
    {
        springs.push_back(new Spring(masses[i], masses[i + 1], k));
    }
    for (auto& i : pinned_nodes)
    {
        masses[i]->pinned = true;
    }
}

2、在 Rope::simulateEuler() 中,首先实现胡克定律。遍历所有的弹簧,对弹簧两端的质点施加正确的弹簧力。对每个质点,累加所有的弹簧力。

void Rope::simulateEuler(float delta_t, Vector2D gravity)
{
    for (auto &s : springs)
    {
        //TODO (Part 2): Use Hooke's law to calculate the force on a node
        Vector2D dir = s->m2->position - s->m1->position;
        Vector2D f = s->k * dir / dir.norm() * (dir.norm() - s->rest_length);
        
		s->m1->forces = s->m1->forces + f;
        s->m2->forces = s->m2->forces - f;
    }
    for (auto &m : masses)
    {
        if (!m->pinned)
        {
            // TODO (Part 2): Add the force due to gravity, then compute the new velocity and position
			Vector2D a = m->forces / m->mass + gravity;
			m->velocity += a * delta_t;
			m->position += m->velocity * delta_t;
            // TODO (Part 2): Add global damping
            m->velocity += -0.0005 * m->velocity;
        }
        // Reset all forces on each mass
        m->forces = Vector2D(0, 0);
    }
}

3、在 Rope::simulateVerlet() 中,实现显示 Verlet 方法积分的胡克定律

void Rope::simulateVerlet(float delta_t, Vector2D gravity)
{
    for (auto &s : springs)
    {
        // TODO (Part 3): Simulate one timestep of the rope using explicit Verlet (solving constraints)
        Vector2D dir = s->m2->position - s->m1->position;
        Vector2D f = s->k * dir / dir.norm() * (dir.norm() - s->rest_length);
        s->m1->forces = s->m1->forces + f;
        s->m2->forces = s->m2->forces - f;
    }
    for (auto &m : masses)
    {
        if (!m->pinned)
        {
            Vector2D temp_position = m->position;
            // TODO (Part 3.1): Set the new position of the rope mass
            // TODO (Part 4): Add global Verlet damping
            Vector2D a = m->forces / m->mass + gravity;
            m->position = temp_position + (1 - 0.0005) * (temp_position - m->last_position) + a * delta_t * delta_t;
            m->last_position = temp_position;
        }
        // Reset all forces on each mass
        m->forces = Vector2D(0, 0);
    }
}