Games101 学习笔记(1)--作业1到作业3

656 阅读7分钟

作为研一刚入学的新生,硕士方向是姿态估计,写下的第一篇博客却是计算机图形学中的内容,还是蛮奇妙的。因为考研过程中了解到CG,并看到许多大佬们的成果,非常的优雅以及美妙,对CG有了非常大的兴趣。了解到计图+AI的交融是蛮有前景的,就打算自学CG。所以现在是一个零基础的小白,在荒野放下第一块砖。

学习Games101前,已有的基础是线代,微积分,大学物理,编程语言是c和Python,没有C++基础。在学习Games101前只看了C++的菜鸟教程,就急着开始学习Games101,所以前两次作业是网上查找的作业解释,得以勉强读懂了第三次作业的代码,能够独立完成作业了。虽然得到渲染结果,但还是有照猫画虎的感觉,所以想用博客的形式强迫自己主动梳理消化知识点。就把前三章的内容做小小总结,以及自己遇到的问题。

视频链接:Lecture 01 Overview of Computer Graphics_哔哩哔哩_bilibili 课件链接:GAMES-101/doc at master · AshkenSC/GAMES-101 · GitHub

1.变换(Transformation)

主要部分是是MVP变换:

  • M: model transformation,模型变换;

  • V: view transformation,视图变换;

  • P:project transformation,投影变换;

    MVP变换目的就是为了将3D物体投影到摄像机的二维平面上,M是为了将实际物体投影到一个cube中,包含旋转,平移,放缩变换,变换矩阵如下所示:

image.png

image.png

V是针对摄像机的变换,将其放置在一个固定的位置(坐标原点上);P再实现从3D到2D的映射。

image.png 投影变换又包括正交(orthographic)投影和透视(Perspective)投影。 正交投影没有视点,就像三视图的投影;透视投影有个近大远小的效果,是一组以视点为顶点的相似三角形。透视投影实现的思路是将其台形的模型先和正交投影中方形模型建立映射,在进行正交投影,详细推导如下图所示:

0B7566E0B64A8CEDDBBC3B36423FAC8D.png

2D5C48D5EA829F1C0110D687DA0D1284.png

image.png

image.png

image.png

作业里的三个函数就是利用上面的公式实现变换矩阵。

2.光栅化(Rasterization)

Raster是德语里屏幕的意思,所以光栅化是将投影后的2D图像显示出来,变换后的2D图像是模拟图像,显示到屏幕上需要将其数字化,这就涉及到采样的问题。

使用较多的是Triangle Meshes,使用三角形因为其可以保证是一个平面,内部定义明确,可以使用三角形顶点方便插值。

流程就是先通过三角形的各个顶点位置确定一个区域(最简单的是矩形),使用最小的空间包住三角形,遍历区域所有的点,判断其是否在三角形内部(使用叉乘的特性),在内部则赋予特定的颜色值。

采样产生锯齿现象(sampling artifacts)。此时可以先模糊在采样(截断高频信息,避免频谱混叠)

3.渲染(Shading)

渲染考虑的就是光栅化中赋值的问题(The process of applying a material to an object.)

3.1 远近

方案一:油画算法,对每个三角形中的每个点都赋值,像油画一样近处的颜色覆盖远处的颜色。但这种不好判断远近关系(比如三个三角形互相掩盖),另外不同赋值顺序也会产生不同的结果。

方案二:使用Z-Buffer,保存每个点的深度信息,每当渲染一个三角形时,遇到比Z-Buffer深度更近的,更新Z-Buffer。(习惯将Z恒正,所以z越小表示越近)

该算法的时间复杂度为O(n),排序算法的时间复杂度最好为O(nlogn),闫老师说是因为该算法只需知道最大的那个是多少,而排序算法需要知道两两之间的大小关系。(从信息论的角度?)

3.2 光照模型

Blinn-Phong Reflectance Model

image.png

image.png

image.png

image.png

漫反射只考虑光源到物体表面的物理因素,应该是在屏幕上显示只有这个阶段的参与,与人眼到表面的因素无关。 可以看到该模型对表面法线的依赖程度,法线的产生有下面的方法:

Flat shading:一个三角形里的点共享该面的法线; Gouraud shading:三角形的每个顶点具有法线,三角形里的颜色由顶点颜色插值得到; Phong shading:三角形里的每个点的法线由三角形顶点法线插值得到。

其中顶点法线可以由包含该顶点的三角形法线根据面积加权得到。插值是由重心坐标得到:

image.png

3.3 纹理

纹理就是不同地方有不同的颜色。、 构建现实世界、屏幕、纹理空间的映射。

image.png image.png 纹理太小怎么办?(即一个纹理元素texel对应多个pixel):插值,如双线性插值。lerp(x,v0,v1)=v0+x(v1v0)lerp(x,v_0,v_1)=v_0+x(v_1-v_0) 纹理太大怎么办?(一个pixel对应多个texel)此时采样频率较低,可以区域采多个值求平均,但这样开销较大。可以用Mipmap方法: step1:对纹理空间倍减求平均,得到多个map; image.png step2:每个map里的texel相距步长不同,将像素映射到纹理空间后,根据欧氏距离取得的D到对应的map得到颜色,为了过渡平滑,可以根据距离值在不同D中的值进行插值。

image.png

3.4 凹凸

利用纹理也可以得到凹凸效果。

image.png

image.png h(p)函数是纹理的颜色函数。

3.5 作业

  1. 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};
    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 la, ld, ls;
        Eigen::Vector3f light_dir = (light.position - point).normalized();
        Eigen::Vector3f view_dir = (eye_pos - point).normalized();

        auto r2 = (light.position - point).dot(light.position - point);
        ld = kd.cwiseProduct(light.intensity / r2) * std::max(0.f, normal.dot(light_dir));

        Eigen::Vector3f h;
        h = (light_dir + view_dir).normalized();
        ls = ks.cwiseProduct(light.intensity / r2) * pow((std::max(0.f, normal.dot(h))), p);

        la = ka.cwiseProduct(amb_light_intensity);

        result_color += la + ld + ls;
    }

    return result_color * 255.f;
}
  • 渲染的光照角度不对:计算半程向量前没有把v,l向量先单位化;
  • amb_light_intensity是背景光照的强度,计算的I/r2I/r^2不是背景光照强度;
  • 向量对应元素相乘使用 cwiseProduct()函数.
  1. displacement_fragment_shader函数

这个函数包含了前面所有的函数。

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();
    float y = normal.y();
    float z = normal.z();

    Eigen::Vector3f t = Eigen::Vector3f(x * y / sqrt(x * x + z * z), sqrt(x * x + z * z), z * y / 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.f / w, v).norm() - payload.texture->getColor(u, v).norm());
    float dV = kh * kn * (payload.texture->getColor(u, v + 1.f / w).norm() - payload.texture->getColor(u, v).norm());
    Eigen::Vector3f ln = Eigen::Vector3f(-dU, -dV, 1);
    normal = (TBN * ln).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 la, ld, ls;
        Eigen::Vector3f light_dir = (light.position - point).normalized();
        Eigen::Vector3f view_dir = (eye_pos - point).normalized();

        auto r2 = (light.position - point).dot(light.position - point);
        ld = kd.cwiseProduct(light.intensity / r2) * std::max(0.f, normal.dot(light_dir));

        Eigen::Vector3f h;
        h = (light_dir + view_dir).normalized();
        ls = ks.cwiseProduct(light.intensity / r2) * pow((std::max(0.f, normal.dot(h))), p);

        la = ka.cwiseProduct(amb_light_intensity);

        result_color += la + ld + ls;
    }

    return result_color * 255.f;
}

凹凸变化是根据注释来的,暂时不知道什么意思。但学习嘛,不求甚解也是个好的习惯。不要因为死磕某个小点浪费了大量时间,有些难点放一放,先往前学,到时候自然水到渠成,渐渐清楚了。

渲染管线

image.png