【GAMES101】作业3 Pipeline and Shading

写在前面:时隔多年,研究生还是决定入坑图形学,亦是爱好使然,也是专业所需,希望研究生三年可以在图形学领域有所建树,更希望自己可以在这个领域坚持下去。

这次文章从闫老师GAMES101的作业3开始,之前的两个作业已经完成了,但由于比较懒,所以没有写成文章出来,看看后续有没有机会补上。

作业3内容很多,题目是Pipeline and Shading,图形管线和着色,实际上基本包含了之前作业的全部知识点,先把作业要求贴出来:

话不多说,逐一开始。

1.修改rasterize_triangle函数

该函数的作用是对模型中的每个三角形进行栅格化,与作业2相比,增加了法向量(normal)、颜色(color)、纹理(texcoords)以及(shadingcoords)的插值部分,shadingcoords的作用可能一开始不是很了解,之后再详细解释一下。

//Screen space rasterization
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos) 
{
    auto v = t.toVector4();
    using namespace std;
    // 求三角形最小外接矩形
    float x_min = INT_MAX;
    float x_max = INT_MIN;
    float y_min = INT_MAX;
    float y_max = INT_MIN;
    for (auto & v: t.v) {
        x_min = min(v.x(), x_min);
        x_max = max(v.x(), x_max);
        y_min = min(v.y(), y_min);
        y_max = max(v.y(), y_max);
    }
    // 逐个像素点处理
    for (int x = x_min; x <= x_max; x++)
    {
        for (int y = y_min; y <= y_max; y++)
        {
            if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v)) 
            {
                // 计算重心坐标
                auto abg = computeBarycentric2D((float)x + 0.5, (float)y + 0.5, t.v);
                // float alpha = std::get<0>(abg);
                // float beta = std::get<1>(abg);
                // float gamma = std::get<2>(abg);
                float alpha, gamma, beta;
                tie(alpha, beta, gamma) = abg;
                //z-buffer插值
                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 (z_interpolated < depth_buf[get_index(x, y)])
                {
                    Eigen::Vector2i p = { (float)x,(float)y};
                    // 颜色插值
                    auto interpolated_color = interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1);
                    // 法向量插值
                    auto interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1);
                    // 纹理颜色插值
                    auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);
                    // 内部点位置插值
                    auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1);
                    fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
                    payload.view_pos = interpolated_shadingcoords;
                    auto pixel_color = fragment_shader(payload);
                    set_pixel(p, pixel_color); //设置颜色
                    depth_buf[get_index(x, y)] = z_interpolated;//更新z值
                }
            }
        }
    }
}

求三角形最小外接矩形时,由于三角形的顶点使用Vector4f定义,所以顶点值为浮点数,求x,y最大最小值时也需要定义为float类型,才能使用min和max函数进行比较。

逐个像素点处理时,需要判断外接矩形中每一个像素点是否再三角形内,对于每一个三角形内部的点,通过重心坐标公式求出alpha,beta,gamma,在进行插值求出z-buffer的结果。

如果当前像素的z_interpolated值小于深度缓冲中的值,则需要对该像素点进行着色和处理,interpolated插值函数已经在框架中写好了,通过重心坐标得到该像素位置的颜色、纹理、法向量以及shadingcoords插值结果。

2.修改get_pojection_matrix函数

不必多说,作业1和作业2都使用到了,投影包含两个步骤,正交投影和透视投影。

透视投影可以理解为将视锥压缩为一个立方体

正交投影包含移动、归一化,将立方体移动到原点,在归一化为[-1, 1]³的规则立方体(cubic)

Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
    // TODO: Use the same projection matrix from the previous assignments
    Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
    float fovY = eye_fov / 180 * MY_PI;
    float t = abs(zNear) * tan(fovY / 2);
    float b = -1.0 * t;
    float r = aspect_ratio * t;
    float l = -1.0 * r;
    // orthoMatrix
    Eigen::Matrix4f orthoScaleMatrix = Eigen::Matrix4f::Identity();
    orthoScaleMatrix << 2 / (r - l), 0, 0, 0,
                   0, 2 / (t - b), 0, 0,
                   0, 0, 2 / (zNear - zFar), 0,
                   0, 0, 0, 1;
    Eigen::Matrix4f orthoShiftMatrix = Eigen::Matrix4f::Identity();
    orthoShiftMatrix << 1, 0, 0, -1.0 * (r + l) / 2,
                        0, 1, 0, -1.0 * (t + b) / 2,
                        0, 0, 1, -1.0 * (zNear + zFar) / 2,
                        0, 0, 0, 1;
    // perspectMatrix
    Eigen::Matrix4f perspMatrix = Eigen::Matrix4f::Identity();
    perspMatrix << zNear, 0, 0, 0,
                   0, zNear, 0, 0,
                   0, 0, zNear + zFar, -zNear * zFar,
                   0, 0, 1, 0;
    
    projection = orthoScaleMatrix * orthoShiftMatrix * perspMatrix;

    return projection;
}

由于openCV的坐标系标准和课程中有所不同,所以结果的图像是上下颠倒的。

 3.修改phong_fragment_shader函数

Blinn-Phong反射模型中,光照强度分为三个部分:环境光、漫反射光、高光,公式如上。

环境光 = 环境光系数 * 光照强度

漫反射光 = 漫反射系数 * 到达该点的光照强度(I/r²)* 光线向量法线夹角

高光 = 高光系数 * 到达该点的光照强度(I/r²)* 半程向量法线夹角,p为幂数项,p越大,高光面积越小

要注意的是,计算半程向量的过程中光线向量、视线向量要进行归一化(normalize)处理;计算点乘的过程中,法线向量、光线向量、半程向量要进行归一化处理。

Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);  // ambient
    Eigen::Vector3f kd = payload.color;  // diffuse
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);  // specular

    auto l1 = light{{20, 20, 20}, {500, 500, 500}};  // {{position}, {intensity}}
    auto l2 = light{{-20, 20, 0}, {500, 500, 500}};

    std::vector<light> lights = {l1, l2};
    Eigen::Vector3f amb_light_intensity{10, 10, 10};  // ambient light
    Eigen::Vector3f eye_pos{0, 0, 10};  // v

    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 l = light.position - point;
        Eigen::Vector3f v = eye_pos - point;
        Eigen::Vector3f h = l.normalized() + v.normalized();
        Eigen::Vector3f n = normal.normalized();
        auto r_square = l.dot(l);
        auto ambient = ka.cwiseProduct(amb_light_intensity);
        auto diffuse = kd.cwiseProduct(light.intensity / r_square) * std::max(0.0f, n.dot(l.normalized()));
        auto specular = ks.cwiseProduct(light.intensity / r_square) * std::pow(std::max(0.0f, n.dot(h.normalized())), p);
        result_color += (ambient + diffuse + specular);
    }
    return result_color * 255.f;
}

简单说一下cwiseProduct函数,该函数返回两个矩阵同位置的元素分别相乘的新矩阵。

理论上Blinn-Phong反射模型得到的结果为一个值,但为了方便矩阵计算,所以phong_fragment_shader函数返回值为Vector3f类型,是一个1×3的向量,其中每个元素值相同,均等于反射模型的结果值。

 

 4.修改函数texture_fragment_shader,将公式中的kd替换为纹理颜色

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.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 l = light.position - point;
        Eigen::Vector3f v = eye_pos - point;
        Eigen::Vector3f h = l.normalized() + v.normalized();
        Eigen::Vector3f n = normal.normalized();
        auto r_square = l.dot(l);
        auto ambient = ka.cwiseProduct(amb_light_intensity);
        auto diffuse = kd.cwiseProduct(light.intensity / r_square) * std::max(0.0f, n.dot(l.normalized()));
        auto specular = ks.cwiseProduct(light.intensity / r_square) * std::pow(std::max(0.0f, n.dot(h.normalized())), p);
        result_color += (ambient + diffuse + specular);
    }

    return result_color * 255.f;
}

与Blinn-Phong反射模型函数差别不大,仅仅是修改了kd的值,变成了纹理颜色,其余代码全部照搬。关于怎么获取纹理颜色替代kd,涉及到纹理映射的知识点,纹理说到底就是一张图(贴图),每个三角形的顶点都对应纹理贴图上的一个颜色。

 不过具体的映射函数就不再我们的考虑范围之内了。

5、6分别为实现凹凸贴图和位移贴图,在课程上闫老师也没有细说实现方法,需要自己在课外额外花时间完成这两个问题,打算放到之后再详细说说这两问,关于作业3的前4个任务就到此结束,下一篇文章再继续补充(争取不鸽)。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值