GAMES101作业3及课程总结

在做这个作业之前,看到弹幕说这次作业难度相对于前面几次上升很多,真正做了之后确实有那么点感受,但是实际上当厘清渲染管线之后,其实挺简单的,其实我觉得本次作业最难的地方就是看懂框架源码,知道它在干什么,理解核心代码的功能,通过这个过程去掌握渲染管线,明白这个之后,将问题拆解,其实这次作业也就不那么难了==

目录

    • 课程总结与理解(着色)
    • 框架梳理
      • main函数
      • draw函数
      • rasterize_triangle函数
    • 任务一:实现插值算法
    • 任务二:填入投影矩阵
      • get_projection_matrix函数
      • 法向量着色结果
    • 任务三:实现phong光照模型
      • phong_fragment_shader函数
      • phong光照模型着色结果
    • 作业四:纹理贴图实现(小奶牛)
      • texture_fragment_shader函数
      • 小奶牛着色效果
    • 作业五:Bump mapping实现
      • bump_fragment_shader函数
      • Bump mapping着色结果
    • 作业六:displacement mapping实现
      • displacement mapping函数
      • displacement mapping着色结果
    • 最终效果对比
    • 感悟
    • 参考链接

课程总结与理解(着色)

断断续续搞了一个星期的时间,总算把相关课程和作业3弄完了,感觉收获真的超大,就是这节课作业做完之后,对整个图形的渲染管线有了基本的理解,之前很多懵懂的概念也都清晰了很多很多

对这节课做个小总结,这节课主题就是着色,也就是如何计算物体的颜色,先是讲了phong光照模型,根据这个模型可以计算出物体顶点反射进入人眼的光(也就是人能看到的颜色),然后讲了3个着色方法(flat和gouraud都是在投影变换前就计算好颜色,而phong是光栅化阶段计算颜色,但顶点的一些属性的插值是要矫正到投影变换之前的),再然后总结了渲染管线,这个渲染管线非常重要,我感觉这个弄明白了,图形学算脚拇指迈入门了。然后就是介绍插值算法,重心坐标,数学真的博大精深。

最后就是介绍纹理映射,其实就是基于phong光照模型,纹理就是光照模型里面的反射系数(作业是选用的漫反射系数),其中如何得到顶点的纹理坐标没有细讲(作业直接提供了,感觉这个技术点挺麻烦的,虽然我不懂==),重点讲了在已知顶点纹理坐标后,如何对整个图形进行着色,分析了两个问题,即图形大纹理小的情况和图形小纹理大的情况,前者直接用双线性插值就能解决(但不可避免的会使图形模糊),后者麻烦很多,像素与像素之间的纹理坐标差距很大,一个像素可能会对应大面积的纹理,因此就要使用mipmap方法(范围查询,想到这个办法的人是真的牛逼,简单高效==),然后由于mipmap是一层一层的,也是离散的,因此再次使用插值使过渡平滑,真的牛逼。之后提了一些其他范围查询的方法,不过没有细讲(各向异性过滤和EWA过滤,分别解决的是区域条形和区域旋转的情况)。再然后就是提了下纹理的其他应用,比如天空盒记录环境光,用纹理模拟凹凸效果(bump方法和diplacment方法,前者是假凹凸,没有改变顶点位置,后者是真凹凸,改变了顶点的位置)。

框架梳理

在做作业前,一定一定要先把框架看懂,要不然真的是无从下手,其实我觉得这次作业最大的收获就是看懂框架。我是先自己琢磨了一两天,然后很关键的几个点去借鉴了大佬的博客,建议不要直接去看,思维会被束缚住,而且他们也有地方是错误的。建议先自己尽量去理解,实在理解不了再去其他人的博客看一些细节的地方。

main函数

先直接从main开始看,因为我是自己配的环境,在vs2019上跑的,所以把命令行相关的删掉了,关键的注释我已经写在代码里面了,总的来说,main函数的作用就是导入模型和纹理,设置着色器,然后调用draw函数,得到frambuffer数组,生成图像。

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 = "./models/spot/";

    // Load .obj File
    //读入三维模型,数据存入TriangleList,路径有点不一样,要留心=
    bool loadout = Loader.LoadFile("./models/spot/spot_triangulated_good.obj");
    for(auto mesh:Loader.LoadedMeshes)
    {
        for(int i=0;i<mesh.Vertices.size();i+=3)
        {
            Triangle* t = new Triangle();
            //读入的数据包括:
            //一组顶点位置(对应三角形的三个顶点,已经配好了)
            //对应顶点的法向量,已经单位化
            //对应顶点的纹理映射坐标,已经单位化到[0,1]
            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);
        }
    }

    rst::rasterizer r(700, 700);

    //设置纹理
    auto texture_path = "spot_texture.png";
    r.set_texture(Texture(obj_path + texture_path));

    //摄像机位置
    Eigen::Vector3f eye_pos = { 0,0,10 };

    //设置着色器
    //vertex_shader是顶点着色器,这里实际上没用到这个,这次作业的5个着色器都是片元着色器,因为都是插值出了每个像素的法向量(phong着色)
    //active_shader是激活的片元着色器
    //顶点着色器:对点的顶点进行着色,一般是在透视变换之前,在draw函数中对顶点直接赋予相同的颜色,因此没有用到这个
    //片元着色器:对像素进行着色,
    std::function<Eigen::Vector3f(fragment_shader_payload)> active_shader = texture_fragment_shader;
    r.set_vertex_shader(vertex_shader);
    r.set_fragment_shader(active_shader);

    int key = 0;
    int frame_count = 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;
}

draw函数

然后就是关键的函数draw函数,其实框架的很多关键代码的作用都是我打断点测出来的==,总的来说,这个函数的作用就是计算三角形视口变换后的顶点信息,以及投影变换前的顶点信息(这个后面着色有用,着色要在投影变换前,因为投影变换会影响插值的准确性,要矫正回去),最终将二者作为参数送入rasterize_triangle函数,进行三角形面片的绘制。

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

    //这里的50和0.1是根据透视投影那边的zfar和znear对应得到的
    float f1 = (50 - 0.1) / 2.0;
    float f2 = (50 + 0.1) / 2.0;

    Eigen::Matrix4f mvp = projection * view * model;
    for (const auto& t:TriangleList)
    {
        //在该循环内部逐个绘制三角形newtri
        Triangle newtri = *t;

        //计算得到摄像机坐标系下的点坐标,并存入viewspace_pos
        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;
        std::transform(mm.begin(), mm.end(), viewspace_pos.begin(), [](auto& v) {
            return v.template head<3>();
        });

        //计算投影坐标系下的点的坐标
        Eigen::Vector4f v[] = {
                mvp * t->v[0],
                mvp * t->v[1],
                mvp * t->v[2]
        };
        //Homogeneous division
        //注意,这里w没有除掉,因为后面插值需要用到w(但是实际上,toVector4函数把w变成1了,后面也没有真正用到w),作业2除掉了,实际上作业2里面的插值是有点问题的,并不算真正的矫正。
        //透视除法是为了真正将点转换到立方体中,只有除了之后点才会是立方体中的点,因为推导投影过程中第四维发生了变化,这里要把它变回去
        //然后这里的w,我推导了下,根据投影矩阵,它就是透视变换前,摄像机坐标下的z值,关键在投影矩阵第四行第三列
        for (auto& vec : v) {
            vec.x()/=vec.w();
            vec.y()/=vec.w();
            vec.z()/=vec.w();
        }

        //这里是计算摄像机坐标系下的法向量,法向量并不是直接乘上变换矩阵就行了的
        //这里和顶点的运算不一样,多了个逆和转置,这里是用线性代数的知识推导出来的,见https://blog.csdn.net/Q_pril/article/details/123598746
        Eigen::Matrix4f inv_trans = (view * model).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
        //视图变换,转换到屏幕空间
        for (auto & vert : v)
        {
            vert.x() = 0.5*width*(vert.x()+1.0);
            vert.y() = 0.5*height*(vert.y()+1.0);

            //就是把z还原成投影变换之前的。
            //这里要加负号的,切记切记,要不然小奶牛位置不对,要么改这里,要么改投影矩阵
            //这个问题如果作业2解决了(三角形顺序反了),就不用管了
            vert.z() = -vert.z() * f1 + f2;
        }

        //这项前面是MVP+视点变换,现在已经将顶点转到了屏幕空间,接下来就是光栅化,把顶点信息存入newtri中
        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>());
        }

        //顶点颜色,后面有部分着色器要用到它,比如phong,diplacment。而texture没有用到,因为用的就是纹理的颜色。
        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
        //viewspace_pos是视点坐标,就是投影变换之前的坐标
        rasterize_triangle(newtri, viewspace_pos);
    }
}

rasterize_triangle函数

之后就是rasterize_triangle函数了,如果认认真真做了作业2,其实这个函数的作用应该大致知道的。它的功能我就不赘述了,就是绘制三角形,关键点在于里面的插值算法,其实框架给的是有一定误差的,本质上都没有进行透视矫正(https://blog.csdn.net/Mine268/article/details/120273744),参见注释。哦对,还有一个很重要的地方,就是它这里插值之后,是把各个属性送入着色器,使用着色器进行着色的,可以把着色器看作一个函数,各个属性就是参数,其返回结果就是最终要着色的颜色。

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.
    //toVector4()函数是框架自定义的。
    //它只会把顶点坐标拷过去,w没拷过去,因此w就没有用了==,这就导致后面的插值是并不是真的插值。
    //不过我试了一下,改和不改效果看不出来,可能是模型的原因
    auto v = t.toVector4();
    int min_x, max_x, min_y, max_y;
    min_x = std::min(v[0].x(), std::min(v[1].x(), v[2].x()));
    max_x = std::max(v[0].x(), std::max(v[1].x(), v[2].x())); 
    min_y = std::min(v[0].y(), std::min(v[1].y(), v[2].y()));
    max_y = std::max(v[0].y(), std::max(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((float)x + 0.5, (float)y + 0.5, t.v))
            {
                // TODO: Inside your rasterization loop:
                //    * v[i].w() is the vertex view space depth value z.
                //    * Z is interpolated view space depth for the current pixel
                //    * zp is depth between zNear and zFar, used for z-buffer
                //对z进行插值,这里的算法是透视矫正,https://blog.csdn.net/Mine268/article/details/120273744
                //虽然这里的算法写的是对的,但是实际上,不是真正的透视矫正==
                //因为w没拷过去,值为1(参见上面对toVector4函数的分析),它的值并不是摄像机坐标下的z值==
                auto [alpha, beta, gamma] = computeBarycentric2D((float)x + 0.5, (float)y + 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;
                //深度测试,通过便着色,并同时将深度存入缓存
                if (depth_buf[get_index(x, y)] > zp)
                {
                    // TODO: Interpolate the attributes:
                    // auto interpolated_color
                    // auto interpolated_normal
                    // auto interpolated_texcoords
                    // auto interpolated_shadingcoords
                    //这里的插值都是直接在屏幕坐标系插的,按理说是应该返回到摄像机坐标系插的,这是因为投影会影响插值,具体原因可以去上面透视矫正的链接看,还是有点意思的。
                    //但是这里没有,直接在像素空间插了,应该是会有一定误差的。
                    Vector3f interpolated_color= interpolate(alpha, beta, gamma, t.color[0], t.color[1], t.color[2], 1);
                    Vector3f interpolated_normal = interpolate(alpha, beta, gamma, t.normal[0], t.normal[1], t.normal[2], 1);
                    Vector2f interpolated_texcoords = interpolate(alpha, beta, gamma, t.tex_coords[0], t.tex_coords[1], t.tex_coords[2], 1);
                    Vector3f 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;

                    //根据main函数中定义的片元着色器进行着色
                    //实际上,fragment_shader就已经是一个函数,传入参数然后最终返回计算的颜色
                    auto pixel_color = fragment_shader(payload);
                    //深度存入缓存
                    depth_buf[get_index(x, y)] = zp;
                    Vector2i point = { (float)x,(float)y};
                    //给像素赋予颜色
                    set_pixel(point, pixel_color);
                }
            }
        }
    }

任务一:实现插值算法

写在上文rasterize_triangle解析里面了,直接回头看这个就行,关键是要学明白透视校正==老师上课没有讲,其实这个证明也不是特别好证明的,反正我是搞了大半天没搞出来。

任务二:填入投影矩阵

直接把作业2的投影矩阵贴进去就行,此时如果要运行得到法向量着色结果,记得把main函数里面的着色器设置为法向量着色的那个,就是normal shader

get_projection_matrix函数

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();

    // TODO: Implement this function
    // Create the projection matrix for the given parameters.
    // Then return it.

    Eigen::Matrix4f persp_to_ortho, ortho, ortho_translate, ortho_scale;

    float EyeAngelRadian = eye_fov / 180 * MY_PI;
    float n, f, l, r, b, t;
    n = -zNear;//观察源码,和老师讲的,应该需要往-z方向看
    f = -zFar;
    t = abs(n) * tan(EyeAngelRadian / 2);
    b = -t;
    r = aspect_ratio * t;
    l = -r;

    persp_to_ortho << n, 0, 0, 0,
        0, n, 0, 0,
        0, 0, n + f, -n * f,
        0, 0, 1, 0;
    ortho_translate << 1, 0, 0, -(r + l) / 2,
        0, 1, 0, -(t + b) / 2,
        0, 0, 1, -(n + f) / 2,
        0, 0, 0, 1;
    ortho_scale << 2 / (r - l), 0, 0, 0,
        0, 2 / (t - b), 0, 0,
        0, 0, 2 / (n - f), 0,
        0, 0, 0, 1;
    ortho = ortho_scale * ortho_translate;
    projection = ortho * persp_to_ortho * projection;

    return projection;
}

法向量着色结果

还挺好看==
GAMES101作业3及课程总结_第1张图片

任务三:实现phong光照模型

接下来几个任务就很简单了,就是写main函数里面的着色器,我感觉公式搞明白,这部分敲代码还是比较好敲的,关键的地方在于一定要对带入光照模型的那几个向量作归一化,要不然效果是有问题的,我看有些博客是没有做归一化的,这个还是要细心一点。

phong_fragment_shader函数

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

    //计算环境光
    Eigen::Vector3f ambient = { 0,0,0 };
    ambient = ka.cwiseProduct(amb_light_intensity);

    Eigen::Vector3f diffuse = { 0,0,0 };
    Eigen::Vector3f specular = { 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;
        float r_square = l.norm();
        r_square = r_square * r_square;
        //向量单位化
        l=l.normalized();

        //计算漫反射光
        diffuse = diffuse + kd.cwiseProduct(light.intensity / (r_square))*std::max(0.f, normal.dot(l));
        //std::cout << diffuse;

        Eigen::Vector3f v = (eye_pos - point).normalized();
        Eigen::Vector3f h = (v+l).normalized();

        //计算高光
        specular= specular+ks.cwiseProduct(light.intensity / (r_square)) * pow(std::max(0.f, normal.dot(h)),p);
    }

    result_color = ambient + diffuse + specular;

    return result_color * 255.f;
}

phong光照模型着色结果

这部分写得比较快,没怎么遇到棘手的问题,感觉渲染部分主要是要搞懂原理,代码认真跟着算法敲下来一般问题不会特别多。
GAMES101作业3及课程总结_第2张图片

作业四:纹理贴图实现(小奶牛)

这部分是在phong着色基础上实现的,说白了就是把漫反射系数改成纹理映射的颜色,但是这里有个比较坑的点,卡了我老半天,就是在边界插值出来的纹理坐标可能会超出[0,1],需要在getColor函数中设定边界判定,要不然就会出现越界的bug。参考https://zhuanlan.zhihu.com/p/419872527

texture_fragment_shader函数

Eigen::Vector3f texture_fragment_shader(const fragment_shader_payload& payload)
//纹理着色器,就是把小奶牛的图片贴上去,核心思路就是改掉漫反射系数kd
{
    Eigen::Vector3f return_color = {0, 0, 0};
    if (payload.texture)
    {
        // TODO: Get the texture value at the texture coordinates of the current fragment
        //注意,这段话可能会报错,因为在边界插值出来的纹理坐标可能会超出[0,1],需要在getColor函数中设定边界判定
        return_color= payload.texture->getColor(payload.tex_coords[0], payload.tex_coords[1]);
    }
    Eigen::Vector3f texture_color;
    texture_color << return_color.x(), return_color.y(), return_color.z();

    Eigen::Vector3f ka = Eigen::Vector3f(0.005, 0.005, 0.005);
    //材质颜色对应的是漫反射系数
    Eigen::Vector3f kd = texture_color / 255.f;
    Eigen::Vector3f ks = Eigen::Vector3f(0.7937, 0.7937, 0.7937);

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

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

    float p = 150;

    Eigen::Vector3f color = payload.color;
    Eigen::Vector3f point = payload.view_pos;
    //查了下源码,模型载入的法向量就是单位向量
    Eigen::Vector3f normal = payload.normal;

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

    //计算环境光
    Eigen::Vector3f ambient = { 0,0,0 };
    ambient = ka.cwiseProduct(amb_light_intensity);

    Eigen::Vector3f diffuse = { 0,0,0 };
    Eigen::Vector3f specular = { 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;
        float r_square = l.norm();
        r_square = r_square * r_square;
        //向量单位化,normalized()是不会改变原有大小的。
        l = l.normalized();

        //计算漫反射光
        diffuse = diffuse + kd.cwiseProduct(light.intensity / (r_square)) * std::max(0.f, normal.dot(l));
        //std::cout << diffuse;

        Eigen::Vector3f v = (eye_pos - point).normalized();
        Eigen::Vector3f h = (v + l).normalized();

        //计算高光
        specular = specular + ks.cwiseProduct(light.intensity / (r_square)) * pow(std::max(0.f, normal.dot(h)), p);
    }

    result_color = ambient + diffuse + specular;

    return result_color * 255.f;
}

小奶牛着色效果

看到这个效果还是把我激动了不久,真的挺有成就感的,反正我觉得比开发有意思多了==,之前给老师搬砖搬的我是真难受。
GAMES101作业3及课程总结_第3张图片

作业五:Bump mapping实现

这部分之前看老师课程纠结了很久,没看明白,可能是自己水平不行吧,论坛说光线追踪那部分会再提到,就先照着注释写了,后面要用到再回过头来搞明白。

bump_fragment_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;
    float u = payload.tex_coords[0];
    float v = payload.tex_coords[1];
    float w = payload.texture->width;
    float h = payload.texture->height;

    // TODO: Implement bump mapping here
    auto [x, y, z] = std::tuple{ normal[0],normal[1],normal[2] };
    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);
    Matrix3f TBN ;
    TBN << t, b, normal;

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

    // 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)


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

    return result_color * 255.f;
}

Bump mapping着色结果

GAMES101作业3及课程总结_第4张图片

作业六:displacement mapping实现

和作业四同理,不再赘述,后面用到回来补。

displacement mapping函数

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;
    float u = payload.tex_coords[0];
    float v = payload.tex_coords[1];
    float w = payload.texture->width;
    float h = payload.texture->height;

    auto [x, y, z] = std::tuple{ normal[0],normal[1],normal[2] };
    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);
    Matrix3f TBN;
    TBN << t, b, normal;

    float dU = kh * kn * (payload.texture->h(u + 1.0f / w, v) - payload.texture->h(u, v));
    float dV = kh * kn * (payload.texture->h(u, v + 1.0f / h) - payload.texture->h(u, v));
    Vector3f ln = { -dU,-dV,1 };
    point = point + kn * normal * payload.texture->h(u, v);
    normal = (TBN * ln).normalized();
    // 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)


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

    //计算环境光
    Eigen::Vector3f ambient = { 0,0,0 };
    ambient = ka.cwiseProduct(amb_light_intensity);
    Eigen::Vector3f diffuse = { 0,0,0 };
    Eigen::Vector3f specular = { 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.
        // 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;
        float r_square = l.norm();
        r_square = r_square * r_square;
        //向量单位化,normalized()是不会改变原有大小的。
        l = l.normalized();

        //计算漫反射光
        diffuse = diffuse + kd.cwiseProduct(light.intensity / (r_square)) * std::max(0.f, normal.dot(l));
        //std::cout << diffuse;

        Eigen::Vector3f v = (eye_pos - point).normalized();
        Eigen::Vector3f h = (v + l).normalized();

        //计算高光
        specular = specular + ks.cwiseProduct(light.intensity / (r_square)) * pow(std::max(0.f, normal.dot(h)), p);

    }
    result_color = ambient + diffuse + specular;

    return result_color * 255.f;
}

displacement mapping着色结果

GAMES101作业3及课程总结_第5张图片

最终效果对比

GAMES101作业3及课程总结_第6张图片

感悟

最后作业做完真的还是蛮有成就感的,做这种图形学的作业,我发现我还是比较专注的,做那种开发的作业,我真的懒得做。。。可能我不是很适合搞开发吧。我觉得用数学推导出来然后用算法实现的感觉,老爽了==这段时间是少有的比较闲的时间,后面五月份之后可能会忙起来了,希望自己进度抓紧一点,赶快把games101搞搞完,争取图形学能早点入门。

参考链接

https://blog.csdn.net/Q_pril/article/details/123598746(这篇博客我觉得总结的还是相当不错的,能感受到是下了功夫的
https://games-cn.org/forums/topic/%e4%bd%9c%e4%b8%9a3%e6%9b%b4%e6%ad%a3%e5%85%ac%e5%91%8a/(作业3更正,做之前一定要看看这个
https://blog.csdn.net/Mine268/article/details/120273744(透视矫正的推导
https://blog.csdn.net/n5/article/details/100148540(透视矫正的推导
https://zhuanlan.zhihu.com/p/419872527(插值出来的纹理坐标越界

你可能感兴趣的:(计算机图形学,计算机图形学,games101,作业3)