• games101——作业7



    总览

    在之前的练习中,我们实现了 Whitted-Style Ray Tracing 算法,并且用 BVH
    等加速结构对于求交过程进行了加速。在本次实验中,我们将在上一次实验的基
    础上实现完整的 Path Tracing 算法。至此,我们已经来到了光线追踪版块的最后
    一节内容。

    请认真阅读本文档,按照本文档指示的流程完成本次实验。


    调通框架

    修改的内容

    相比上一次实验,本次实验对框架的修改较大,主要在以下几方面:

    • 修改了 main.cpp,以适应本次实验的测试模型 CornellBox
    • 修改了 Render,以适应 CornellBox 并且支持 Path Tracing 需要的同一 Pixel 多次 Sample
    • 修改了 Object,Sphere,Triangle,TriangleMesh,BVH,添加了 area 属性与Sample 方法,以实现对光源按面积采样,并在 Scene 中添加了采样光源的接口 sampleLight
    • 修改了 Material 并在其中实现了 sample, eval, pdf 三个方法用于 Path Tracing 变量的辅助计算

    你需要迁移的内容

    你需要从上一次编程练习中直接拷贝以下函数到对应位置:

    • Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数
      粘贴到此处,请直接将上次实验中实现的内容粘贴在此。
    inline Intersection Triangle::getIntersection(Ray ray)
    {
        Intersection inter;
    
        if (dotProduct(ray.direction, normal) > 0)
            return inter;
        double u, v, t_tmp = 0;
        Vector3f pvec = crossProduct(ray.direction, e2);
        double det = dotProduct(e1, pvec);
        if (fabs(det) < EPSILON)
            return inter;
    
        double det_inv = 1. / det;
        Vector3f tvec = ray.origin - v0;
        u = dotProduct(tvec, pvec) * det_inv;
        if (u < 0 || u > 1)
            return inter;
        Vector3f qvec = crossProduct(tvec, e1);
        v = dotProduct(ray.direction, qvec) * det_inv;
        if (v < 0 || u + v > 1)
            return inter;
        t_tmp = dotProduct(e2, qvec) * det_inv;
    
        // TODO find ray triangle intersection
        inter.happened = true;
        inter.coords = ray.origin + t_tmp * ray.direction;
        inter.normal = this->normal;
        inter.distance = dotProduct(t_tmp * ray.direction, t_tmp * ray.direction);
        inter.obj = this;
        inter.m = this->m;
    
        return inter;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • IntersectP(const Ray& ray, const Vector3f& invDir,const std::array& dirIsNeg) in the Bounds3.hpp: 这个函数的作用是判断包围盒 BoundingBox 与光线是否相交,请直接将上次实验中实现
      的内容粘贴在此处,并且注意检查 t_enter = t_exit 的时候的判断是否正确。
    inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
                                    const std::array<int, 3>& dirIsNeg) const
    {
        // invDir: ray direction(x,y,z), invDir=(1.0/x,1.0/y,1.0/z), use this because Multiply is faster that Division
        // dirIsNeg: ray direction(x,y,z), dirIsNeg=[int(x>0),int(y>0),int(z>0)], use this to simplify your logic
        // TODO test if ray bound intersects
        float t_enter;
        float t_exit;
        Vector3f t_enter_v3f = (pMin - ray.origin) * invDir;
        Vector3f t_exit_v3f = (pMax - ray.origin) * invDir;
    
        // for(int i=0; i<3; ++i) {
        //     if(!dirIsNeg[i])
        //         std::swap(t_enter_v3f[i], t_exit_v3f[i]);
        // }
        if(!dirIsNeg[0])
            std::swap(t_enter_v3f.x, t_exit_v3f.x);
        if(!dirIsNeg[1])
            std::swap(t_enter_v3f.y, t_exit_v3f.y);
        if(!dirIsNeg[2])
            std::swap(t_enter_v3f.z, t_exit_v3f.z);
    
        t_enter = std::max(t_enter_v3f.x, std::max(t_enter_v3f.y, t_enter_v3f.z));
        t_exit = std::min(t_exit_v3f.x, std::min(t_exit_v3f.y, t_exit_v3f.z));
    
        if (t_enter <= t_exit && t_exit >= 0)
            return true;
        else
            return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    这里需要注意的是, t e n t e r = = t e x i t t_{enter} == t_{exit} tenter==texit 也满足条件,主要是因为场景中(与水平面或垂直面平行)存在高为 0 包围框(比如墙壁),如果不加上相等,可能会出现渲染之后全黑的情况

    • getIntersection(BVHBuildNode* node, const Ray ray) in BVH.cpp: BVH查找过程,请直接将上次实验中实现的内容粘贴在此处.
    Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
    {
        Intersection isect;
        // TODO Traverse the BVH to find intersection
        if(!node->bounds.IntersectP(ray, ray.direction_inv, std::array<int, 3> {ray.direction.x>0, ray.direction.y>0, ray.direction.z>0}))
            return isect;
        if(node->object != nullptr)
            return node->object->getIntersection(ray);
    
        Intersection isect_left, isect_right;
        isect_left = getIntersection(node->left, ray);
        isect_right = getIntersection(node->right, ray);
    
        return isect_left.distance <= isect_right.distance ? isect_left : isect_right;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    编译运行

    基础代码只依赖于 CMake,下载基础代码后,执行下列命令,就可以编译这
    个项目:

    mkdir build
    cd ./build
    cmake ..
    make
    
    • 1
    • 2
    • 3
    • 4

    在此之后,你就可以通过 ./Raytracing 来执行程序。请务必确保程序可以正常编译之后,再进入下一节的内容。


    开始实现

    代码框架

    在本次实验中,你只需要修改这一个函数:

    • castRay(const Ray ray, int depth) in Scene.cpp: 在其中实现 Path Tracing 算法

    可能用到的函数有:

    • intersect(const Ray ray) in Scene.cpp: 求一条光线与场景的交点
    • sampleLight(Intersection pos, float pdf) in Scene.cpp: 在场景的所有光源上按面积 uniform 地 sample 一个点,并计算该sample 的概率密度
    • sample(const Vector3f wi, const Vector3f N) in Material.cpp: 按照该
      材质的性质,给定入射方向与法向量,用某种分布采样一个出射方向
    • pdf(const Vector3f wi, const Vector3f wo, const Vector3f N) in Material.cpp: 给定一对入射、出射方向与法向量,计算 sample 方法得到该出射方向的概率密度
    • eval(const Vector3f wi, const Vector3f wo, const Vector3f N) in Material.cpp: 给定一对入射、出射方向与法向量,计算这种情况下的 f_r 值

    可能用到的变量有:

    • RussianRoulette in Scene.cpp: P_RR, Russian Roulette 的概率

    Path Tracing 的实现说明

    课程中介绍的 Path Tracing 伪代码如下 (为了与之前框架保持一致,wo 定义与课程介绍相反,因为代码里的光线是从相机射出的
    按照本次实验给出的框架,我们进一步可以将伪代码改写为:
    请确保你已经清晰地理解 Path Tracing 的实现方式,再进入下一个环节的讨论。


    结果与分析

    本章节讨论得到结果与调试过程中需要特别注意的一些问题。

    注意事项

    1. 本次实验代码的运行非常慢,建议调试时调整 main.cpp 中的场景大小或 Render.cpp 中的 SPP 数以加快运行速度;此外,还可以实现多线程来进一步加快运算。
    2. 注意数值精度问题,尤其注意 pdf 接近零的情况,以及 sampleLight 时判断光线是否被挡的边界情况。这些情况往往会造成渲染结果噪点过多,或出现黑色横向条纹。

    参考结果

    如果严格按照上述算法实现,你会发现渲染结果中光源区域为纯黑。请分析这一现象的原因,并且修改 Path Tracing 算法使光源可见。最终结果如下:

    材质的扩展

    目前的框架中拆分 sample, eval, pdf,实现了最基础的 Diffuse 材质。请在不破
    坏这三个函数定义方式的情况下修改这三个函数,实现 Microfacet 模型。本任务
    不要求你实现复杂的采样手段,因此你依然可以沿用 Diffuse 材质采用的 sample
    与 pdf 计算。

    Microfacet 相关知识见第十七讲 Slides https://sites.cs.ucsb.edu/~lingqi/teaching/resources/GAMES101_Lecture_17.pdf.


    作业代码

    首先向场景中射出一条光线,看其与场景中最近的交点在哪,如果存在交点,获得相关属性(交点坐标,法向量,材质)

    Intersection intersection = Scene::intersect(ray);
    if(intersection.happened) {
            // std::cout<<1<
            Vector3f hitPoint = intersection.coords;
            Vector3f N = intersection.normal; // normal
            Material *m = intersection.m;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里光照由直接光照与间接光照组成,我们先将其初始化

     // init L_dir and L_indir
    Vector3f L_dir(0.0), L_indir(0.0);
    
    • 1
    • 2

    计算直接光照对光源进行采样(课上提到过,如果只是对物体的光线进行采样,如果上方有光源,会有许多射线没打到光源而被浪费掉)

     // Uniformly sample the light at x (pdf_light = 1 / A)
    Intersection intersection_light;
    float pdf_light;
    sampleLight(intersection_light, pdf_light);
    
    • 1
    • 2
    • 3
    • 4

    然后从点 p p p 向采样光线处点 x x x 射出一条光线,这里需要确定其起点,方向,然后在场景中射出这条射线。

    // Shoot a ray from p to x
    Vector3f dir_p_x = (intersection_light.coords - hitPoint).normalized();
    Ray ray_p_x(hitPoint + EPSILON * N, dir_p_x);
    // std::cout<
    // std::cout<
    Intersection intersection_p_x = Scene::intersect(ray_p_x);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里需要注意的是,我们要在 hitPoint 上加上 EPSLON * N,如果不这样,那么之后判断光线是否被遮挡会有问题(会认为被自己遮挡),然后出现下图的问题
    如果光线没有被遮挡住这里我们寻找 d ω d\omega dω d A dA dA 的关系,将原来的积分转换为对 d A dA dA 的积分,这样就可以使用蒙特卡洛积分求解对光源进行采样

    在伪代码中对于这个式子

    这里注意光线的方向在光源上是如下图所示的
    那我们使用向量点积计算 c o s θ ′ cos\theta' cosθ,要给 向量 ws 加上负号

    		// If the ray is not blocked in the middle
            if(intersection_p_x.happened && intersection_p_x.m->hasEmission()) {
                // std::cout<<1<
                Vector3f NN = intersection_p_x.normal;
                L_dir = intersection_p_x.m->m_emission * m->eval(ray.direction, dir_p_x, N) * dotProduct(dir_p_x, N) * dotProduct(-dir_p_x, NN) / intersection_p_x.distance / pdf_light;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    下面计算间接光照,首先使用俄罗斯轮盘赌判断此时是否结束,如果还未结束,我们采样入射方向(这里指的是光线的入射方向,在之前的代码框架里对应 w o w_o wo),这里在 f r f_r fr 的计算中是与光线的入射方向有关的,而与出射方向无关(对于漫反射材质),然后使用蒙特卡洛采样即可,间接光照要不停弹射(在这里就是递归调用castRay),直到俄罗斯轮盘赌停止。

     if(get_random_float() <= RussianRoulette) {
                // Trace a ray r(p, wi)
                // std::cout<<1<
                Vector3f dir_i = m->sample(ray.direction, N).normalized();
                Ray ray_p_diri(hitPoint, dir_i);
                Intersection intersection_p_diri = Scene::intersect(ray_p_diri);
                
                // If ray r hit a non-emitting object at q
                if(intersection_p_diri.happened && !intersection_p_diri.m->hasEmission()) {
                    L_indir = castRay(ray_p_diri, depth+1) * m->eval(ray.direction, dir_i, N) * dotProduct(dir_i, N) / m->pdf(ray.direction, dir_i, N) / RussianRoulette;
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后返回直接光照+间接光照的结果

    return L_dir + L_indir;
    
    • 1

    其结果如下
    我们会发现光源很暗,这是因为我们在计算渲染方程时是要加上自身释放的光

    我们把自身释放的光加上

    return m->getEmission() + L_dir + L_indir;
    
    • 1

    castRay完整代码如下

    // Implementation of Path Tracing
    Vector3f Scene::castRay(const Ray &ray, int depth) const
    {
        // TO DO Implement Path Tracing Algorithm here
        Intersection intersection = Scene::intersect(ray);
        if(intersection.happened) {
            // std::cout<<1<
            Vector3f hitPoint = intersection.coords;
            Vector3f N = intersection.normal; // normal
            Material *m = intersection.m;
            // if(m->hasEmission())
            //     std::cout<<1<
    
            // init L_dir and L_indir
            Vector3f L_dir(0.0), L_indir(0.0);
    
            // Uniformly sample the light at x (pdf_light = 1 / A)
            Intersection intersection_light;
            float pdf_light;
            sampleLight(intersection_light, pdf_light);
            // std::cout<
    
            // Shoot a ray from p to x
            Vector3f dir_p_x = (intersection_light.coords - hitPoint).normalized();
            Ray ray_p_x(hitPoint + EPSILON * N, dir_p_x);
            // std::cout<
            // std::cout<
            Intersection intersection_p_x = Scene::intersect(ray_p_x);
            // if(intersection_p_x.happened)
                // std::cout<
    
            // If the ray is not blocked in the middle
            if(intersection_p_x.happened && intersection_p_x.m->hasEmission()) {
                // std::cout<<1<
                Vector3f NN = intersection_p_x.normal;
                L_dir = intersection_p_x.m->m_emission * m->eval(ray.direction, dir_p_x, N) * dotProduct(dir_p_x, N) * dotProduct(-dir_p_x, NN) / intersection_p_x.distance / pdf_light;
            }
    
            // Test Russian Roulette with probability RussianRoulette
            if(get_random_float() <= RussianRoulette) {
                // Trace a ray r(p, wi)
                // std::cout<<1<
                Vector3f dir_i = m->sample(ray.direction, N).normalized();
                Ray ray_p_diri(hitPoint, dir_i);
                Intersection intersection_p_diri = Scene::intersect(ray_p_diri);
                
                // If ray r hit a non-emitting object at q
                if(intersection_p_diri.happened && !intersection_p_diri.m->hasEmission()) {
                    L_indir = castRay(ray_p_diri, depth+1) * m->eval(ray.direction, dir_i, N) * dotProduct(dir_i, N) / m->pdf(ray.direction, dir_i, N) / RussianRoulette;
                }
            }
    
            return m->getEmission() + L_dir + L_indir;
        } else {
            return Vector3f(0,0,0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    改进结果如下(SPP=16)


    SPP=256 结果如下
    请添加图片描述


    进阶代码

    多线程

    如果不使用多线程,那么渲染会特别慢,这里 SPP=4,就使用了 24 分钟
    其实我们对于每一个像素射出一条光线,其本身就特别适合多线程处理,因为并不会相互影响(除了 m,这里使用 i 与 j 表示,以及进度的更新如果是在外层循环加多线程会受到影响)

    这里参考 games101 论坛大佬的评论,直接使用 #pragma omp parallel for 实现多线程

    这里对比了在外层循环 j j j和内层循环 i i i以及内存循环 k k k分别使用多线程的时间对比(SPP=16),可以发现在外层循环加多线程会更快(因为内层循环在所有线程完成之后,再进行外层循环的下一次遍历,这时就会产生线程空闲)

    内层循环 k k k使用多线程

    内层循环 i i i使用多线程

    外层循环 j j j使用多线程

    Microfacet

    这里微表面模型建议可以先观看 Games202的第十节,有一个更深入的了解。这里主要参考这位大佬的代码,并对一些地方做了调整。

    首先在场景中添加一个 Microfacet 材质的球

    	Material* red = new Material(DIFFUSE, Vector3f(0.0f));
        red->Kd = Vector3f(0.63f, 0.065f, 0.05f);
        Material* green = new Material(DIFFUSE, Vector3f(0.0f));
        green->Kd = Vector3f(0.14f, 0.45f, 0.091f);
        Material* white = new Material(DIFFUSE, Vector3f(0.0f));
        white->Kd = Vector3f(0.725f, 0.71f, 0.68f);
        Material* light = new Material(DIFFUSE, (8.0f * Vector3f(0.747f+0.058f, 0.747f+0.258f, 0.747f) + 15.6f * Vector3f(0.740f+0.287f,0.740f+0.160f,0.740f) + 18.4f *Vector3f(0.737f+0.642f,0.737f+0.159f,0.737f)));
        light->Kd = Vector3f(0.65f);
        Material* m = new Material(Microfacet, Vector3f(0.0f));
        // m->Ks = Vector3f(0.45, 0.45, 0.45);
        m->Kd = Vector3f(0.3, 0.3, 0.25);
        Sphere sphere1(Vector3f(150, 100, 300), 100, m);
    
        MeshTriangle floor("../models/cornellbox/floor.obj", white);
        // MeshTriangle shortbox("../models/cornellbox/shortbox.obj", white);
        // MeshTriangle tallbox("../models/cornellbox/tallbox.obj", white);
        MeshTriangle left("../models/cornellbox/left.obj", red);
        MeshTriangle right("../models/cornellbox/right.obj", green);
        MeshTriangle light_("../models/cornellbox/light.obj", light);
    
        scene.Add(&floor);
        scene.Add(&sphere1);
        // scene.Add(&shortbox);
        // scene.Add(&tallbox);
        scene.Add(&left);
        scene.Add(&right);
        scene.Add(&light_);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    下面是CookTorrance模型的公式,主要用于计算镜面反射

    那么根据能量守恒,最后反射的光由漫反射与镜面反射组成,即 ( 1 − F ) ∗ r d + r s (1-F)*r_d + r_s (1F)rd+rs,这里的 F F F在这里为菲涅尔项,我觉得这里可能考虑折射的光线有的部分成为了漫反射。漫反射还是按照之前的做法(之前的做法,就是部分变成了漫反射),镜面反射就按照 CookTorrance 模型计算

    漫反射计算公式如下,可以看到漫反射就是一个常数, ρ \rho ρ 就代表了颜色,如果为1,就是没有吸收,全均匀地反射出去

     Vector3f diffuse = Kd / M_PI;
    
    • 1

    这里 F 为菲涅尔项,因为反射率依赖入射角度,具体公式如下,代码框架中也也已经给出具体代码

     void fresnel(const Vector3f &I, const Vector3f &N, const float &ior, float &kr) const
        {
            float cosi = clamp(-1, 1, dotProduct(I, N));
            float etai = 1, etat = ior;
            if (cosi > 0) {  std::swap(etai, etat); }
            // Compute sini using Snell's law
            float sint = etai / etat * sqrtf(std::max(0.f, 1 - cosi * cosi));
            // Total internal reflection
            if (sint >= 1) {
                kr = 1;
            }
            else {
                float cost = sqrtf(std::max(0.f, 1 - sint * sint));
                cosi = fabsf(cosi);
                float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));
                float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));
                kr = (Rs * Rs + Rp * Rp) / 2;
            }
            // As a consequence of the conservation of energy, transmittance is given by:
            // kt = 1 - kr;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    D 和 G 的实现方式有很多在,这里主要参考下面的公式
    其中 α \alpha α 表示粗糙度的平方, n n n 表示法线, h h h 表示半程向量, k k k 是基于粗糙度的项 k = ( r ∗ r ) / 8 k=(r*r)/8 k=(rr)/8

    D 表示微表面的法线分布,因为考虑微表面都是镜面反射,所以考虑其法线分布,决定了有多少光能反射到出射方向(宏观)

    float Material::DistributionGGX(const Vector3f &N, const Vector3f &H, const float &roughness)
    {
        float a = roughness * roughness;
        float a2 = a * a;
        float cosnh = std::max(0.0f, dotProduct(N, H));
        float cosnh2 = cosnh * cosnh;
        float x2 = (cosnh2 * (a2 - 1) + 1) * (cosnh2 * (a2 - 1) + 1);
    
        return a2 / (M_PI * x2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    G 表示阴影遮挡项,因为可能存在反射的光被其他表面给遮挡住,所以要加上这项

    float Material::GeometrySub(const float &NdotV, const float &roughness)
    {
        float r = (roughness + 1.0);
        float k = (r*r) / 8.0;
    
        float nom = NdotV;
        float denom = NdotV * (1.0 - k) + k;
        return nom / denom; 
    }
    
    float Material::GeometrySmith(const Vector3f &N, const Vector3f &V, const Vector3f &L, const float &roughness)
    {
        float NdotV = std::max(dotProduct(N, V), 0.0f);
        float NdotL = std::max(dotProduct(N, L), 0.0f);
        return GeometrySub(NdotV, roughness) * GeometrySub(NdotL, roughness);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    实现该材质,主要是通过修改 Material::eval 函数来实现的:

    Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){
        switch(m_type){
            case DIFFUSE:
            {
                // calculate the contribution of diffuse   model
                float cosalpha = dotProduct(N, wo);
                if (cosalpha > 0.0f) {
                    Vector3f diffuse = Kd / M_PI;
                    return diffuse;
                }
                else
                    return Vector3f(0.0f);
                break;
            }
            case Microfacet:
            {
                // calculate the contribution of Microfacet model
                float cosalpha = dotProduct(N, wo);
                if (cosalpha > 0.0f) {
                    float roughness = 0.35;
    
                    Vector3f V = -wi;
                    Vector3f L = wo;
                    Vector3f H = normalize(V + L);
    
                    // calculate distribution of normals: D
                    float D = DistributionGGX(N, H, roughness);
    
                    // calculate shadowing masking term: G
                    float G = GeometrySmith(N, V, L, roughness);
    
                    // calculate fresnel coefficient: F
                    float F;
                    float etat = 1.85;
                    fresnel(wo, N, etat, F);
    
                    Vector3f nominator = D * G * F;
                    float denominator = 4 * std::max(dotProduct(N, V), 0.0f) * std::max(dotProduct(N, L), 0.0f);
                    Vector3f specular = nominator / std::max(denominator, 0.001f);
    
                    // energy balance
                    Vector3f diffuse = Kd / M_PI;
                    return (Vector3f(1.0f) - F) * diffuse + specular;
                }
                else
                    return Vector3f(0.0f);
                break;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    结果如下(SPP=16,可见球上噪点还是很明显的)

    看别人博客说要修改球与光线相交的判断,更改完代码如下

    Intersection getIntersection(Ray ray){
            Intersection result;
            result.happened = false;
            Vector3f L = ray.origin - center;
            float a = dotProduct(ray.direction, ray.direction);
            float b = 2 * dotProduct(ray.direction, L);
            float c = dotProduct(L, L) - radius2;
            float t0, t1;
            if (!solveQuadratic(a, b, c, t0, t1)) return result;
            if (t0 < 0) t0 = t1;
            if (t0 < 0) return result;
    
    		// 相交判定修改
    		if (t0 > 0.5) {
    			result.happened = true;
    
    			result.coords = Vector3f(ray.origin + ray.direction * t0);
    			result.normal = normalize(Vector3f(result.coords - center));
    			result.m = this->m;
    			result.obj = this;
    			result.distance = t0;
    		}
            return result;
    
        }
    
    Vector3f evalDiffuseColor(const Vector2f &st)const {
            //return m->getColor();
            return {};
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    结果如下

  • 相关阅读:
    “磐云杯”中职网络安全技能大赛A模块新题
    Java继承中成员方法的访问特点
    慢跑是早上跑好,还是晚上跑好?对的时间跑,效果可能还翻倍
    如何构建前端自动化测试套件?
    网络安全(黑客)自学
    golang map clear
    【GDB】 .gdbinit 文件
    2022/09/06 day03:命令2
    Mac/Linux安装使用 opengauss数据库步骤
    用java刷点题-lc101. 对称二叉树
  • 原文地址:https://blog.csdn.net/weixin_44491423/article/details/127552276