这里回顾GAMES101 HW3,这次作业的内容是Pipeline and Shading。

课程主页:

课程作业:

课程视频:

参考资料:

rasterize_triangle

对上一讲的代码进行了修改,主要改动如下:

  • 支持对其他指标的插值;
  • 支持ssaa;
    • 返回的结果为$\sum_{i\in triangleq} s_i / \sum_{i\in triangleq}$;
    • 上一讲返回的结果为$\sum_{i\in triangleq} s_i / n^2$;
    • 即只关于三角形内部的点求均值;
    • ssaa的分别率通过最后一个命令行参数传入;
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();
    
    // TODO : Find out the bounding box of current triangle.
    // bounding box
    float x_min = 1e10;
    float x_max = -1e10;
    float y_min = 1e10;
    float y_max = -1e10;
    for (int i = 0; i < 3; i++) {
        x_min = std::min(x_min, t.v[i](0));
        x_max = std::max(x_max, t.v[i](0));
        y_min = std::min(y_min, t.v[i](1));
        y_max = std::max(y_max, t.v[i](1));
    }
    int x_min_int = int(x_min);
    int x_max_int = int(x_max) + 1;
    int y_min_int = int(y_min);
    int y_max_int = int(y_max) + 1;
    

    // 支持ssaa
    float x, y;
    // 间距
    float d = 1.0 / n;
    for (int i = x_min_int; i <= x_max_int; i++) {
        for (int j = y_min_int; j <= y_max_int; j++) {
            // 记录super-sample在三角形内的数量
            int cnt = 0;
            // 记录块内的最小深度
            float z_interpolated_min = 1e10;
            // msaa内的平均值
            Eigen::Vector3f interpolated_color = Eigen::Vector3f::Zero();
            Eigen::Vector3f interpolated_normal = Eigen::Vector3f::Zero();
            Eigen::Vector2f interpolated_texcoords = Eigen::Vector2f::Zero();
            Eigen::Vector3f interpolated_shadingcoords = Eigen::Vector3f::Zero();
            // 记录在区域内的个数
            float m = 0;
            // 区域内的平均坐标
            float x_mean = 0;
            float y_mean = 0;
            // 遍历每个super-sample
            for (int p = 0; p < n; p++) {
                for (int q = 0; q < n; q++) {
                    x = i + (p + 0.5) * d;
                    y = j + (q + 0.5) * d;
                    x_mean += x;
                    y_mean += y;
                    // 在三角形内则更新cnt
                    if (insideTriangle(x, y, t.v)) {
                        cnt++;
                        // 计算插值深度值
                        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_interpolated_min = std::min(z_interpolated_min, z_interpolated);
                        // 计算个指标的插值结果
                        interpolated_color = interpolated_color + interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1);
                        interpolated_normal = interpolated_normal + interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1);
                        interpolated_texcoords = interpolated_texcoords + interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);
                        interpolated_shadingcoords = interpolated_shadingcoords + interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1);
                        // 更新个数
                        m += 1;
                    }
                }
            }

            // 计算均值
            interpolated_color = interpolated_color / m;
            interpolated_normal = interpolated_normal / m;
            interpolated_texcoords = interpolated_texcoords / m;
            interpolated_shadingcoords = interpolated_shadingcoords / m;
            x_mean /= m;
            y_mean /= m;

            int index = get_index(i, j);
            // 如果平均点在三角形内则生成结果
            if (insideTriangle(x_mean, y_mean, t.v)) {
                // 越小越近
                if (z_interpolated_min < depth_buf[index]) {
                    // 得到颜色
                    auto payload = fragment_shader_payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
                    payload.view_pos = interpolated_shadingcoords;
                    auto pixel_color = fragment_shader(payload);
                    // 设置颜色
                    Eigen::Vector2i point(i, j);
                    set_pixel(point, pixel_color);
                    // 更新深度
                    depth_buf[index] = z_interpolated_min;
                }
            }
        }
    }
}

测试:

./Rasterizer normal_1.png normal 1

./Rasterizer normal_2.png normal 2

phong_fragment_shader

首先结合下图进行参数说明:

  • point: 光线打在物体上位置,即上图中三条线的交点;
  • light.position: 光源位置;
  • normal: $\mathrm n$;
  • eye_pos:图中眼睛的位置;

计算公式:

代码:

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{{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;

    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.

        Eigen::Vector3f light_direction = (light.position - point);
        Eigen::Vector3f viewer_direction = (eye_pos - point);

        Eigen::Vector3f intensity = light.intensity / light_direction.squaredNorm();

        light_direction = light_direction.normalized();
        viewer_direction = viewer_direction.normalized();
        
        Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        Eigen::Vector3f diffuse = std::max(0.0f, normal.dot(light_direction)) * kd.cwiseProduct(intensity);
        Eigen::Vector3f h = (light_direction + viewer_direction).normalized();
        Eigen::Vector3f specular = std::pow(std::max(0.0f, normal.dot(h)), p) * ks.cwiseProduct(intensity);
        result_color += ambient + diffuse + specular;
    }

    return result_color * 255.f;
}

测试:

./Rasterizer phong_1.png phong 1

./Rasterizer phong_2.png phong 2

texture_fragment_shader

和之前唯一的区别是改变color,具体方式为利用getColor方法:

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
        return_color = payload.texture->getColor(payload.tex_coords[0], payload.tex_coords[1]);
    }
    
    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.
        
        Eigen::Vector3f light_direction = (light.position - point);
        Eigen::Vector3f viewer_direction = (eye_pos - point);

        Eigen::Vector3f intensity = light.intensity / light_direction.squaredNorm();

        light_direction = light_direction.normalized();
        viewer_direction = viewer_direction.normalized();
        
        Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        Eigen::Vector3f diffuse = std::max(0.0f, normal.dot(light_direction)) * kd.cwiseProduct(intensity);
        Eigen::Vector3f h = (light_direction + viewer_direction).normalized();
        Eigen::Vector3f specular = std::pow(std::max(0.0f, normal.dot(h)), p) * ks.cwiseProduct(intensity);
        result_color += ambient + diffuse + specular;
    }

    return result_color * 255.f;
}

测试:

./Rasterizer texture_1.png texture 1

./Rasterizer texture_2.png texture 2

bump_fragment_shader

这一部分主要参考lecture 10的15到17页的内容,这里要做一点说明:

  • $h$为一个预设的函数,这里助教设置为h(u,v)=texture_color(u,v).norm,该函数是人为随意定义的,没有理论含义;
  • $kh, kn$是为了控制生成结果;
  • $t$为切线方向,即曲线方程为$f(x,y, z) = y\sqrt{x ^2 +z^2 }$,暂时不知道该方程是人为设置的,还是推导出来的;

代码:

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();
    Eigen::Vector3f t(x * y / std::sqrt(x * x + z * z),
                      std::sqrt(x * x + z * z),
                      z * y / std::sqrt(x * x + z * z));
    Eigen::Vector3f b = normal.cross(t);
    Eigen::Matrix3f TBN;
    TBN.col(0) = t;
    TBN.col(1) = b;
    TBN.col(2) = normal;
    float u = payload.tex_coords[0];
    float v = payload.tex_coords[1];
    float w = payload.texture->width;
    float h = payload.texture->height;
    // 人为选择的函数
    float huv = payload.texture->getColor(u, v).norm();
    // kh, kn为预设的值, 是为了控制最后颜色
    float dU = kh * kn * (payload.texture->getColor(u + 1.0 / w, v).norm() - huv);
    float dV = kh * kn * (payload.texture->getColor(u, v + 1.0 / h).norm() - huv);
    Eigen::Vector3f ln(-dU, -dV, 1);
    normal = (TBN * ln).normalized();

    Eigen::Vector3f result_color = {0, 0, 0};
    result_color = normal;

    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.
        
        Eigen::Vector3f light_direction = (light.position - point);
        Eigen::Vector3f viewer_direction = (eye_pos - point);

        Eigen::Vector3f intensity = light.intensity / light_direction.squaredNorm();

        light_direction = light_direction.normalized();
        viewer_direction = viewer_direction.normalized();
        
        Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        Eigen::Vector3f diffuse = std::max(0.0f, normal.dot(light_direction)) * kd.cwiseProduct(intensity);
        Eigen::Vector3f h = (light_direction + viewer_direction).normalized();
        Eigen::Vector3f specular = std::pow(std::max(0.0f, normal.dot(h)), p) * ks.cwiseProduct(intensity);
        result_color += ambient + diffuse + specular;
    }

    return result_color * 255.f;
}

测试:

./Rasterizer bump_1.png bump 1

./Rasterizer bump_2.png bump 2

displacement_fragment_shader

和前一部分类似,唯一的区别是这里更新的是point:

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 point = point + kn * n * h(u,v)
    // Normal n = normalize(TBN * ln)

    float x = normal.x();
    float y = normal.y();
    float z = normal.z();
    Eigen::Vector3f t(x * y / std::sqrt(x * x + z * z),
                      std::sqrt(x * x + z * z),
                      z * y / std::sqrt(x * x + z * z));
    Eigen::Vector3f b = normal.cross(t);
    Eigen::Matrix3f TBN;
    TBN.col(0) = t;
    TBN.col(1) = b;
    TBN.col(2) = normal;
    float u = payload.tex_coords[0];
    float v = payload.tex_coords[1];
    float w = payload.texture->width;
    float h = payload.texture->height;
    // 人为选择的函数
    float huv = payload.texture->getColor(u, v).norm();
    // kh, kn为预设的值, 是为了控制最后颜色
    float dU = kh * kn * (payload.texture->getColor(u + 1.0 / w, v).norm() - huv);
    float dV = kh * kn * (payload.texture->getColor(u, v + 1.0 / h).norm() - huv);
    Eigen::Vector3f ln(-dU, -dV, 1);
    point = point + kn * normal * huv;
    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.
        
        Eigen::Vector3f light_direction = (light.position - point);
        Eigen::Vector3f viewer_direction = (eye_pos - point);

        Eigen::Vector3f intensity = light.intensity / light_direction.squaredNorm();

        light_direction = light_direction.normalized();
        viewer_direction = viewer_direction.normalized();
        
        Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        Eigen::Vector3f diffuse = std::max(0.0f, normal.dot(light_direction)) * kd.cwiseProduct(intensity);
        Eigen::Vector3f h = (light_direction + viewer_direction).normalized();
        Eigen::Vector3f specular = std::pow(std::max(0.0f, normal.dot(h)), p) * ks.cwiseProduct(intensity);
        result_color += ambient + diffuse + specular;
    }

    return result_color * 255.f;
}

测试:

./Rasterizer bump_1.png bump 1

./Rasterizer bump_2.png bump 2

getColorBilinear

和texture_fragment_shader类似,区别是将getColor修改为getColorBilinear:

Eigen::Vector3f bilinear_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
        return_color = payload.texture->getColorBilinear(payload.tex_coords[0], payload.tex_coords[1]);
    }
    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.
        
        Eigen::Vector3f light_direction = (light.position - point);
        Eigen::Vector3f viewer_direction = (eye_pos - point);

        Eigen::Vector3f intensity = light.intensity / light_direction.squaredNorm();

        light_direction = light_direction.normalized();
        viewer_direction = viewer_direction.normalized();
        
        Eigen::Vector3f ambient = ka.cwiseProduct(amb_light_intensity);
        Eigen::Vector3f diffuse = std::max(0.0f, normal.dot(light_direction)) * kd.cwiseProduct(intensity);
        Eigen::Vector3f h = (light_direction + viewer_direction).normalized();
        Eigen::Vector3f specular = std::pow(std::max(0.0f, normal.dot(h)), p) * ks.cwiseProduct(intensity);
        result_color += ambient + diffuse + specular;
    }

    return result_color * 255.f;
}

然后是getColorBilinear的实现,这部分需要参考lecture 9的17到22页,具体代码如下:

Eigen::Vector3f getColorBilinear(float u, float v)
{
    float u_img = u * width;
    float v_img = (1 - v) * height;
    int u_img_min = (int) u_img;
    int u_img_max = u_img_min + 1;
    int v_img_min = (int) v_img;
    int v_img_max = v_img_min + 1;
    float s = u_img - u_img_min;
    float t = v_img - v_img_min;

    auto c00 = img2Color(v_img_min, u_img_min);
    auto c10 = img2Color(v_img_min, u_img_max);
    auto c01 = img2Color(v_img_max, u_img_min);
    auto c11 = img2Color(v_img_max, u_img_max);

    auto c0 = lerp(s, c00, c10);
    auto c1 = lerp(s, c01, c11);
    auto color = lerp(t, c0, c1);
    
    return color;
}

Eigen::Vector3f lerp(float s, Eigen::Vector3f a, Eigen::Vector3f b)
{
    return a + s * (b - a);
}

测试:

./Rasterizer bump_1.png bump 1

./Rasterizer bump_2.png bump 2