Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)

文章目录

  • 前言
  • 一、作业要求
  • 二、渲染管线分析
    • 1.main函数
      • 1.1main.cpp的功能:
    • 2.draw函数
      • 2.1draw函数的作用:
      • 2.2viewspace下的顶点的法向量
    • ​​​​​​3.rasterize_triangle函数
      • 3.1rasterize_triangle函数实现的整体思路:
      • 3.2一些注意的地方:
  • 三、着色模型分析
    • 1.normal着色模型
      • 1.1normal着色模型的分析
    • 2.phone着色模型
      • 2.1phone着色模型的分析
    • 3.texture着色模型
      • 3.1texture着色模型的分析
    • 4.bump着色模型
      • 4.1bump着色模型的分析
    • 5.displacement着色模型
  • 四、提高部分(双线性插值)
    • 双线性插值原理和分析:

前言

这是个人第一次的图形学相关知识的博客分享,主要内容是Games101作业3的渲染管线梳理和分析、作业要求任务的实现和一些相关知识的补充,若有不足,还请指正。

一、作业要求

在这次编程任务中,我们会进一步模拟现代图形技术。我们在代码中添加了ObjectLoader(用于加载三维模型),VertexShader与FragmentShader,并且支持了纹理映射。而在本次实验中,你需要完成的任务是:

1.修改函数rasterize_triangle(constTriangle&t)inrasterizer.cpp:在此处实现与作业2类似的插值算法,实现法向量、颜色、纹理颜色的插值。

2.修改函数get_projection_matrix()inmain.cpp:将你自己在之前的实验中实现的投影矩阵填到此处,此时你可以运行./Rasterizeroutput.pngnormal来观察法向量实现结果。

3.修改函数phong_fragment_shader()inmain.cpp:实现Blinn-Phong模型计算FragmentColor.

4.修改函数texture_fragment_shader()inmain.cpp:在实现Blinn-Phong的基础上,将纹理颜色视为公式中的kd,实现TextureShadingFragmentShader.

5.修改函数bump_fragment_shader()inmain.cpp:在实现Blinn-Phong的基础上,仔细阅读该函数中的注释,实现Bumpmapping.

6.修改函数displacement_fragment_shader()inmain.cpp:在实现Bumpmapping的基础上,实现displacementmapping.。

二、渲染管线分析

在Shading中,闫老师讲了图形管线的一般流程,概括起来分别是:
1.Vertex Processing 点操作 :将三维空间上的点投影到二维
2.Triangle Processing 三角形操作 :将三个点连成一个三角形/线,构造出一个平面/线,使得原三维空间物体变成由若干三角形组成的物体
3.Raserization 光栅化:进行离散化的采样,用像素表示采样结果
4.Fragment Processing 片元处理 :进行着色处理
5.Framebuffer Operations片元缓冲操作:根据深度信息,确定遮挡和可视情况
​​​​Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第1张图片

接下来我就按渲染管线的顺序,进行此次渲染管线的分析。

1.main函数

代码如下:

int main(int argc, const char** argv)
{
    //用来记录组成三维图形的所有的小三角形
    std::vector<Triangle*> TriangleList;

    float angle = 140.0;
    bool command_line = false;

    std::string filename = "output.png";
    objl::Loader Loader;
    std::string obj_path = "C:/Users/86189/Desktop/c++/Games_assignment/Games101_assignment/Games101_assignment3/Games101_assignment3/models/spot/";
    //此处加上自己文件的储存地址
    // Load .obj File,加载模型
    bool loadout = Loader.LoadFile("C:/Users/86189/Desktop/c++/Games_assignment/Games101_assignment/Games101_assignment3/Games101_assignment3/models/spot/spot_triangulated_good.obj");
    for (auto mesh : Loader.LoadedMeshes)
    {
        //将三维图形中的每一个面(即每一个三角形)的三个点记录下来
        for (int i = 0; i+3 <= mesh.Vertices.size()-1; i += 3)
        {
            Triangle* t = new Triangle();//每次遍历一个面的三个点,便用一个Triangle类的指针去接受新开辟的构造函数(此处无参构造,是初始化顶点)地址,记录三角形信息
            for (int j = 0; j < 3; j++)//对三个顶点进行循环,分别对顶点的顶点信息(包括顶点坐标,顶点法线,顶点对应的纹理坐标等顶点信息)进行赋值
            {
                t->setVertex(j, Vector4f(mesh.Vertices[i + j].Position.X, mesh.Vertices[i + j].Position.Y, mesh.Vertices[i + j].Position.Z, 1.0));//设置三角形顶点位置信息
                t->setNormal(j, Vector3f(mesh.Vertices[i + j].Normal.X, mesh.Vertices[i + j].Normal.Y, mesh.Vertices[i + j].Normal.Z));//设置三角形顶点法线信息
                t->setTexCoord(j, Vector2f(mesh.Vertices[i + j].TextureCoordinate.X, mesh.Vertices[i + j].TextureCoordinate.Y));//设置三角形顶点对应纹理坐标 
            }
            TriangleList.push_back(t);//将每一个小三角形放进TriangleList
        }
    }

    //初始化光栅化的对象,这里定义了屏幕的长度和宽度
    rst::rasterizer r(700, 700);

    //记录纹理到对象
    auto texture_path = "hmap.jpg";//纹理路径
    r.set_texture(Texture(obj_path + texture_path));

    //记录片元处理的方式,这里默认为phong的处理方式
    std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = phong_fragment_shader;//类模板function<>包装函数对象:返回值类型是Vector3f,传入参数类型是fragment_shader_payload

    //根据传入的参数的不同,改变rasterizer对象片元的处理方式
    if (argc >= 2)
    {
        command_line = true;
        filename = std::string(argv[1]);

        if (argc == 3 && std::string(argv[2]) == "texture")
        {
            std::cout << "Rasterizing using the texture shader\n";
            active_shader = texture_fragment_shader;
            texture_path = "spot_texture.png";
            r.set_texture(Texture(obj_path + texture_path));
        }
        else if (argc == 3 && std::string(argv[2]) == "normal")
        {
            std::cout << "Rasterizing using the normal shader\n";
            active_shader = normal_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "phong")
        {
            std::cout << "Rasterizing using the phong shader\n";
            active_shader = phong_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "bump")
        {
            std::cout << "Rasterizing using the bump shader\n";
            active_shader = bump_fragment_shader;
        }
        else if (argc == 3 && std::string(argv[2]) == "displacement")
        {
            std::cout << "Rasterizing using the bump shader\n";
            active_shader = displacement_fragment_shader;
        }
    }

    //人眼所在的位置
    Eigen::Vector3f eye_pos = { 0,0,10 };

    r.set_vertex_shader(vertex_shader);//设置顶点着色方式,将vertex_shader对应数据传入rasterizer类的成员变量
    r.set_fragment_shader(active_shader);//设置片元着色方式,将active_shader对应数据传入rasterizer类的成员变量

    int key = 0;//修改这个值,可以选择输出图片或者动态旋转渲染的模型
    int frame_count = 0;

    if (command_line)
    {
        r.clear(rst::Buffers::Color | rst::Buffers::Depth);//清空两个缓冲区,在最后处理遮挡显示情况时发挥作用
        //分别设置MVP矩阵,进行点的操作,实现将三维的点映射到平面
        r.set_model(get_model_matrix(angle));//前面有定义float angle = 140.0,只是为了将每一个片元的点都旋转一个角度,即使模型旋转一个角度,角度可自定义,也可以修改旋转的函数get_model_matrix()
        r.set_view(get_view_matrix(eye_pos));//这里相当于人眼在屏幕外10个单位,屏幕上显示正常的带点范围,而get_view_matrix()将人眼往z轴的负方向(即屏幕里)前进10个单位,将点也往屏幕内缩进10个单位
        r.set_projection(get_projection_matrix(45.0, 1, 0.1, 50));
        //进行光栅化、片元处理
        r.draw(TriangleList);
        cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);
        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);

        cv::imwrite(filename, image);

        return 0;
    }

    while (key != 27)
    {
        r.clear(rst::Buffers::Color | rst::Buffers::Depth);

        r.set_model(get_model_matrix(angle));
        r.set_view(get_view_matrix(eye_pos));
        r.set_projection(get_projection_matrix(45.0, 1, 0.1, 50));

        //r.draw(pos_id, ind_id, col_id, rst::Primitive::Triangle);
        r.draw(TriangleList);
        cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
        image.convertTo(image, CV_8UC3, 1.0f);
        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);

        cv::imshow("image", image);
        cv::imwrite(filename, image);
        key = cv::waitKey(10);

        if (key == 'a')
        {
            angle -= 0.1;
        }
        else if (key == 'd')
        {
            angle += 0.1;
        }

    }
    return 0;
}



1.1main.cpp的功能:

1.1.1将顶点以三角形的方式每三个记录起来,下图是Triangle类的一些属性,注意留意各个变量的名称和存储的内容,可以发现这一个类里装了三个顶点的顶点坐标、颜色、对应纹理坐标、法线。
​​Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第2张图片

1.1.2设置光栅化器的MVP模型(用于将三维的点变换到屏幕空间)

1.1.3根据调用命令的不同设置光栅化器的着色方式,之后便进入了rasterizer的draw函数。

2.draw函数

代码如下:

void rst::rasterizer::draw(std::vector<Triangle *> &TriangleList) {

    //f1是zFar和zNear之间距离的一半
    float f1 = (50 - 0.1) / 2.0;
    //f2是zFar和zNear的中心点z坐标
    float f2 = (50 + 0.1) / 2.0;

    Eigen::Matrix4f mvp = projection * view * model;//定义MVP变换矩阵,projection、view、model是rasterizer类的成员,主函数已经将MVP三个矩阵的值传进去rasterizer类成员了
    //遍历每一个储存好信息的小三角形
    //这里开始即是对每个小三角形进行MVP、深度插值、着色等操作
    for (const auto& t:TriangleList)
    {
        Triangle newtri = *t;

        //这里只是用mm记录了每一个三角形的三个顶点经过model、view变换后的位置(没有进行透视投影变换),此时的三个顶点的位置是作为视图空间viewspace中每一个三角形的三个顶点坐标!!
        //这里是作为后续确定光源与物体表面位置的用处
        std::array<Eigen::Vector4f, 3> mm {
                (view * model * t->v[0]),
                (view * model * t->v[1]),
                (view * model * t->v[2])
        };

        std::array<Eigen::Vector3f, 3> viewspace_pos;

        //记录viewspace空间下当前三角形的三个点的坐标
        std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {
            return v.template head<3>();
        });

        //更新v[],变为经过MVP变换,投影到屏幕的的点坐标
        //记得像前面一样先记录下经过MV变换的三角形的坐标
        Eigen::Vector4f v[] = {
                mvp * t->v[0],
                mvp * t->v[1],
                mvp * t->v[2]
        };

        //x,y,z同时处于w,得到齐次坐标!!(因为在屏幕上操作都是对齐次坐标进行操作的),但这里没有对w进行处理,还保存着此处的深度,用于光栅化里面的深度插值
        //Homogeneous division
        for (auto& vec : v) {
            vec.x()/=vec.w();
            vec.y()/=vec.w();
            vec.z()/=vec.w();
        }
        
        //这里只是用n[]来表示只经过MV变换,即在viewspace视图空间下的各个顶点的法向量方向
        Eigen::Matrix4f inv_trans = (view * model).inverse().transpose();//inverse()是逆矩阵,transpose()是转置矩阵
        Eigen::Vector4f n[] = {
                inv_trans * to_vec4(t->normal[0], 0.0f),//也是表示为齐次坐标的形式
                inv_trans * to_vec4(t->normal[1], 0.0f),
                inv_trans * to_vec4(t->normal[2], 0.0f)
        };

        //Viewport transformation
        //这里是对屏幕三角形顶点进行x、y、z的变换(拉伸等),调整展现出来的图像大小等,以及同步将所有屏幕三角形位置都移到偏中心的位置,整体屏幕投影就会移到偏中心的位置
        for (auto & vert : v)
        {
            vert.x() = 0.5*width*(vert.x()+1.0);
            vert.y() = 0.5*height*(vert.y()+1.0);//x和y两个方向要进行相同形式的变换,才可以保证三角形显示出来的形状不会被压缩等
            vert.z() = vert.z() * f1 + f2;//深度变换因为是平行投影,图像大小不会变化
                                          //f1是zFar和zNear之间距离的一半
                                          //float f1 = (50 - 0.1) / 2.0;
                                          //f2是zFar和zNear的中心点z坐标
                                          //float f2 = (50 + 0.1) / 2.0;
        }

        //将经过MVP变换、齐次坐标变换、视图变换的屏幕三角形顶点坐标记录在v[]里面
        for (int i = 0; i < 3; ++i)
        {
            //screen space coordinates
            newtri.setVertex(i, v[i]);
        }

        //记录法线信息
        for (int i = 0; i < 3; ++i)
        {
            //view space normal
            newtri.setNormal(i, n[i].head<3>());
        }

        //设置三个顶点的颜色
        newtri.setColor(0, 148,121.0,92.0);
        newtri.setColor(1, 148,121.0,92.0);
        newtri.setColor(2, 148,121.0,92.0);

        // Also pass view space vertice position
        //接下来调用 rasterize_triangle对每一个小三角形进行光栅化,使在屏幕位置的小三角形能够真正显示出来
        //这里也是传入了viewspace_pos,即小三角形未经过透视变换的三个顶点的世界空间坐标,用于后面计算光照
        rasterize_triangle(newtri, viewspace_pos);
    }
}

2.1draw函数的作用:

主要是计算了每个小三角形的相关顶点信息,之后进行光栅化处理。具体计算内容:
2.1.1.对只进行MV操作、在viewspace空间中的顶点坐标,先保存在mm,后传给了viewspace_pos,并传递到rasterizer_tirangle函数中,该坐标的作用在于后续计算光线作用
2.1.2.经过MVP变换的顶点坐标,保存在v,后进行了齐次坐标化,(此时并未处理齐次坐标的w维,因为经过了MVP变换后,w坐标记录了原本的z值,这用于后面进行深度插值)
2.1.3.对于在viewspace下的顶点的法向量,保存在n,目的是在判断光线作用的时候,要知道没有变形的三维物体的形状、位置信息(此时只是进行了模型的MV变换,还没有进行透视投影),该法向量n和上方说的viewspace_pos皆是如此。
2.1.4注意代码最初令newtri == *t,即利用newtri记录小三角形三个顶点的位置、法线、纹理、颜色,但后续修改了三角形顶点的位置,将其改成了经过MVP变换和视图变换后的(利用setVertex()函数),法线则将其改成了viewspace空间的(利用setNormal()函数)。同时,viewspace的位置坐标虽然没有记录在newtri中,但是以参数的形式传递给了后续的rasterizer_triangle函数。所以传入光栅化rasterize_triangle函数的其实是屏幕三角形以及viewspace的位置坐标。

2.2viewspace下的顶点的法向量

下面详细说一下viewspace下的顶点的法向量的计算过程,即下面这段代码的推导:

//这里只是用n[]来表示只经过MV变换,即在viewspace视图空间下的各个顶点的法向量方向
        Eigen::Matrix4f inv_trans = (view * model).inverse().transpose();//inverse()是逆矩阵,transpose()是转置矩阵
        Eigen::Vector4f n[] = {
                inv_trans * to_vec4(t->normal[0], 0.0f),//也是表示为齐次坐标的形式
                inv_trans * to_vec4(t->normal[1], 0.0f),
                inv_trans * to_vec4(t->normal[2], 0.0f)
        };

Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第3张图片

​​​​​​3.rasterize_triangle函数

代码如下:

//Screen space rasterization
//!这里传进去的参数有两个!第一个参数是屏幕空间下的三角形;第二个参数是原模型每个三角形通过视图变换后得到的三角形顶点,是视图坐标(viewspace)下的坐标
void rst::rasterizer::rasterize_triangle(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
    //构建bounding box
    // 这里跟作业2里的深度插值差不多,v.w()就是该顶点深度值,用Z和zp代替w_reciprocal和z_interpolated
    auto v = t.toVector4();//将三角形t中的顶点数组v变成四维的向量
    //这里是求包围盒,求出能包围屏幕三角形的最小长方形四个顶点的数值
    int min_x = std::min(std::min(v[0].x(), v[1].x()), v[2].x());
    int min_y = std::min(std::min(v[0].y(), v[1].y()), v[2].y());
    int max_x = std::max(std::max(v[0].x(), v[1].x()), v[2].x());
    int max_y = std::max(std::max(v[0].y(), v[1].y()), v[2].y());
    //遍历包围盒中的像素
    for (int x = min_x; x <= max_x; x++) {
        for (int y = min_y; y <= max_y; y++) {
            //判断像素中心点是否在三角形内
            if (insideTriangle(x + 0.5, y + 0.5, t.v))//传入的是像素中心点的坐标以及三角形三个顶点的坐标数组 
            {
                auto [alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, t.v);//为了获得该点的z值,得到(x,y,z),
                                                                                        //用auto [alpha, beta, gamma]来接受每个像素中心点的重心坐标
                                                                                        // 此时t.v中存储的是此时屏幕空间三角形的顶点坐标
                //深度插值是插进去新的深度,原来其实已经有对应像素的深度值了 
                //进行深度插值之前,要对重心坐标进行透视矫正,即是在将二维像素中心点对应的重心坐标转化成三维的重心坐标
                //v[n].w()就是该屏幕三角形顶点对应的viewspace对应的顶点深度值!!已经是有定义了的
                //修正得到viewspace(camera space)中的情况
                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;//求出像素中心在屏幕三角形上的点对应的viewspace的三角形上的点的深度
                auto cur_index = get_index(x, y);//获取像素所在的数组位置
                //遍历每一个小三角形,都会比较一下当前像素中心在屏幕三角形上的点对应的viewspace的三角形上的点的深度和当前最小的深度值
                if (zp < depth_buf[cur_index] && cur_index < width * height)// cur_index < width * height) 是越界判断!!要加上!!
                                                                            // depth_buf[cur_index]是深度数组,存储着像素中心对应的世界坐标的当前深度
                                                                            //此时是判断要插入的深度值是否比现已存在的深度要小,小的话,深度就更新为刚插进去的新的深度
                {
                    depth_buf[cur_index] = zp;

                    // TODO: Interpolate the attributes:
                    // auto interpolated_color 颜色
                    // auto interpolated_normal 法向量
                    // auto interpolated_texcoords 纹理坐标
                    // auto interpolated_shadingcoords camera space的像素位置,为了求r和向量l
                    //这里的w取值都是1,没有做透视矫正,原本alpha、 beta、 gamma应该先矫正再带进去,但这里差距不大,就直接用alpha、 beta、 gamma了
                    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);//按权重算出当前在三角形内的像素的中心点法向量值,normal用于确定色彩(注意:此时法线是viewspace空间的)
                    auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);//按权重算出当前在三角形内的像素的中心点纹理坐标值,texcoords用于确定纹理坐标
                    auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1);//按权重算出当前像素着色点坐标值,shadingcoords用于确定viewspace位置,用于光照
                    // 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);
                    //此时已经将viewspace空间下的小三角形各个属性插值好了,之后就进行选择性的着色了
                    //将参数传入片元着色模块,用来得到不同的着色方案
                    fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
                    payload.view_pos = interpolated_shadingcoords;//更新着色模块中的着色点坐标,这个着色点坐标就是小三角形片上近似于像素中心对应的viewspace的三角形点(没有进行透视矫正,所以是近似)
                    auto pixel_color = fragment_shader(payload);//将着色模块传入shader计算出最后的像素颜色
                    //作业2中的set_pixel输入的是vector3f点坐标,这次作业的是vector2i坐标,因此直接输入x,y坐标即可
                    Vector2i vertex;
                    vertex << x, y;
                    set_pixel(vertex, pixel_color);//进行像素的着色(将着色结果通过像素展示出来)
                }
            }
        }
    }
}

3.1rasterize_triangle函数实现的整体思路:

(操作对象是传进来的屏幕三角形)
3.3.1.首先将储存三角形顶点的坐标数组v[]从三维坐标变为齐次坐标(四维坐标形式),然后构建包围盒子,这里是一个长方形(能包围传入的屏幕三角形(实际传入的只是顶点)的最小长方形),减少遍历的像素个数;
3.1.2.进行包围盒子的遍历,先判断当前像素的中心是否在屏幕空间三角形内,如果结果为true,则通过重心坐标(求出alpha, beta, gamma三个系数)和透视矫正(求出像素中心对应的屏幕空间三角形位置,并对其对应的世界坐标空间的坐标)进行深度插值;
深度插值的时候,要判断要当前插入的深度值是否比现已存在的深度要小,小的话,深度就更新为刚插进去的新的深度;
ps:深度插值的方法(包括透视矫正) : https://zhuanlan.zhihu.com/p/380589244utm_campaign=&utm_medium=social&utm_oi=1128416725900914688&utm_psn=1573365895249731584&utm_source=qq
3.1.3.深度插值结束之后,通过alpha, beta, gamma代表的权重分别计算出当前像素的color、normal、texcoords、shadingcoords的值,实现颜色、法向量、纹理坐标、着色点坐标的插值,
再将这些值传入着色模块,同时更新着色模块的着色点坐标值,之后将整个着色模块进行颜色计算并进行最终的像素着色。

3.2一些注意的地方:

3.2.1首先是插值的作用,因为光栅化只是为了将经过MVP变换、视图变换到比较恰当屏幕位置后的模型的二维图像展示出来(个人理解是此时的二维图像在屏幕的位置,但没有光栅化我们看不到它),在展示二维图像的过程中,我们只能将同一z轴下深度最小的模型位置信息显示出来,深度插值是通过重心坐标和已知的viewspace空间下的三角形三个顶点深度来操作,算出深度值,并和深度数组比较,最后更新出最新的深度数组。深度值可以用来判断遮挡的情况。

3.2.2通过插值得到三角形的各个属性,并用其初始化fragment_shader_payload结构的对象payload,fragment_shader_payload为Shader中定义的数据结构类型,payload为对象名,此对象用来记录当前像素的view_pos,颜色、法向量、纹理图中对应的坐标等。Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第4张图片
而最后用来获取颜色的fragment_shader函数是先前在主函数通过“函数赋值”得到的着色函数,根据命令不同fragment_shader意味着不同的着色方式(即主函数中的normal、phong、texture、bump等)。

3.3.3在做笔记的时候发现,其实w是默认为1.0的,并不是表示真正的深度值。
3.3.4也可以采用下面修改的MSAA抗锯齿代码(但个人效果感觉不是很明显,可能要再加采样点):

void rst::rasterizer::rasterize_triangle_MSAA(const Triangle& t, const std::array<Eigen::Vector3f, 3>& view_pos)
{
    auto v = t.toVector4();
    //INT_MAX是最大整数,INT_MIN是最小整数
    // TODO : Find out the bounding box of current triangle.//第一步要找到包含三角形的最小矩形,使遍历的像素少一点
    int min_x, max_x, min_y, max_y;//定义bounding box的四个角的坐标
    float alpha, beta, gamma;//为下面做深度插值定义float变量
    // 接下来要找到bounding box的边界坐标
    min_x = min(t.v[0].x(), min(t.v[1].x(), t.v[2].x()));
    max_x = max(t.v[0].x(), max(t.v[1].x(), t.v[2].x()));
    min_y = min(t.v[0].y(), min(t.v[1].y(), t.v[2].y()));
    max_y = max(t.v[0].y(), max(t.v[1].y(), t.v[2].y()));
    // iterate through the pixel and find if the current pixel is inside the triangle
    //逐个遍历像素。判断每个像素中心是否在三角形内,这里使用了MSAA采样
    std::vector<Eigen::Vector2f> super_sample_step//定义的超采样数组,使用了类模板来定义
    {
        {0.125,0.125},{0.125, 0.375},{0.125, 0.625},{0.125,0.875},
        {0.375,0.125},{0.375, 0.375},{0.375, 0.625},{0.375,0.875},
        {0.625,0.125},{0.625, 0.375},{0.625, 0.625},{0.625,0.875},
        {0.875,0.125},{0.875, 0.375},{0.875, 0.625},{0.875,0.875},

    };
    for (int x = min_x; x < max_x; x++)
    {
        for (int y = min_y; y < max_y; y++)
        {
            int judge = 0;//判断是否通过了深度测试,只要一个采样点通过就进行着色
            //判断像素中心点是否在三角形内,如果是,就尝试对该像素进行深度测试,如果通过再进行着色,并同时将深度传入缓存
            float z_interpolated;
            Vector3f pixel_color;
            //开始进行采样点遍历
            for (int count_i = 0; count_i < 16; count_i++)
            {
                if (insideTriangle(x + super_sample_step[count_i][0], y + super_sample_step[count_i][1], t.v))//insideTriangle()函数传入的是float原因就是这里找中心点是浮点类型的
                {

                    if (super_depth_buf[get_index_super_frame(x * 4 + count_i % 4, y * 4 + count_i / 4)] > z_interpolated)//深度缓存和着色步骤
                    {//如果此时的超采样深度缓存中的深度比该点的深度要大,就将缓存数值变成当前的最小值
                        judge = 1;
                        //这里根据count_i的不同,选择四个采样点
                        float z_interploated_super_sample;
                        super_depth_buf[get_index_super_frame(x * 4 + count_i % 4, y * 4 + count_i / 4)] = interpolate_depth(x + super_sample_step[count_i][0], y + super_sample_step[count_i][1], t, z_interploated_super_sample);//深度存入缓存
                        //此时将采样点传入超采样深度数组
                        super_frame_buf[get_index_super_frame(x * 4 + count_i % 4, y * 4 + count_i / 4)] = interpolate_depth_color(x + super_sample_step[count_i][0], y + super_sample_step[count_i][1], t);//颜色存入缓存
                        //此时将采样点传入超采样颜色数组
                    }
                }

            }//一个像素所有采样点判断完就进行着色

            if (insideTriangle(x + 0.5, y + 0.5, t.v))//传入的是像素中心点的坐标以及三角形三个顶点的坐标数组 
            {
                auto [alpha, beta, gamma] = computeBarycentric2D(x + 0.5, y + 0.5, t.v);//为了获得该点的z值,得到(x,y,z),
                //用auto [alpha, beta, gamma]来接受每个像素中心点的重心坐标
                // 此时t.v中存储的是此时屏幕空间三角形的顶点坐标
                //深度插值是插进去新的深度,原来其实已经有对应像素的深度值了 
                //进行深度插值之前,要对重心坐标进行透视矫正,即是在将二维像素中心点对应的重心坐标转化成三维的重心坐标
                //v[n].w()就是该屏幕三角形顶点对应的viewspace对应的顶点深度值!!已经是有定义了的
                //修正得到viewspace(camera space)中的情况
                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;//求出像素中心在屏幕三角形上的点对应的viewspace的三角形上的点的深度
                auto cur_index = get_index(x, y);//获取像素所在的数组位置
                //遍历每一个小三角形,都会比较一下当前像素中心在屏幕三角形上的点对应的viewspace的三角形上的点的深度和当前最小的深度值
                if (zp < depth_buf[cur_index] && cur_index < width * height)// cur_index < width * height) 是越界判断!!要加上!!
                    // depth_buf[cur_index]是深度数组,存储着像素中心对应的世界坐标的当前深度
                    //此时是判断要插入的深度值是否比现已存在的深度要小,小的话,深度就更新为刚插进去的新的深度
                {
                    depth_buf[cur_index] = zp;

                    // TODO: Interpolate the attributes:
                    // auto interpolated_color 颜色
                    // auto interpolated_normal 法向量
                    // auto interpolated_texcoords 纹理坐标
                    // auto interpolated_shadingcoords camera space的像素位置,为了求r和向量l
                    //这里的w取值都是1,没有做透视矫正,原本alpha、 beta、 gamma应该先矫正再带进去,但这里差距不大,就直接用alpha、 beta、 gamma了
                    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);//按权重算出当前在三角形内的像素的中心点法向量值,normal用于确定色彩(注意:此时法线是viewspace空间的)
                    auto interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);//按权重算出当前在三角形内的像素的中心点纹理坐标值,texcoords用于确定纹理坐标
                    auto interpolated_shadingcoords = interpolate(alpha, beta, gamma, view_pos[0], view_pos[1], view_pos[2], 1);//按权重算出当前像素着色点坐标值,shadingcoords用于确定viewspace位置,用于光照
                    // 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);
                    //此时已经将viewspace空间下的小三角形各个属性插值好了,之后就进行选择性的着色了
                    //将参数传入片元着色模块,用来得到不同的着色方案
                    fragment_shader_payload payload(interpolated_color, interpolated_normal.normalized(), interpolated_texcoords, texture ? &*texture : nullptr);
                    payload.view_pos = interpolated_shadingcoords;//更新着色模块中的着色点坐标,这个着色点坐标就是小三角形片上近似于像素中心对应的viewspace的三角形点(没有进行透视矫正,所以是近似)
                    pixel_color = fragment_shader(payload);//将着色模块传入shader计算出最后的像素颜色
                }
            }

            
            if (judge > 0)//只要有一个采样点在三角形内就进行着色
            {
                //接下来进行着色处理
                Vector3f point((float)x, (float)y, z_interpolated);//此时的点坐标
                Vector3f pixel_color_average1 = (super_frame_buf[get_index_super_frame(x * 4, y * 4)] + super_frame_buf[get_index_super_frame(x * 4, y * 4 + 1)] + super_frame_buf[get_index_super_frame(x * 4, y * 4 + 2)] + super_frame_buf[get_index_super_frame(x * 4 + 1, y * 4 + 3)]);
                Vector3f pixel_color_average2 = (super_frame_buf[get_index_super_frame(x * 4 + 1, y * 4)] + super_frame_buf[get_index_super_frame(x * 4 + 1, y * 4 + 1)] + super_frame_buf[get_index_super_frame(x * 4 + 1, y * 4 + 2)] + super_frame_buf[get_index_super_frame(x * 4 + 1, y * 4 + 3)]);
                Vector3f pixel_color_average3 = (super_frame_buf[get_index_super_frame(x * 4 + 2, y * 4)] + super_frame_buf[get_index_super_frame(x * 4 + 2, y * 4 + 1)] + super_frame_buf[get_index_super_frame(x * 4 + 2, y * 4 + 2)] + super_frame_buf[get_index_super_frame(x * 4 + 2, y * 4 + 3)]);
                Vector3f pixel_color_average4 = (super_frame_buf[get_index_super_frame(x * 4 + 3, y * 4)] + super_frame_buf[get_index_super_frame(x * 4 + 3, y * 4 + 1)] + super_frame_buf[get_index_super_frame(x * 4 + 3, y * 4 + 2)] + super_frame_buf[get_index_super_frame(x * 4 + 3, y * 4 + 3)]);
                Vector3f pixel_color_average = (pixel_color_average1 + pixel_color_average2 + pixel_color_average3 + pixel_color_average4) / 16;
                //着色,将一个像素四个采样点的颜色数据相加后除以4就得到最平均的颜色,使过渡就比较自然
                Vector2i vertex;
                vertex << x, y;
                set_pixel(vertex,pixel_color_average.cwiseProduct(pixel_color));//进行像素的着色(将着色结果通过像素展示出来)
            } 

        }
    }
}
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});
        std::fill(super_frame_buf.begin(), super_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_depth_buf.begin(), super_depth_buf.end(), Eigen::Vector3f{ 0,0,0 });
    
    }
}
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{
    frame_buf.resize(w * h);
    depth_buf.resize(w * h);
    super_frame_buf.resize(16 * w * h);
    super_depth_buf.resize(16 * w * h);

    texture = std::nullopt;
}

int rst::rasterizer::get_index_super_frame(int x, int y)
{
    return (4 * height - 1 - y) * width * 4 + x;
}

三、着色模型分析

首先安利一篇关于表面着色的文章:https://mbd.baidu.com/ug_share/mbox/4a83aa9e65/shareproduct=smartapp&tk=5585206cf583e1b909bd15a319cdc871&share_url=https%3A%2F%2Fwjrsbu.smartapps.cn%2Fzhihu%2Farticle%3Fid%3D267059125%26isShared%3D1%26_swebfr%3D1%26_swebFromHost%3Dbaiduboxapp&domain=mbd.baidu.com

1.normal着色模型

代码如下:

Eigen::Vector3f normal_fragment_shader(const fragment_shader_payload& payload)//传入片元着色模块
{
    Eigen::Vector3f return_color = (payload.normal.head<3>().normalized() + Eigen::Vector3f(1.0f, 1.0f, 1.0f)) / 2.f;
    Eigen::Vector3f result;
    result << return_color.x() * 255, return_color.y() * 255, return_color.z() * 255;
    return result;
}

1.1normal着色模型的分析

1.1.1对于normal法线着色,虽然这个着色结果是各处地方颜色不同的小牛,但实际是由法线的变化形成的,并不涉及phong系统下的光照。
1.1.2首先取出当前待着色像素点的法向量的x,y,z坐标并归一化,故此时x,y,z都在[-1,1]之间,加上(1.0f, 1.0f, 1.0f)后,变为[0,2],再除以2,即得[0,1],再分别乘以255即可得到各个颜色值了。(可以自己修改着色x,y,z方向的RGB值,得到不同的着色效果)。
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第5张图片
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第6张图片

2.phone着色模型

代码如下:

//Phone Shading(冯氏着色) 以片元为单位进行着色,对每个点计算一次光照,点的法向量是通过顶点法向量插值得到的。
//冯氏着色最接近现实,可以在减少三角面数的情况下达到相同的效果(插值后法向量会光滑变化),当然,性能开销也非常大。
Eigen::Vector3f phong_fragment_shader(const fragment_shader_payload& payload)
{
    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);//ka:环境光强强度系数
    Eigen::Vector3f kd = payload.color;//kd:漫反射系数
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);//ks:镜面反射高光系数
    //light为数据类型,记录了灯光位置和光照强度
    //struct light
    //{
        //Eigen::Vector3f position;
        //Eigen::Vector3f intensity;
    //};
    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;
    //这里就凸显view_pos的作用,光线作用的点
    Eigen::Vector3f point = payload.view_pos;
    //此时片元的法线
    Eigen::Vector3f normal = payload.normal;
    //result_color是不同光线累加作用的结果,这里先初始化为{0,0,0}
    Eigen::Vector3f result_color = { 0, 0, 0 };
    //这里遍历了vector lights,要计算每一束光对物体的作用
    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_direct = light.position - point;//光照方向是从物体着色点指向灯光
        //视线方向
        Eigen::Vector3f view_direct = eye_pos - point;//视线方向是从物体指向人眼
        //着色点和灯光的距离r
        float r = sqrt(light_direct.dot(light_direct));

        //环境光照ambient
        Eigen::Vector3f la = ka.cwiseProduct(amb_light_intensity);//下面一样,因为都是用三维向量表示(结果也是),所以用cwiseProduct,这里是x,y,z分别相乘得到新的x,y,z
        //漫反射diffuse
        Eigen::Vector3f ld = kd.cwiseProduct(light.intensity / pow(r, 2)) ;
        ld *= std::max(0.0f, normal.normalized().dot(light_direct.normalized()));
        //镜面反射高光specular
        Eigen::Vector3f ls = ks.cwiseProduct(light.intensity / pow(r, 2));
        //求半程向量
        Eigen::Vector3f h = (light_direct.normalized() + view_direct.normalized()).normalized();//此时半程向量已经是单位向量
        float max_p =pow(max(0.0f, normal.normalized().dot(h)),p);//p系数前面已经给了,可以自行修改p来观察效果
        ls *= max_p;

        //最后将环境光照结果、漫反射结果、高光结果加在一起
        result_color += la + ld + ls;//如果有多束光,会不断叠加效果,输出最后的着色
    }
 
    return result_color * 255.f;
}

2.1phone着色模型的分析

2.1.1这里漫反射系数直接是用之前插值的颜色值,其实可用光谱来准确描述某一点的颜色值,但开销太大,就简化为RGB值。
ps:相关模型可以看之前提到的表面着色的文章,也可以看这个笔记:https://zhuanlan.zhihu.com/p/144331612
2.1.2相关的phone模型,即为下图公式,具体推导可参照前面提及的文章
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第7张图片

2.1.3通过调节光照强度参数可以实现不同效果,但最好不要调的太大,不然结果会是下面第二个图所示的效果。
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第8张图片

Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第9张图片

3.texture着色模型

代码如下:

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());//获取纹理的颜色,getColor函数返回的是三维向量的颜色值
    }//payload.tex_coords.x()是着色片元中纹理坐标的u轴值,payload.tex_coords.x()是着色片元中纹理坐标的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);//ka:环境光强强度系数
    Eigen::Vector3f kd = texture_color/255.f;//kd:漫反射系数为纹理颜色归一化的RGB值,下面都是对单位向量的颜色RGB进行处理,在结尾的时候会乘上255.f进行颜色修正
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);//ks:镜面反射高光系数
    //light为数据类型,记录了灯光位置和光照强度
    //struct light
    //{
        //Eigen::Vector3f position;
        //Eigen::Vector3f intensity;
    //};
    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;
    //这里就凸显view_pos的作用,光线作用的点
    Eigen::Vector3f point = payload.view_pos;
    //此时片元的法线
    Eigen::Vector3f normal = payload.normal;
    //result_color是不同光线累加作用的结果,这里先初始化为{0,0,0}
    Eigen::Vector3f result_color = { 0, 0, 0 };
    //这里遍历了vector lights,要计算每一束光对物体的作用
    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_direct = light.position - point;//光照方向是从物体着色点指向灯光
        //视线方向
        Eigen::Vector3f view_direct = eye_pos - point;//视线方向是从物体指向人眼
        //着色点和灯光的距离r
        float r = sqrt(light_direct.dot(light_direct));

        //环境光照ambient
        Eigen::Vector3f la = ka.cwiseProduct(amb_light_intensity);//下面一样,因为都是用三维向量表示(结果也是),所以用cwiseProduct,这里是x,y,z分别相乘得到新的x,y,z
        //漫反射diffuse
        Eigen::Vector3f ld = kd.cwiseProduct(light.intensity / pow(r, 2));
        ld *= std::max(0.0f, normal.normalized().dot(light_direct.normalized()));
        //镜面反射高光specular
        Eigen::Vector3f ls = ks.cwiseProduct(light.intensity / pow(r, 2));
        //求半程向量
        Eigen::Vector3f h = (light_direct.normalized() + view_direct.normalized()).normalized();//此时半程向量已经是单位向量
        float max_p = pow(max(0.0f, normal.normalized().dot(h)), p);//p系数前面已经给了,可以自行修改p来观察效果
        ls *= max_p;

        //最后将环境光照结果、漫反射结果、高光结果加在一起
        result_color += la + ld + ls;//如果有多束光,会不断叠加效果,输出最后的着色
    }

    return result_color * 255.f;
}

3.1texture着色模型的分析

3.1.1注意之前phone模型的漫反射反射的颜色向量是插值的颜色,但这里用了贴图之后反射的是贴图对应贴图坐标的颜色。
3.1.2贴图使用后,也是用了phone模型进行光照的着色。
3.1.3结果会显示出如下的小牛,也可以换不同的贴图,尝试一下不同的效果。Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第10张图片
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第11张图片
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第12张图片

4.bump着色模型

代码如下:

//关于凹凸贴图、切线空间相关知识可以参考冯乐乐大佬的Unity shader入门精要
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();//x,y,z代表的是世界空间坐标下片元的法向量n的x、y、z方向

    Eigen::Matrix3f t  { x * y / sqrt(x * x + z * z),sqrt(x * x + z * z),z * y / sqrt(x * x + z * z) };//t是切线空间的x方向在世界空间下的表示
    Eigen::Matrix3f b = normal.cross(t);//b是切线空间的y方向在世界坐标下的表示,可由法线(法线normal在切线空间下的方向是z轴的方向)和t的叉乘得出来,注意叉乘时两变量的先后顺序,由右手螺旋判断
                                        //normal是在世界坐标下切线空间z坐标的表示
    Eigen::Matrix3f TBN = Eigen::Matrix3f::Identity();
    //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();//这个变量就是对应贴图的uv中的u方向的值
    float v = payload.tex_coords.y();
    float w = payload.texture->width;//纹理的宽度
    float h = payload.texture->height;//纹理的长度

    //纹理图上的颜色值本身就是存储的矢量信息,是用矢量来保存的RGB值
    float dU = kh * kn * (payload.texture->getColor(u + 1.0f/w, v).norm() - payload.texture->getColor(u, v).norm());//1.0f/w这里表示的是u方向的颜色变化的一个单位量
    float dV = kh * kn * (payload.texture->getColor(u, v + 1.0f/h).norm() - payload.texture->getColor(u, v).norm());//1.0f/h这里表示的是v方向的颜色变化的一个单位量

    Eigen::Vector3f ln{ -dU, -dV, 1.0f };//ln为扰动之后的法线,此时法线是切线空间中的表示!!
  
    
    normal = TBN * ln;//此时法线已经变换成[0,1]分量范围了
    Eigen::Vector3f result_color = normal.normalized();//归一化

    return result_color * 255.f;
}

4.1bump着色模型的分析

4.1关于凹凸贴图、切线空间相关知识可以参考冯乐乐大佬的Unity shader入门精要;
4.2凹凸贴图的部分计算
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第13张图片
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第14张图片
4.3对于代码中
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());
这两行的一些分析:
注意颜色是一个三维向量,(拿u方向进行说明)第一行是表示在u方向上改变一个单位长度,计算两个点保存的颜色向量的模的差值再乘以很小的系数(表示很小的变化量),用此来描述u方向上的单位颜色变化,而后面求出来的ln就是新法线的方向,(法线的方向,其实对应着RGB三个数值,一定意义上就是颜色的矢量信息,其实在重心坐标插值就可以看出来),所以法线扰动的实现其实靠的是不同位置颜色的变化,因为颜色就是一个可用来计算的矢量。
4.4对于新法线ln,其实已经是某一点切线空间的一个扰动后的法线。
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第15张图片

5.displacement着色模型

代码如下:

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]

    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 << t.x(), b.x(), normal.x(),
        t.y(), b.y(), normal.y(),
        t.z(), b.z(), normal.z();

    // 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 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_direct = light.position - point;
        Eigen::Vector3f view_direct = eye_pos - point;
        float r = light_direct.dot(light_direct);

        // ambient
        Eigen::Vector3f La = ka.cwiseProduct(amb_light_intensity);
        // diffuse
        Eigen::Vector3f Ld = kd.cwiseProduct(light.intensity / r);
        Ld *= std::max(0.0f, normal.dot(light_direct.normalized()));
        // specular
        Eigen::Vector3f h = (light_direct.normalized() + view_direct.normalized()).normalized();
        Eigen::Vector3f Ls = ks.cwiseProduct(light.intensity / r);
        Ls *= std::pow(std::max(0.0f, normal.dot(h)), p);

        result_color += (La + Ld + Ls);

    }

    return result_color * 255.f;
}

Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第16张图片

四、提高部分(双线性插值)

代码如下:

//双线性插值
    Eigen::Vector3f getColorBilinear(float u, float v)//首先是传入纹理贴图的u、v坐标,这个坐标不是纹理映射后的,就是最原始的texture上的单位坐标
    {
        if (u < 0) u = 0.0f;
        if (u > 1) u = 0.999f;
        if (v < 0) u = 0.0f;
        if (v > 1) u = 0.999f;
        //先定义定位的纹理位置和周围的三个纹理块的位置(一共四个纹理位置)
         
        //下面进行四个周围方块的坐标定义
        //左下角的纹理映射块坐标
        int w1 = u * width; int h1 = v * height;//这里的w1和h1是纹理单位坐标映射到屏幕坐标后的单位,如果要进行纹理颜色坐标对应颜色提取的话,还要/width(/height)
                                                              //这里要转成int类型,是为了找出双线性插值的点(float类型-int类型)
        //右下角的纹理映射块坐标
        float w2 = w1 + 1; float h2 = h1;
        //左上角的纹理映射块坐标
        float w3 = w1; float h3 = h1 + 1;
        //右上角的纹理映射块坐标
        float w4 = w1 + 1; float h4 = h1 + 1;

        float s = u * width - w1;
        float t = v * height - h1;
        //下面进行颜色的双线性插值
        Eigen::Vector3f color1, color2, color3, color4, color_interpolation_1_1, color_interpolation_1_2,color_interpolation_2, color;//定义颜色变量
        color1 = getColor((float)w1 /(float) width, (float)h1 / (float)height);//获取左下角纹理块的颜色
        color2 = getColor(w2 / width, h2 / height);//获取右下角纹理块的颜色
        color3 = getColor(w3 / width, h3 / height);//获取左下角纹理块的颜色
        color4 = getColor(w4 / width, h4 / height);//获取左下角纹理块的颜色
        //先进行横着的两次插值
        //获取第一次线性插值的颜色(下面两块纹理块)
        color_interpolation_1_1 = lerp(color1,color2,s);
        //获取第一次线性插值的颜色(上面两块纹理块)
        color_interpolation_1_2 = lerp(color3, color4,s);
        //获取第二次线性插值的颜色(竖直的一次线性插值)
        color_interpolation_2 = lerp(color_interpolation_1_1, color_interpolation_1_2,t);
        
        
        return Eigen::Vector3f(color_interpolation_2[0],color_interpolation_2[1],color_interpolation_2[2] );
    }

};

双线性插值原理和分析:

Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第17张图片
双线性插值主要是用于纹理贴图对应颜色的选取,一般来说,纹理映射的时候会有多个纹理坐标取同一个纹理单位的颜色,会使得颜色过渡不够平滑,双线性插值使纹理坐标可以提取到精确点的颜色,使颜色过渡更好。
一般来说,使用的定位系数是通过传入的float值减去int值得到0

以下用压缩的100*100的纹理贴图进行试验:
双线性插值前:
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第18张图片
双线性插值后:
Games101第三次作业(渲染管线分析、着色模型分析、双线性插值)_第19张图片
很明显,双线性插值之后,颜色过渡要平滑很多了。

你可能感兴趣的:(算法)