GAMES101-ASSIGNMENT7(作业7)

总览

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

 调通框架

 2.1修改的内容

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

2.2你需要迁移的内容

你需要从上一次编程练习中直接拷贝以下函数到对应位置:
• Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,请直接将上次实验中实现的内容粘贴在此。
• IntersectP(const Ray& ray, const Vector3f& invDir,const std::array<int, 3>& dirIsNeg) in the Bounds3.hpp: 这个函数的2作用是判断包围盒 BoundingBox 与光线是否相交,请直接将上次实验中实现的内容粘贴在此处,并且注意检查 t_enter = t_exit 的时候的判断是否正确。
• getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: BVH查找过程,请直接将上次实验中实现的内容粘贴在此处.

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

mkdir build
cd ./ build
cmake ..
make

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

 3 开始实现

3.1代码框架

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

• 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 Maerial.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 的概率

 IntersectP(const Ray& ray, const Vector3f& invDir,const std::array<int, 3>& 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
{
    Vector3f tmin = (pMin - ray.origin) * invDir;
    Vector3f tmax = (pMax - ray.origin) * invDir;
    if (dirIsNeg[0])
        std::swap(tmin.x, tmax.x);
    if (dirIsNeg[1])
        std::swap(tmin.y, tmax.y);
    if (dirIsNeg[2])
        std::swap(tmin.z, tmax.z);
    float texit = std::min(tmax.x, std::min(tmax.y, tmax.z));
    float tenter = std::max(tmin.x, std::max(tmin.y, tmin.z));
    return tenter <= texit&& texit >= 0;
}

BVHBuildNode* BVHAccel::recursiveBuild(std::vector<Object*> objects)
{
    BVHBuildNode* node = new BVHBuildNode();

    // Compute bounds of all primitives in BVH node
    Bounds3 bounds;
    for (int i = 0; i < objects.size(); ++i)
        bounds = Union(bounds, objects[i]->getBounds());
    if (objects.size() == 1) {
        // Create leaf _BVHBuildNode_
        node->bounds = objects[0]->getBounds();
        node->object = objects[0];
        node->left = nullptr;
        node->right = nullptr;
        node->area = objects[0]->getArea();
        return node;
    }
    else if (objects.size() == 2) {
        node->left = recursiveBuild(std::vector{objects[0]});
        node->right = recursiveBuild(std::vector{objects[1]});

        node->bounds = Union(node->left->bounds, node->right->bounds);
        node->area = node->left->area + node->right->area;
        return node;
    }
    else {
        Bounds3 centroidBounds;
        for (int i = 0; i < objects.size(); ++i)
            centroidBounds =
                Union(centroidBounds, objects[i]->getBounds().Centroid());
        int dim = centroidBounds.maxExtent();
        switch (dim) {
        case 0:
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().x <
                       f2->getBounds().Centroid().x;
            });
            break;
        case 1:
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().y <
                       f2->getBounds().Centroid().y;
            });
            break;
        case 2:
            std::sort(objects.begin(), objects.end(), [](auto f1, auto f2) {
                return f1->getBounds().Centroid().z <
                       f2->getBounds().Centroid().z;
            });
            break;
        }

        auto beginning = objects.begin();
        auto middling = objects.begin() + (objects.size() / 2);
        auto ending = objects.end();

        auto leftshapes = std::vector<Object*>(beginning, middling);
        auto rightshapes = std::vector<Object*>(middling, ending);

        assert(objects.size() == (leftshapes.size() + rightshapes.size()));

        node->left = recursiveBuild(leftshapes);
        node->right = recursiveBuild(rightshapes);

        node->bounds = Union(node->left->bounds, node->right->bounds);
        node->area = node->left->area + node->right->area;
    }

    return node;
}

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

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    Intersection inter;
    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;

}

 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;
    if(t_tmp<0) 
        return inter;
    // TODO find ray triangle intersection
    inter.normal = normal;
    inter.coords = ray(t_tmp);
    inter.distance = t_tmp;
    inter.happened = true;
    inter.m = m;
    inter.obj = this;
    
    return inter;
}

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


// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
    // TO DO Implement Path Tracing Algorithm here
    //创建变量以储存直接和间接光照计算值
    Vector3f L_dir = {0,0,0} , L_indir = {0,0,0};
    //1.判断是否有交点:光线与场景中物体相交?
    Intersection intersection = Scene::intersect(ray); //求一条光线与场景的交点
    if (!intersection.happened) //没交点
        return {};
    //2.ray打到光源了:说明渲染方程只用算前面的自发光项,因此直接返回材质的自发光项
    if (intersection.m->hasEmission()){//一、交点是光源:
        // if (depth == 0)//第一次打到光
            return intersection.m->getEmission();
        // return {};//弹射打到光,直接返回0,0.0
    }
    
    //---------二、交点是物体:1)向光源采样计算direct----------
    Intersection lightpos;
    float lightpdf = 0.0f;
    //对场景中的光源进行采样,得到采样点light_pos和pdf_light
    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);//材质,课上说了,BRDF==材质,ws不参与计算
    //判断光源与物体间有无遮挡
    if (light_to_anything_ray.distance - collisionlight.norm() > -0.005){//无遮挡
        //渲染方程
        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;
    //随机生成一个w0方向
    Vector3f w0 = intersection.m ->sample(ray.direction , intersection.normal).normalized();//这里的w0其实没参与计算,返回的是一个随机的方向
    Ray object_to_object_ray(intersection.coords , w0);
    Intersection islight = Scene::intersect(object_to_object_ray);
    if (islight.happened && !islight.m->hasEmission()){ //光线击中的不是点光源
        float pdf = intersection.m ->pdf(ray.direction,w0,intersection.normal);
        f_r = intersection.m->eval(ray.direction,w0,intersection.normal);//材质,课上说了,BRDF==材质,ws不参与计算
        //渲染方程
        L_indir = castRay(object_to_object_ray,depth+1)*f_r*dotProduct(w0 , intersection.normal) / pdf / RussianRoulette;
    }
    return L_dir + L_indir;


}

结果:

 PS:在windows上运行的时候需要修改global.cpp文件的以下函数,不然可能会跑不了

inline float get_random_float()

{

    static std::random_device dev;

    static std::mt19937 rng(dev());

    static std::uniform_real_distribution<float> dist(0.f, 1.f); // distribution in range [0,1]

    return dist(rng);

}

猜你喜欢

转载自blog.csdn.net/qq_48626761/article/details/126853620