玩命加载中 . . .

GAMES-101HW代码归档


图形学课程作业代码归档

作业0:虚拟机的配置和VSCode基本使用

本次作业主要是实现一个homogeneous coordinate下的点(2,1)的旋转45度和平移(1,2),十分简单,主要目的是在虚拟机环境下的编译(但是虚拟机太卡了建议自行配置link文件,只需要下载eigen3和openCV即可)

#include<cmath>
#include<eigen3/Eigen/Core>
#include<eigen3/Eigen/Dense>
#include<iostream>
int main() {
    float sqrt2_div_2 = sqrt(2) / 2;
    Eigen::Vector3f P(2.0f, 1.0f, 1.0f);
    Eigen::Matrix3f rotate_and_trans;
    rotate_and_trans << sqrt2_div_2, sqrt2_div_2, 1.0, -sqrt2_div_2, sqrt2_div_2, 2.0, 0.0, 0.0, 1.0;
    P = rotate_and_trans * P;
    //std::cout << P << std::endl;
    return 0;
}

注意线性变换是从右往左看的哦(经过手算和输出后答案是对的)。

作业1:旋转与投影

本次作业的任务是填写一个旋转矩阵和一个透视投影矩阵,打分如下:

  1. [5 分] 正确构建模型矩阵;

  2. [5 分] 正确构建透视投影矩阵;

  3. [10 分] 你的代码可以在现有框架下正确运行,并能看到变换后的三角形;

  4. [10 分] 当按 A 键与 D 键时,三角形能正确旋转。或者正确使用命令行得 到旋转结果图像;

  5. [提高项 5 分] 在 main.cpp 中构造一个函数,该函数的作用是得到绕任意过原点的轴的旋转变换矩阵。

    Eigen::Matrix4f get_rotation(Vector3f axis, float angle)

具体代码(不会包含有模板,需要自行判断在哪里,后续一致)

  1. 模型矩阵:指对于z轴旋转的函数,根据我GAMES101的文章内写有三个轴旋转矩阵,具体代码如下

    Eigen::Matrix4f get_model_matrix(float angle){
        Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
        angle = angle * 180 / 3.14;
        model <<  cos(angle) ,  -sin(angle) ,  0 ,  0  ,
                  sin(angle) ,   cos(angle) ,  0 ,  0  ,  
                       0     ,      0       ,  1 ,  0  ,
                       0     ,      0       ,  0 ,  1  ;
        // TODO: Implement this function
        // Create the model matrix for rotating the triangle around the Z axis.
        // Then return it.
        return model;
    }
  2. 正确构建透视投影矩阵:如下列代码所示,注意return的矩阵乘法中,第二项不return可能在本地显示的会更好一些。

    Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) {
        // Students will implement this function
        float n = -zNear;
        float f = -zFar;
        float t = n * std::tan(0.5f * eye_fov / 180.f * MY_PI);
        float b = -t;
        float r = aspect_ratio * t;
        float l = -r;
        Eigen::Matrix4f perspective_to_orthogonal = Eigen::Matrix4f::Identity();
    
        perspective_to_orthogonal << zNear ,   0   ,       0      ,        0     ,
                                       0   , zNear ,       0      ,        0     , 
                                       0   ,   0   , zNear + zFar , -zNear * zFar,
                                       0   ,   0   ,      -1      ,        0     ;
        Eigen::Matrix4f move_to_center = Eigen::Matrix4f::Identity();
        move_to_center << 1 , 0 , 0 , -(r-l)/2.f ,
                          0 , 1 , 0 , -(t-b)/2.f ,
                          0 , 0 , 1 , -(n-f)/2.f ,
                          0 , 0 , 0 ,       1    ;
    
        Eigen::Matrix4f scale_to_canonical = Eigen::Matrix4f::Identity();
        scale_to_canonical << 2.f/(r-l) ,     0     ,     0     , 0 ,
                                 0      , 2.f/(t-b) ,     0     , 0 ,
                                 0      ,    0      , 2.f/(n-f) , 0 ,
                                 0      ,    0      ,     0     , 1 ;
    
        // TODO: Implement this function
        // Create the projection matrix for the given parameters.
        // Then return it.
    
        return scale_to_canonical * move_to_center * perspective_to_orthogonal;
    }
  3. 提高部分:其公式选自于Rodrigues’ Rotation Formula,因此只需要按照对应公示即可(注:由于没有参加到实时批改,因此此部分正确性不敢保证)

    Eigen::Matrix4f get_rotation(Vector3f axis, float angle) {
        Eigen::Matrix4f rotation,dual_matrix;
        dual_matrix <<     0    , -axis.z() , axis.y() , 0 , 
                       axis.z() ,     0     , -axis.x(), 0 ,
                      -axis.y() ,  axis.x() ,     0    , 0 ,
                            0   ,     0     ,     0    , 1 ;
        rotation << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
        Vector4f A;
        A << axis.x(), axis.y(), axis.z(), 0;
        Eigen::Matrix4f I = Eigen::Matrix4f::Identity();
        rotation = std::cos(angle / 180.f * MY_PI) * I+ (1 - std::cos(angle / 180.f * MY_PI)) * A * A.transpose()+ std::sin(angle / 180.f * MY_PI) * dual_matrix;
        return rotation;
    }

作业2:光栅化三角形

  1. [5 分] 正确地提交所有必须的文件,且代码能够编译运行。
  2. [20 分] 正确实现三角形栅格化算法。
  3. [10 分] 正确测试点是否在三角形内。
  4. [10 分] 正确实现 z-buffer 算法, 将三角形按顺序画在屏幕上。
  5. [提高项 5 分] 用 super-sampling 处理 Anti-aliasing : 你可能会注意 到,当我们放大图像时,图像边缘会有锯齿感。我们可以用 super-sampling 来解决这个问题,即对每个像素进行 2 * 2 采样,并比较前后的结果 (这里 并不需要考虑像素与像素间的样本复用)。需要注意的点有,对于像素内的每 一个样本都需要维护它自己的深度值,即每一个像素都需要维护一个 sample list。最后,如果你实现正确的话,你得到的三角形不应该有不正常的黑边。

具体代码

  1. 关于rasterizer的代码:

    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 xmax = std::max(t.v[0].x(), std::max(t.v[1].x(), t.v[2].x()));
        float ymax = std::max(t.v[0].y(), std::max(t.v[1].y(), t.v[2].y()));
        float xmin = std::min(t.v[0].x(), std::min(t.v[1].x(), t.v[2].x()));
        float ymin = std::min(t.v[0].y(), std::min(t.v[1].y(), t.v[2].y()));
        xmax = (int)xmax + 1, xmin = (int)xmin - 1, ymax = (int)ymax + 1, ymin = (int)ymin - 1;
        //std::cout << xmax << ymax << xmin << ymin << std::endl;
        for (int i = xmin; i <= xmax; ++i)
        {
            for (int j = ymin; j <= ymax; ++j)
            {
            // 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;
                if (insideTriangle((float)i + 0.5f, (float)j + 0.5f, t.v)) 
                {
                    float alpha, beta, gamma;
                    auto k = computeBarycentric2D(i, j, t.v);
                    std::tie(alpha, beta, gamma) = k;
                    //static std::tuple<float, float, float> computeBarycentric2D(float x, float y, const Vector3f* 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.
                }
            }
        }
    }
  2. 关于判定点是否在三角形内:使用向量叉积进行判定

    static bool insideTriangle(int x, int y, const Vector3f* _v)
    {   
        // TODO : Implement this function to check if the point (x, y)
        //is inside the triangle represented by _v[0], _v[1], _v[2]
        Eigen::Vector2f p(x, y);
        Eigen::Vector2f AB = _v[1].head(2) - _v[0].head(2);
        Eigen::Vector2f BC = _v[2].head(2) - _v[1].head(2);
        Eigen::Vector2f CA = _v[0].head(2) - _v[2].head(2);
        Eigen::Vector2f AP = p - _v[0].head(2);
        Eigen::Vector2f BP = p - _v[1].head(2);
        Eigen::Vector2f CP = p - _v[2].head(2);
        return (AB[0] * AP[1] - AB[1] * AP[0] > 0)
            && (BC[0] * BP[1] - BC[1] * BP[0] > 0)
            && (CA[0] * CP[1] - CA[1] * CP[0] > 0);
    }
  3. 关于z-buffer的实现:在(1)中_TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted._处添加下面的代码

    if (depth_buf[get_index(i, j)] > z_interpolated) {
        Vector3f color = t.getColor();
        Vector3f point((float)i, (float)j, z_interpolated);
        depth_buf[get_index(i, j)] = z_interpolated;
        set_pixel(point, color);
    }
  4. 提高部分——超采样(MSAA),实际上就是把每一个格子细分称成四个格子,需要的代码如下

    首先是需要增加一个预处理的格子细分:

     std::vector<Eigen::Vector2f> memo{
        {0.25,0.25},
        {0.75,0.25},
        {0.25,0.75},
        {0.75,0.75},
    };

    然后将循环体更换为下面的部分

    std::vector<Eigen::Vector2f> memo
    {
        {0.25,0.25},
        {0.75,0.25},
        {0.25,0.75},
        {0.75,0.75},
    };
    float minDepth = FLT_MAX;
    int count = 0;
    for (int l = 0; l < 4; l++)
    {
        if (insideTriangle((float)i + memo[l][0], (float)j + memo[l][1], t.v))
        {
            float alpha, beta, gamma;
            auto k = computeBarycentric2D((float)i + memo[l][0], (float)j + memo[l][1], t.v);
            std::tie(alpha, beta, gamma) = k;
            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;
            minDepth = std::min(minDepth, z_interpolated);
            count++;
        }
    }
    if (count != 0)
    {
        if (depth_buf[get_index(i, j)] > minDepth)
        {
            Vector3f color = t.getColor() * count / 4.0;
            Vector3f point((float)i, (float)j, minDepth);
            depth_buf[get_index(i, j)] = minDepth;
            set_pixel(point, color);
        }
    }

作业3:Pipeline and Shading

  1. [5 分] 提交格式正确,包括所有需要的文件。代码可以正常编译、执行。
  2. [10 分] 参数插值: 正确插值颜色、法向量、纹理坐标、位置 (Shading Position) 并将它们传递给 fragment_shader_payload.
  3. [20 分]Blinn-phong 反射模型: 正确实现 phong_fragment_shader 对应的 反射模型。
  4. [5 分] Texture mapping: 将 phong_fragment_shader 的代码拷贝到 texture_fragment_shader, 在此基础上正确实现 Texture Mapping.
  5. [10 分] Bump mapping 与 Displacement mapping: 正确实现 Bump mapping 与 Displacement mapping.
  6. [Bonus 3 分] 尝试更多模型: 找到其他可用的.obj 文件,提交渲染结果并 把模型保存在 /models 目录下。这些模型也应该包含 Vertex Normal 信息。
  7. [Bonus 5 分] 双线性纹理插值: 使用双线性插值进行纹理采样, 在 Texture 类中实现一个新方法 Vector3f getColorBilinear(float u, float v) 并 通过 fragment shader 调用它。为了使双线性插值的效果更加明显,你应该 考虑选择更小的纹理图。请同时提交纹理插值与双线性纹理插值的结果,并进行比较

具体代码

  1. 参数插值

    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:
        // TODO : Find out the bounding box of current triangle.
        // iterate through the pixel and find if the current pixel is inside the triangle
        //    * 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
        auto v = t.toVector4();
        float xmax = std::max(t.v[0].x(), std::max(t.v[1].x(), t.v[2].x()));
        float ymax = std::max(t.v[0].y(), std::max(t.v[1].y(), t.v[2].y()));
        float xmin = std::min(t.v[0].x(), std::min(t.v[1].x(), t.v[2].x()));
        float ymin = std::min(t.v[0].y(), std::min(t.v[1].y(), t.v[2].y()));
        xmax = (int)xmax + 1, xmin = (int)xmin, ymax = (int)ymax + 1, ymin = (int)ymin;
        for (int i = xmin; i <= xmax; ++i) {
            for (int j = ymin; j <= ymax; ++j) {
                if (insideTriangle((float)i + 0.5f, (float)j + 0.5f, t.v)) {
                    auto [alpha, beta, gamma] = computeBarycentric2D((float)i + 0.5, j + 0.5, 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;
        // 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);
                    if (zp < depth_buf[get_index(i, j)]){
                        depth_buf[get_index(i, j)] = zp;
                        auto interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1);// color 
                        auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1).normalized();// normal
                        auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);// texture
                        auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1);// shadingcoords
                        fragment_shader_payload payload(interpolated_color, interpolated_normal, interpolated_texcoords, texture ? &*texture : nullptr);
                        payload.view_pos = interpolated_shadingcoords;
                        auto pixel_color = fragment_shader(payload);
                        set_pixel(Eigen::Vector2i(i, j), pixel_color);
                    }
                }
            }
        }
    }
  2. Blinn-Phong 模型

    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_dir = light.position - point;
            Eigen::Vector3f view_dir = eye_pos - point;
            float r = light_dir.dot(light_dir);
            // ambient
            Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
            // diffuse
            Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r);
            Ld *= std::max(0.0f, normal.normalized().dot(light_dir.normalized()));
            // specular
            Eigen::Vector3f h = (light_dir + view_dir).normalized();
            Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r);
            Ls *= std::pow(std::max(0.0f, normal.normalized().dot(h)), p);
            result_color += (La + Ld + Ls);
        }
    
        return result_color * 255.f;
    }
  3. 纹理映射

    注意此处需要修改getcolor相关的函数,使得$u,v\in[0,1]$

    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->getColorBilinear(payload.tex_coords.x(), payload.tex_coords.y());
        }
        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_dir = light.position - point;
            Eigen::Vector3f view_dir = eye_pos - point;
            float r = light_dir.dot(light_dir);
            // ambient
            Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
            // diffuse
            Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r);
            Ld *= std::max(0.0f, normal.normalized().dot(light_dir.normalized()));
            // specular
            Eigen::Vector3f h = (light_dir + view_dir).normalized();
            Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r);
            Ls *= std::pow(std::max(0.0f, normal.normalized().dot(h)), p);
            result_color += (La + Ld + Ls);
        }
        return result_color * 255.f;
    }
  4. 凹凸贴图

    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(), y = normal.y(), 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 <<  t.x(), b.x(), normal.x(),
                t.y(), b.y(), normal.y(),
                t.z(), b.z(), normal.z();
    
        float u = payload.tex_coords.x();
        float v = payload.tex_coords.y();
        float w = payload.texture->width;
        float h = payload.texture->height;
    
        float dU = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm());
        float dV = kh * kn * (payload.texture->getColor(u, v + 1.0f / h).norm() - payload.texture->getColor(u, v).norm());
        Eigen::Vector3f ln{ -dU,-dV,1.0f };
        normal = TBN * ln;
        Eigen::Vector3f result_color = normal.normalized();
    
        return result_color * 255.f;
    }
  5. 位移贴图

    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(), y = normal.y(), 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 << t.x(), b.x(), normal.x(),
               t.y(), b.y(), normal.y(),
               t.z(), b.z(), normal.z();
    
        float u = payload.tex_coords.x();
        float v = payload.tex_coords.y();
        float w = payload.texture->width;
        float h = payload.texture->height;
        float dU = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm());
        float dV = kh * kn * (payload.texture->getColor(u, v + 1.0f / h).norm() - payload.texture->getColor(u, v).norm());
    
        Eigen::Vector3f ln{ -dU,-dV,1.0f };
    
        point += (kn * normal * payload.texture->getColor(u, v).norm());
        normal = TBN * ln;
        normal = 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.
            Eigen::Vector3f light_dir = light.position - point;
            Eigen::Vector3f view_dir = eye_pos - point;
            float r = light_dir.dot(light_dir);
            // ambient
            Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
            // diffuse
            Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r);
            Ld *= std::max(0.0f, normal.normalized().dot(light_dir.normalized()));
            // specular
            Eigen::Vector3f h = (light_dir + view_dir).normalized();
            Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r);
            Ls *= std::pow(std::max(0.0f, normal.normalized().dot(h)), p);
            result_color += (La + Ld + Ls);
        }
        return result_color * 255.f;
    }
  6. 双线性插值

    //in texture.hpp
        Eigen::Vector3f getColorBilinear(float u, float v)
        {
            if (u < 0) u = 0;
            if (u > 1) u = 1;
            if (v < 0) v = 0;
            if (v > 1) v = 1;
            auto u_img = u * width;
            auto v_img = (1 - v) * height;
    
            int umin = (int)u_img;
            int umax = std::min(width, (int)u_img + 1);
            int vmin = (int)v_img;
            int vmax = std::min(height, (int)v_img + 1);
    
            auto row_1 = image_data.at<cv::Vec3b>(vmax, umin);
            auto row_2 = image_data.at<cv::Vec3b>(vmax, umax);
            //row
            //column
            auto column_1 = image_data.at<cv::Vec3b>(vmin, umin);
            auto column_2 = image_data.at<cv::Vec3b>(vmin, umax);
    
            float row = (u_img - umin) / (umax - umin);
            float column = (v_img - vmax) / (vmin - vmax);
            auto bottom_color = (1 - row) * row_1 + row * row_2;
            auto top_color = (1 - row) * column_1 + row * column_2;
            auto color = (1 - column) * bottom_color + column * top_color;
    
            return Eigen::Vector3f(color[0], color[1], color[2]);
        }

作业4:Bézier 曲线

  1. [5 分] 提交的格式正确,包含所有必须的文件。代码可以编译和运行

  2. [20 分] De Casteljau 算法: 对于给定的控制点,你的代码能够产生正确的 Bézier 曲线

  3. [5 分] 奖励分数: 实现对 Bézier 曲线的反走样。(对于一个曲线上的点,不只把它对应于一个像素,你需要根据到像素中心的距离来考虑与它相邻的像素的颜色。)

    实际上本次作业就是De Casteljau 算法的实现

具体代码

De Casteljau算法的实现

具体数学推导见我的另外一篇文章,注意是递归的方法,可能会比较慢。

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
    // TODO: Implement de Casteljau's algorithm
    if (control_points.size() == 2)
    {
        return control_points[0] + t * (control_points[1] - control_points[0]);
    }

    std::vector<cv::Point2f> vec;
    for (int i = 0; i < control_points.size() - 1; i++)
    {
        vec.push_back(control_points[i] + t * (control_points[i + 1] - control_points[i]));
    }

    return recursive_bezier(vec, 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 (double t = 0.0; t <= 1.0; t += 0.0001)
    {
        cv::Point2f point = recursive_bezier(control_points, t);
        window.at<cv::Vec3b>(point.y, point.x)[1] = 255;
        //anti-aliasing
        float x = point.x - std::floor(point.x);
        float y = point.y - std::floor(point.y);    
        int x_flag = x < 0.5f ? -1 : 1;
        int y_flag = y < 0.5f ? -1 : 1;
        // 距离采样点最近的4个坐标点
        cv::Point2f p00 = cv::Point2f(std::floor(point.x) + 0.5f, std::floor(point.y) + 0.5f);
        cv::Point2f p01 = cv::Point2f(std::floor(point.x + x_flag * 1.0f) + 0.5f, std::floor(point.y) + 0.5f);
        cv::Point2f p10 = cv::Point2f(std::floor(point.x) + 0.5f, std::floor(point.y + y_flag * 1.0f) + 0.5f);
        cv::Point2f p11 = cv::Point2f(std::floor(point.x + x_flag * 1.0f) + 0.5f, std::floor(point.y + y_flag * 1.0f) + 0.5f);
        std::vector<cv::Point2f> vec;
        vec.push_back(p01);
        vec.push_back(p10);
        vec.push_back(p11);
        // 计算最近的坐标点与采样点距离
        cv::Point2f distance = p00 - point;
        float len = sqrt(distance.x * distance.x + distance.y * distance.y);
        // 对边缘点进行着色
        for (auto p : vec)
        {
            cv::Point2f d = p - point;
            float l = sqrt(d.x * d.x + d.y * d.y);
            float percnet = len / l;
            cv::Vec3d color = window.at<cv::Vec3b>(p.y, p.x);
            color[1] = std::max(color[1], (double)255 * percnet);
            window.at<cv::Vec3b>(p.y, p.x) = color;
        }
    }
}

main函数中的内容更改

    //naive_bezier(control_points, window);
    bezier(control_points, window);

作业5:光线与三角形相交

  1. [5 分] 提交的格式正确,包含所有必须的文件。代码可以编译和运行。
  2. [10 分] 光线生成: 正确实现光线生成部分,并且能够看到图像中的两个球体。
  3. [15 分] 光线与三角形相交: 正确实现了 Moller-Trumbore 算法,并且能够看到图像中的地面。

具体代码

光线生成

void Renderer::Render(const Scene& scene)
{
    //前面省略
    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
            // 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;
            Vector3f direction(x, y, -1);
            direction = normalize(direction); // Don't forget to normalize this direction!
            framebuffer[m++] = castRay(eye_pos, direction, scene, 0);
        }
        UpdateProgress(j / (float)scene.height);
    }
    //后面省略   
}

光线与三角形求交

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 S = orig - v0;
    Vector3f S1 = crossProduct(dir, E2);
    Vector3f S2 = crossProduct(S, E1);

    float k = dotProduct(S1, E1);
    tnear = 1.0f / k * dotProduct(S2, E2);
    u = 1.0f / k * dotProduct(S1, S);
    v = 1.0f / k * dotProduct(S2, dir);

    if (tnear > 0 && v >= 0 && v <= 1 && u >= 0 && u <= 1) return true;
    else return false;
}

作业6:加速结构

  1. [5 points] 提交格式正确,包含所有需要的文件;代码可以在虚拟机下正确 编译运行。
  2. [20 points] 包围盒求交:正确实现光线与包围盒求交函数。
  3. [15 points] BVH 查找:正确实现 BVH 加速的光线与场景求交。
  4. [加分项 20 points] SAH 查找:自学 SAH(Surface Area Heuristic) , 正 确实现 SAH 加速,并且提交结果图片,并在 README.md 中说明 SVH 的实现 方法,并对比 BVH、SVH 的时间开销。(可参考 http://15462.courses.cs.cmu.edu/fall2015/lecture/acceleration/slide_024,也可以查找其他资料)

具体代码

包围盒求交

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
    //x
    float xmin = (pMin.x - ray.origin.x) * invDir.x, xmax = (pMax.x - ray.origin.x) * invDir.x;
    //y
    float ymin = (pMin.y - ray.origin.y) * invDir.y, ymax = (pMax.y - ray.origin.y) * invDir.y;
    //z
    float zmin = (pMin.z - ray.origin.z) * invDir.z, zmax = (pMax.z - ray.origin.z) * invDir.z;;

    //判断是否反向
    if (!dirIsNeg[0]){
        float t = xmin, xmin = xmax, xmax = t;
    }
    if (!dirIsNeg[1]){
        float t = ymin, ymin = ymax, ymax = t;
    }
    if (!dirIsNeg[2]){
        float t = zmin, zmin = zmax, zmax = t;
    }

    //进出包围盒的时间
    float T_Enter = std::max(xmin, std::max(ymin, zmin));
    float T_Exit = std::min(xmax, std::min(ymax, zmax));

    if (T_Enter <= T_Exit && T_Exit >= 0) return true;
    return false;
}

BVH树的查找

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    // 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
    // 命名采用的是求交的名字,此处是简单的前序遍历
    Vector3f invDir = Vector3f{ 1.0f / ray.direction.x, 1.0f / ray.direction.y, 1.0f / ray.direction.z };
    std::array<int, 3> dirIsNeg = { ray.direction.x > 0, ray.direction.y > 0, ray.direction.z > 0 };
    if (!node->bounds.IntersectP(ray, invDir, dirIsNeg))
    {
        return {};
    }

    if (node->left == nullptr && node->right == nullptr)
    {
        return node->object->getIntersection(ray);
    }

    Intersection BVH_Left = getIntersection(node->left, ray);
    Intersection BVH_Right = getIntersection(node->right, ray);

    return BVH_Left.distance < BVH_Right.distance ? BVH_Left : BVH_Right;
}

SAH查找(暂略)

作业7:路径追踪

(史上最难,因为我第一次学习写多线程。。)

  1. [5 points] 提交格式正确,包含所有需要的文件;代码可以在虚拟机下正确 编译运行。
  2. [45 points] Path Tracing:正确实现 Path Tracing 算法,并提交分辨率 不小于 512*512,采样数不小于 8 的渲染结果图片。
  3. [加分项 10 points] 多线程:将多线程应用在 Ray Generation 上,注意实现时可能涉及的冲突。
  4. [加分项 10 points] Microfacet:正确实现 Microfacet 材质,并提交可 体现 Microfacet 性质的渲染结果。

具体代码

Path Tracing主要代码

//in Scene.cpp

// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
    /*
    intersect(const Ray ray)in Scene.cpp: 求一条光线与场景的交点

    sampleLight(Intersection pos, float pdf)inScene.cpp: 在场景的所有光源上按面积uniform、地、sample、一个点,并
    计算该 sample 的概率密度

    sample(const Vector3f wi, const Vector3f N) in Material.cpp: 按照该材质的性质,给定入射方向与法向量,用某种分
    布采样一个出射方向

    pdf(const Vector3f wi, const Vector3f wo, const Vector3f N)inMaterial.cpp: 给定一对入射、出射方向与法向量,计
    算 sample 方法得到该出射 方向的概率密度

    eval(const Vector3f wi, const Vector3f wo, const Vector3f N)inMaterial.cpp: 给定一对入射、出射方向与法向量,计
    算这种情况下的 f_r 值

    可能用到的变量有:
    RussianRoulette in Scene.cpp: P_RR, Russian Roulette 的概率
    */

    // TO DO Implement Path Tracing Algorithm here
    /*
    shade(p, wo)
        sampleLight(inter, pdf_light)
        Get x, ws, NN, emit from inter
        Shoot a ray from p to x 
        If the ray is not blocked in the middle
            L_dir = emit * eval(wo, ws, N) * dot(ws, N) * dot(ws, NN) / |x-p|^2 / pdf_light
        L_indir = 0.0 
        Test Russian Roulette with probability RussianRoulette
        wi = sample(wo, N)
        Trace a ray r(p, wi)
        If ray r hit a non-emitting object at q
            L_indir = shade(q, wi) * eval(wo, wi, N) * dot(wi, N) / pdf(wo, wi, N) / RussianRoulette
        Return L_dir + L_indir
    */

    //sampleLight(inter, pdf_light)
    /*
    Intersection的类成员
    bool happened;
    Vector3f coords;
    Vector3f tcoords;
    Vector3f normal;
    Vector3f emit;
    double distance;
    Object* obj;
    Material* meterial;
    */
    Intersection Light_Inter;
    float Light_PDF = 0.0f;
    sampleLight(Light_Inter, Light_PDF);

    Intersection Object_Inter = intersect(ray);
    //Check
    if (!Object_Inter.happened)
    {
        return {};
    }
    if (Object_Inter.meterial->hasEmission())
    {
        return Object_Inter.meterial->getEmission();
    }

    //Get object, ws, NN, emit from inter
    Vector3f Object_To_Light = Light_Inter.coords - Object_Inter.coords;
    Vector3f Object_To_Light_Direction = Object_To_Light.normalized();
    float Distance_square = Object_To_Light.x * Object_To_Light.x + Object_To_Light.y * Object_To_Light.y + Object_To_Light.z * Object_To_Light.z;

    //Shoot a ray from p to x 
    Ray Object_To_Light_Ray = { Object_Inter.coords, Object_To_Light_Direction };
    Intersection t = intersect(Object_To_Light_Ray);

    //L_dir = 0.0
    Vector3f L_dir = { 0, 0, 0 };

    //If the ray is not blocked in the middle
    if (t.distance - Object_To_Light.norm() > -EPSILON)
    {
        //L_dir = emit * eval(wo, ws, N) * dot(ws, N) * dot(ws, NN) / |x-p|^2 / pdf_light
        L_dir = Light_Inter.emit * Object_Inter.meterial->eval(ray.direction, Object_To_Light_Direction, Object_Inter.normal) * dotProduct(Object_To_Light_Direction, Object_Inter.normal) * dotProduct(-Object_To_Light_Direction, Light_Inter.normal) / Distance_square / Light_PDF;
    }

    //L_indir = 0.0
    Vector3f L_indir = { 0, 0, 0 };

    //Test Russian Roulette with probability RussianRoulette
    //俄罗斯轮盘赌
    if (get_random_float() > RussianRoulette)
    {
        return L_dir;
    }

    //wi = sample(wo, N)
    Vector3f Object_To_Next_Object = Object_Inter.meterial->sample(ray.direction, Object_Inter.normal).normalized();

    //Trace a ray r(p, wi)
    Ray Object_To_Next_Ray = { Object_Inter.coords, Object_To_Next_Object };
    Intersection nextObjInter = intersect(Object_To_Next_Ray);

    //If ray r hit a non-emitting object at q
    if (nextObjInter.happened && !nextObjInter.meterial->hasEmission())
    {
        //L_indir = shade(q, wi) * eval(wo, wi, N) * dot(wi, N) / pdf(wo, wi, N) / RussianRoulette
        float pdf = Object_Inter.meterial->pdf(ray.direction, Object_To_Next_Object, Object_Inter.normal);
        L_indir = castRay(Object_To_Next_Ray, depth + 1) * Object_Inter.meterial->eval(ray.direction, Object_To_Next_Object, Object_Inter.normal) * dotProduct(Object_To_Next_Object, Object_Inter.normal) / pdf / RussianRoulette;
    }

    //Return L_dir + L_indir
    return L_dir + L_indir;
}

需要迁移的内容见HM6,直接C/V

  1. Triangle::getIntersection in Triangle.hpp
  2. IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg) in the Bounds3.hpp(*注意检查 t_enter = t_exit *
  3. getIntersection(BVHBuildNode* node, const Ray ray)inBVH.cpp

关于多线程

为了保证数据不产生竞争,需要灵活地使用mutex和锁,同时数据传输也需要进行一次打包防止中断,使用的是std::Thread的部分内容和晚上查阅的材料

  1. 关于Render.hpp

    //
    // Created by goksu on 2/25/20.
    //
    #pragma once
    #include "Scene.hpp"
    #include <functional>
    #include <queue>
    
    struct hit_payload
    {
        float tNear;
        uint32_t index;
        Vector2f uv;
        Object* hit_obj;
    };
    
    struct Thread_Data_List
    {
        int m;
        Vector3f eye_pos;
        Vector3f dir;
        int depth;
        int SPP;
        unsigned int id;
    };
    
    struct Threads
    {
        Thread_Data_List Thread_data;
        std::function<void(Thread_Data_List)> Thread_Function;
    };
    
    class Renderer
    {
    public:
        void Render(const Scene& scene);
        void CastRayTask(Thread_Data_List);
    
        //多线程相关
        void Run(int id);
        void Add_Thread(const Threads&);
    
    private:
        Scene* renderer_scene;
        std::vector<Vector3f> framebuffer;
    
        //多线程相关
        bool Exit_Thread;
        unsigned int Thread_ID;
        std::queue<Threads> Thread_Queue;
    };
  2. 对应的.cpp文件

    void Renderer::Render(const Scene& scene)
    {
        Exit_Thread = false;
        std::vector<std::thread*> threads;
        for (int i = 0; i < MAX_THREAD_SIZE; i++)
        {
            std::thread* t = new std::thread([=] {Run(i); });
            threads.push_back(t);
        }
    
        renderer_scene = (Scene*)(&scene);
        framebuffer.resize(scene.width * scene.height);
    
        float scale = tan(deg2rad(scene.fov * 0.5));
        float imageAspectRatio = scene.width / (float)scene.height;
        Vector3f eye_pos(278, 273, -800);
        int m = 0;
        Thread_ID = 0;
    
        // change the spp value to change sample ammount
        int SPP = 200;
        std::cout << "SPP: " << SPP << "\n";
        for (uint32_t j = 0; j < scene.height; ++j)
        {
            for (uint32_t i = 0; i < scene.width; ++i)
            {
                for (int k = 0; k < SPP; ++k)
                {
                    // generate primary ray direction
                    float x = (2 * (i + get_random_float()) / (float)scene.width - 1) * imageAspectRatio * scale;
                    float y = (1 - 2 * (j + get_random_float()) / (float)scene.height) * scale;
                    Vector3f dir = normalize(Vector3f(-x, y, 1));
    
                    Thread_Data_List Thread_Data = Thread_Data_List{ m++, eye_pos, dir, 0, SPP };
                    auto Thread_Function = std::bind(&Renderer::CastRayTask, this, std::placeholders::_1);
                    Threads Ready_Thread = { Thread_Data, Thread_Function };
                    Add_Thread(Ready_Thread);
                }
            }
        }
    
        Exit_Thread = true;
        for (auto& t : threads)
        {
            //上一个线程结束后,再开启下一个线程
            t->join();
        }
        //后续不变
    }
    
    void Renderer::Add_Thread(const Threads& t)
    {
        while (true)
        {
            std::unique_lock Lock(_lock);
            if (Thread_Queue.size() < MAX_QUEUE_SIZE)
            {
                Thread_Queue.push(t);
                Lock.unlock();
                _no_empty.notify_all();
                break;
            }
            else
            {
                _no_full.wait(Lock);
            }
        }
    }
    
    void Renderer::Run(int id)
    {
        while (true)
        {
            //线程上锁防止竞争
            std::unique_lock Lock(_lock);
            if (!Thread_Queue.empty())
            {
                Threads t = Thread_Queue.front();
                Thread_Queue.pop();
    
                Lock.unlock();
                t.Thread_data.id = id;
                t.Thread_Function(t.Thread_data);
    
                //唤醒
                _no_full.notify_all();
            }
            else if (Exit_Thread)
            {
                Lock.unlock();
                break;
            }
            else
            {
                _no_empty.wait_for(Lock, std::chrono::milliseconds(50));
            }
        }
    }
    
    void Renderer::CastRayTask(Thread_Data_List data)
    {
        // ProtoType
        // framebuffer[m] += scene.castRay(Ray(eye_pos, dir), 0) / spp;
        std::unique_lock Lock(_buf_lock);
        framebuffer[data.m] += renderer_scene->castRay(Ray(data.eye_pos, data.dir), data.depth) / data.SPP;
        int line = (Thread_ID++) / (renderer_scene->width * data.SPP);
        Lock.unlock();
    }

作业8:弹簧质点系统

  1. [5 分] 提交的格式正确,包含所有必须的文件。代码可以编译和运行。
  2. [5 分] 连接绳子约束,正确的构造绳子
  3. [5 分] 半隐式欧拉法
  4. [5 分] 显式欧拉法
  5. [10 分] 显式 Verlet
  6. [5 分] 阻尼

由于虚拟机早已删除,同时windows下无法使用<unistd.h>,因此暂时咕咕咕,啥时候补的话,看心情咯


文章作者: AleXandrite
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 AleXandrite !
  目录