GAMES101:作业7

GAMES101:作业7

附其他所有作业超链接如下:
Games101 作业0:作业0
Games101 作业1:作业1
Games101 作业2:作业2
Games101 作业3:作业3
Games101 作业4:作业4
Games101 作业5:作业5
Games101 作业6:作业6
Games101 作业7:作业7

完整代码获取途径:
https://github.com/liupeining/Games_101_homework

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

修改的内容:
你需要从上一次编程练习中直接拷贝以下函数到对应位置:
• Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,请直接将上次实验中实现的内容粘贴在此。

/*inline 函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,
它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。
为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。
栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。
*/
inline Intersection Triangle::getIntersection(Ray ray)
{
    Intersection inter;
    //如果结果大于0,那么这两个向量的夹角小于90度;
    //这里normal是从三角指向眼的,因此若同方向则不可能穿过三角
    if (dotProduct(ray.direction, normal) > 0)
        return inter; // happen = false;
    /*
    Intersection的定义
        Intersection(){
        happened=false;
        coords=Vector3f();
        normal=Vector3f();
        distance= std::numeric_limits::max();
        obj =nullptr;
        m=nullptr;
    }
    */
    double u, v, t_tmp = 0;
    Vector3f pvec = crossProduct(ray.direction, e2); //S1
    double det = dotProduct(e1, pvec);               //S1*E1(顺便说一句,能推出S1*E1=-D*N)
    if (fabs(det) < EPSILON) //const float EPSILON = 0.00001; 分母特别小将导致t特别大,相当于很远很远,看不见
        return inter;
    double det_inv = 1. / det;                      // 1/S1*E1
    Vector3f tvec = ray.origin - v0;                //S
    u = dotProduct(tvec, pvec) * det_inv;           //b1
    if (u < 0 || u > 1)  
        return inter;
    Vector3f qvec = crossProduct(tvec, e1);         //S2
    v = dotProduct(ray.direction, qvec) * det_inv;  //b2
    if (v < 0 || u + v > 1) 
        return inter;
    t_tmp = dotProduct(e2, qvec) * det_inv;         //t
    if(t_tmp<0) 
        return inter;

    inter.distance = t_tmp;
    inter.happened = true;
    inter.m = m;
    inter.obj = this;
    inter.normal = normal;
    inter.coords = ray(t_tmp);
    return inter;
}

• IntersectP(const Ray& ray, const Vector3f& invDir, const std::array& dirIsNeg) in the Bounds3.hpp: 这个函数的作用是判断包围盒 BoundingBox 与光线是否相交,请直接将上次实验中实现的内容粘贴在此处,并且注意检查 t_enter = t_exit 的时候的判断是否正确。
这句提示其实非常关键!老师上课的时候经常提到,图形学很少关心相等的时候的条件,但是这里确实会影响到输出的结果。假如 t_enter = t_exit 时返回的是false,那么作业6并不会出任何问题,但作业7中会导致只能渲染出左侧的盒子和墙体。因此,如果你在作业6中写的是t_enter < t_exit return true,请务必改成小于等于。

inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
                                const std::array<int, 3>& dirIsNeg) const
{   
    //invDir = 1 / D; t = (Px - Ox) / dx
    float t_Min_x = (pMin.x - ray.origin.x)*invDir[0];
    float t_Min_y = (pMin.y - ray.origin.y)*invDir[1];
    float t_Min_z = (pMin.z - ray.origin.z)*invDir[2];
    float t_Max_x = (pMax.x - ray.origin.x)*invDir[0];
    float t_Max_y = (pMax.y - ray.origin.y)*invDir[1];
    float t_Max_z = (pMax.z - ray.origin.z)*invDir[2];

    //如果发现射线的方向是反的,调换t_min和t_max的位置。
    if(dirIsNeg[0])
    {
        float t = t_Min_x;
        t_Min_x = t_Max_x;
        t_Max_x = t;
    }
    if(dirIsNeg[1])
    {
        float t = t_Min_y;
        t_Min_y = t_Max_y;
        t_Max_y = t;
    }
    if(dirIsNeg[2])
    {
        float t = t_Min_z;
        t_Min_z = t_Max_z;
        t_Max_z = t;
    }
 
    float t_enter = std::max(t_Min_x, std::max(t_Min_y, t_Min_z));
    float t_exit  = std::min(t_Max_x, std::min(t_Max_y, t_Max_z));
    if(t_enter <= t_exit && t_exit >= 0)
        return true;
    return false;     
}

• getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: BVH查找过程,请直接将上次实验中实现的内容粘贴在此处.

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    Intersection inter;
    //invdir = 1 / D; bounds3.hpp中会用到。
    Vector3f invdir(1 / ray.direction.x, 1 / ray.direction.y, 1 / ray.direction.z);
    //判断射线的方向正负,如果负,为1;bounds3.hpp中会用到。
    std::array<int, 3> dirIsNeg;
    dirIsNeg[0] = ray.direction.x < 0;
    dirIsNeg[1] = ray.direction.y < 0;
    dirIsNeg[2] = ray.direction.z < 0;
    
    //没有交点
    if(!node -> bounds.IntersectP(ray, invdir, dirIsNeg))
        return inter;
    //有交点,且该点为叶子节点,去和三角形求交
    if(node -> left == nullptr && node -> right == nullptr)
        return node -> object -> getIntersection(ray);
    //该点为中间节点,继续判断,并返回最近的包围盒交点
    Intersection hit1 = getIntersection(node -> left,  ray);
    Intersection hit2 = getIntersection(node -> right, ray);
    return hit1.distance < hit2.distance ? hit1 : hit2;
}

当然也可以粘贴SAH,详情见作业6的帖子。

https://blog.csdn.net/qq_41765657/article/details/121865049?spm=1001.2014.3001.5501

在本次实验中,你只需要修改这一个函数:
• 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 的概率

Vector3f Scene::castRay(const Ray &ray, int depth) const
{
    Vector3f L_dir = {0, 0, 0}, L_indir = {0, 0, 0};
    Intersection intersection = Scene::intersect(ray); //求一条光线与场景的交点
    if (!intersection.happened) //没交点
        return {};
    if (intersection.m->hasEmission()) //一、交点是光源:
        return intersection.m->getEmission();
    /*
    bool Material::hasEmission() {
        if (m_emission.norm() > EPSILON) return true; 这里只有光才>0
        else return false;
    }
    */
    //---------二、交点是物体:1)向光源采样计算direct----------
    Intersection lightpos;
    float lightpdf = 0.0f;
    sampleLight(lightpos, lightpdf);//获得对光源的采样,包括光源的位置和采样的pdf(在场景的所有光源上按面积 uniform 地 sample 一个点,并计算该 sample 的概率密度)
    Vector3f collisionlight = lightpos.coords - intersection.coords;
    float dis = dotProduct(collisionlight, collisionlight);
    Vector3f collisionlightdir = collisionlight.normalized();   
    Ray light_to_object_ray(intersection.coords, collisionlightdir);
    Intersection light_to_anything_ray = Scene::intersect(light_to_object_ray);
    auto f_r = intersection.m -> eval(ray.direction, collisionlightdir, intersection.normal);
    if (light_to_anything_ray.distance - collisionlight.norm() > -0.005){  //没有遮挡,有横条就是数太小了!
        //L_dir = L_i * f_r * cos_theta * cos_theta_x / |x - p | ^ 2 / pdf_light
        L_dir = lightpos.emit * f_r * dotProduct(collisionlightdir, intersection.normal) * dotProduct(-collisionlightdir, lightpos.normal) / dis / lightpdf;
    }

    //--------二、交点是物体:2)向其他物体采样递归计算indirect---------
    if (get_random_float() > RussianRoulette)     //打到物体后对半圆随机采样使用RR算法
        return L_dir;
    Vector3f w0 = intersection.m -> sample(ray.direction, intersection.normal).normalized();
    Ray object_to_object_ray(intersection.coords, w0);
    Intersection islight = Scene::intersect(object_to_object_ray);
    if (islight.happened && !islight.m->hasEmission())
    {   // shade(q, wi) * f_r * cos_theta / pdf_hemi / P_RR
        float pdf = intersection.m->pdf(ray.direction, w0, intersection.normal);
        f_r = intersection.m->eval(ray.direction, w0, intersection.normal);
        L_indir = castRay(object_to_object_ray, depth + 1) * f_r * dotProduct(w0, intersection.normal) / pdf / RussianRoulette;
    }
    return L_dir + L_indir;
}

做出来图片有横条的原因在这里:这个数字不能过于接近0。

if (light_to_anything_ray.distance - collisionlight.norm() > -0.005){  //没有遮挡,有横条就是数太小了!

由于本人电脑虚拟机不太稳定,日常闪退,因此我渲染了474秒,取SPP=30,结果如下:
在这里插入图片描述
可以看到,不到七分钟的渲染结果还是非常不错的~(有锯齿是因为调整了输出尺寸,为了提高渲染速度)
GAMES101:作业7_第1张图片

你可能感兴趣的:(c++,几何学,图形学,cg)