【Games101】作业3总结

前言

本次作业有一定难度,真的汗流浃背了。做了好几天,看了各种博客,也不敢保证做得很好,但基本的也搞懂了课上的原理

作业要求

在这里插入图片描述
pdf文档说的很清楚了,原理基本上课堂都讲过,其他博客都有更好的答案,这里只讲讲原理和注意事项

任务1:实现插值

一开始还没有理解插值的真正含义是什么,但学习了P9的课程内容后,我可以用大白话来解释:插值其实就是使用算法对某些值代入计算得到的内部均值

比如三角形的颜色插值,求到的就是一个在三个顶点颜色之间的比较均匀的颜色
在这里插入图片描述

作业具体代码

首先你会发现实际上代码模板用的是作业二没有优化锯齿的代码,这其实是因为不需要优化锯齿;然后插值部分直接调用写好的函数就行,对于最后一个参数为1实际是做透视矫正;最后是那个坐标得是2i,不然会有奇怪的错误
参考博客:作业3解题

void rst::rasterizer::rasterize_triangle(const Triangle& t, const array<Vector3f, 3>& view_pos) 
{
    auto v = t.toVector4();
    int minx, maxx, miny, maxy;     //定义一个三角形的覆盖面boundingBox
    minx = min(v[0].x(), min(v[1].x(), v[2].x()));
    maxx = max(v[0].x(), max(v[1].x(), v[2].x()));
    miny = min(v[0].y(), min(v[1].y(), v[2].y()));
    maxy = max(v[0].y(), max(v[1].y(), v[2].y()));

    //循环判断每个点是否在三角形内
    for (int y = miny; y <= maxy; y++)
    {
        for (int x = minx; x <= maxx; x++)
        {
            if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v)) //用像素中心点当像素坐标
            {
                //求深度插值
                float alpha, beta, gamma;
                tie(alpha, beta, gamma) = computeBarycentric2D(x + 0.5, y + 0.5, t.v);  //取tuple值用tie流式比较简单
                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(x, y);
                if (z_interpolated < depth_buf[index]) //判断当前z值是否小于原来z表此位置的z值
                {
                    depth_buf[index] = z_interpolated; //更新z值
                    // TODO: Interpolate the attributes:
                    // auto interpolated_color
                    // auto interpolated_normal
                    // auto interpolated_texcoords
                    // auto interpolated_shadingcoords
                    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);
                    
                    // 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);
                    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);

                    Vector2i pi = {x,y}; //作业二是三维坐标,在本次作业是二维
                    set_pixel(pi, pixel_color);
                }
            }
        }
    }
}

任务2:投影矩阵

实验二做的没问题,这里应该也没啥问题,如果出现奶牛倒立,可能就是实验二的n和f没有设置正确

Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar)
{
    float n = zNear;       //右手系,n应该比f大,但传入参数n比f小
    float f = zFar;

    Matrix4f PerToOr = Matrix4f::Identity();    //这里是距离,n和f不用变
    PerToOr <<
        n, 0, 0, 0,
        0, n, 0, 0,
        0, 0, n + f, -1.0 * n * f,
        0, 0, 1, 0;

    n = -n;
    f = f;

    float angle = 0.5 * eye_fov * MY_PI / 180;
    float t = n * tan(angle);
    float b = -t;
    float r = t * aspect_ratio;
    float l = -r;


    //平移
    Matrix4f translate;
    translate <<
        1, 0, 0, -(r + l) / 2,
        0, 1, 0, -(t + b) / 2,
        0, 0, 1, -(n + f) / 2,
        0, 0, 0, 1;

    //标准化
    Matrix4f nor;
    nor <<
        2 / (r - l), 0, 0, 0,
        0, 2 / (t - b), 0, 0,
        0, 0, 2 / (n - f), 0,
        0, 0, 0, 1;

    Matrix4f projection = Matrix4f::Identity();

    //先转换为正交投影变成立方体,再平移到原点,再标准化
    return nor * translate * PerToOr * projection;
}

任务3:实现phong_fragment_shader()

首先需要清楚公式,如下
在这里插入图片描述

求环境光La

在这里插入图片描述

由于环境光来自四面八方不好精确计算,所以代码中老师已经给出了光强和系数ka求大致的值

求慢反射光Ld

在这里插入图片描述
如图,首先需要求3个法向量(注意方向和归一化),其次要求平面和光源的距离,这个距离需要用未归一化的I向量来求,Eigen中有norm方法可以求

求高光Ls

在这里插入图片描述
很明显,相比慢反射光,需要多求一个h向量,之后基本和漫反射差不多

PS:别忘了计算指数P

作业代码

Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
    Vector3f ka = Vector3f(0.005, 0.005, 0.005);
    Vector3f kd = payload.color;
    Vector3f ks = 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}};

    vector<light> lights = {l1, l2};
    Vector3f amb_light_intensity{10, 10, 10};
    Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Vector3f color = payload.color;
    Vector3f point = payload.view_pos;
    Vector3f normal = payload.normal;

    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 I = light.intensity;
        Vector3f L = (light.position - point).normalized();   //入射光向量
        Vector3f V = (- point).normalized();
        Vector3f N = normal.normalized();
        Vector3f h = (V + L).normalized();      //与老师的公式略有不同,其实确定了方向直接归一化就行

        float r = (light.position-point).norm();           //入射光长度
        float R = r * r;

        Vector3f La = ka.cwiseProduct(amb_light_intensity);
        Vector3f Ld = kd.cwiseProduct(I / R) * max(0.f, N.dot(L));
        Vector3f Ls = ks.cwiseProduct(I / R) * pow(max(0.f, N.dot(h)), p);

        result_color += La + Ld + Ls;
    }

    return result_color * 255.f;
}

唯一需要注意的就是Vector3f V = (- point).normalized();不需要用eye_pos再减去像素位置了,因为已经经过了MV变换

任务4:实现texture_fragment_shader()

没啥好说的,就是用纹理的坐标更换颜色的坐标
在这里插入图片描述

Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
{
    Vector3f return_color = {0, 0, 0};
    if (payload.texture)
    {
        // TODO: Get the texture value at the texture coordinates of the current fragment
        Vector2f tex = payload.tex_coords;
        return_color = payload.texture->getColor(tex.x(), tex.y());
    }
    Vector3f texture_color;
    texture_color << return_color.x(), return_color.y(), return_color.z();

    Vector3f ka = Vector3f(0.005, 0.005, 0.005);
    Vector3f kd = texture_color / 255.f;
    Vector3f ks = 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}};

    vector<light> lights = {l1, l2};
    Vector3f amb_light_intensity{10, 10, 10};
    Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Vector3f color = texture_color;
    Vector3f point = payload.view_pos;
    Vector3f normal = payload.normal;

    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 I = light.intensity;
        Vector3f L = (light.position - point).normalized();   //入射光向量
        Vector3f V = (- point).normalized();
        Vector3f N = normal.normalized();
        Vector3f h = (V + L).normalized();      //与老师的公式略有不同,其实确定了方向直接归一化就行

        //float r = (light.position - point).norm();           //入射光长度求法1,先求长度但由于根号,导致精度低
        //float R = r * r;
        float R = (light.position - point).dot(light.position - point); //入射光长度求法2,直接求平方精度高

        Vector3f La = ka.cwiseProduct(amb_light_intensity);
        Vector3f Ld = kd.cwiseProduct(I / R) * max(0.f, N.dot(L));
        Vector3f Ls = ks.cwiseProduct(I / R) * pow(max(0.f, N.dot(h)), p);

        result_color += La + Ld + Ls;
    }

    return result_color * 255.f;
}

任务5:实现bump_fragment_shader()

最难的就在这里,老师上课其实说过原理,bump shader实际就只是改变了贴图的高度进而改变法向量的高度,造成了一种凹凸感去欺骗人眼
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码

Vector3f bump_fragment_shader(const fragment_shader_payload& payload)
{
    Vector3f ka = Vector3f(0.005, 0.005, 0.005);
    Vector3f kd = payload.color;
    Vector3f ks = 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} };

    vector<light> lights = { l1, l2 };
    Vector3f amb_light_intensity{ 10, 10, 10 };
    Vector3f eye_pos{ 0, 0, 10 };

    float p = 150;
    Vector3f color = payload.color;
    Vector3f point = payload.view_pos;
    Vector3f normal = payload.normal;

    float kh = 0.2, kn = 0.1;

    // TODO: Implement bump mapping here

    float x = normal.x(), y = normal.y(), z = normal.z();
    Vector3f t = { x * y / sqrt(x * x + z * z),sqrt(x * x + z * z),z * y / sqrt(x * x + z * z) };
    Vector3f b = normal.cross(t);

    //TBN [t,b,n]
    Matrix3f TBN;       
    TBN <<
        t.x(), b.x(), x,
        t.y(), b.y(), y,
        t.z(), b.z(), z;

    float h = payload.texture->height;  //整个纹理贴图的高度
    float w = payload.texture->width;
    float u = payload.tex_coords.x();   //纹理具体的点的u方向值
    float v = payload.tex_coords.y();

    // dU = kh * kn * (h(u+1/w,v)-h(u,v))
    // dV = kh * kn * (h(u,v+1/h)-h(u,v))
    float du, dv;
    du = kh * kn * (payload.texture->getColorBilinear(u + 1.0f / w, v).norm() - payload.texture->getColorBilinear(u, v).norm());
    dv = kh * kn * (payload.texture->getColorBilinear(u, v + 1.0f / h).norm() - payload.texture->getColorBilinear(u, v).norm());
    
    // Vector ln = (-dU, -dV, 1)
    // Normal n = normalize(TBN * ln)
    Vector3f ln = { -du,-dv,1.0f };
    normal = (TBN * ln).normalized();

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

    return result_color * 255.f;
}

关键在于理解代码和拆分代码,一个一个解决问题,先求TBN,再求du和dv,最后按照代码提示归一化

任务6:实现 displacement_fragment_shader()

这个没啥好说的,在凹凸贴图的基础上实现Phong Shader

代码

Vector3f displacement_fragment_shader(const fragment_shader_payload& payload)
{
    
    Vector3f ka = Vector3f(0.005, 0.005, 0.005);
    Vector3f kd = payload.color;
    Vector3f ks = 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}};

    vector<light> lights = {l1, l2};
    Vector3f amb_light_intensity{10, 10, 10};
    Vector3f eye_pos{0, 0, 10};

    float p = 150;

    Vector3f color = payload.color; 
    Vector3f point = payload.view_pos;
    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();
    Vector3f t = { x * y / sqrt(x * x + z * z),sqrt(x * x + z * z),z * y / sqrt(x * x + z * z) };
    Vector3f b = normal.cross(t);

    //TBN [t,b,n]
    Matrix3f TBN;
    TBN <<
        t.x(), b.x(), x,
        t.y(), b.y(), y,
        t.z(), b.z(), z;

    float h = payload.texture->height;
    float w = payload.texture->width;
    float u = payload.tex_coords.x();
    float v = payload.tex_coords.y();

    // dU = kh * kn * (h(u+1/w,v)-h(u,v))
    // dV = kh * kn * (h(u,v+1/h)-h(u,v))
    float du, dv;
    du = kh * kn * (payload.texture->getColor(u + 1.0f / w, v).norm() - payload.texture->getColor(u, v).norm()); //u,v坐标限定在[0,1]
    dv = kh * kn * (payload.texture->getColor(u, v + 1.0f / h).norm() - payload.texture->getColor(u, v).norm());

    // Vector ln = (-dU, -dV, 1)
    // Normal n = normalize(TBN * ln)
    Vector3f ln = { -du,-dv,1.0f };

    // Position p = p + kn * n * h(u,v)
    point += (kn * normal * payload.texture->getColor(u, v).norm());
    
    normal = (TBN * ln).normalized();
    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 I = light.intensity;
        Vector3f L = (light.position - point).normalized();   //入射光向量
        Vector3f V = (-point).normalized();
        Vector3f N = normal.normalized();
        Vector3f h = (V + L).normalized();      //与老师的公式略有不同,其实确定了方向直接归一化就行

        //float r = (light.position - point).norm();           //入射光长度求法1,先求长度但由于根号,导致精度低
        //float R = r * r;
        float R = (light.position - point).dot(light.position - point); //入射光长度求法2,直接求平方精度高

        Vector3f La = ka.cwiseProduct(amb_light_intensity);
        Vector3f Ld = kd.cwiseProduct(I / R) * max(0.f, N.dot(L));
        Vector3f Ls = ks.cwiseProduct(I / R) * pow(max(0.f, N.dot(h)), p);

        result_color += La + Ld + Ls;
    }

    return result_color * 255.f;
}

总结

可以这么说,这次作业的难度主要在凹凸贴图的实现,也就是bump shading,其他的只要上课注意听和做笔记,多理解一下代码的提示也还好

参考博客

1.代码过程详细理解
2.作业3解题
3.作业问题整理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值