GAMES101作业(06)- 加速结构(求光线与包围盒的交点、使用BVH加速结构)

作业来自官网

知识点整理

总览

在之前的编程练习中,我们实现了基础的光线追踪算法,具体而言是光线传输、光线与三角形求交。我们采用了这样的方法寻找光线与场景的交点:遍历场景中的所有物体,判断光线是否与它相交。在场景中的物体数量不大时,该做法可以取得良好的结果,但当物体数量增多、模型变得更加复杂,该做法将会变得非常低效。因此,我们需要加速结构来加速求交过程。在本次练习中,我们重点关注物体划分算法 Bounding Volume Hierarchy (BVH)。本练习要求你实现 Ray-Bounding Volume 求交与 BVH 查找。

任务

  • Render() in Renderer.cpp: 将你的光线生成过程粘贴到此处,并且按照新框架更新相应调用的格式。
  • Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,并且按照新框架更新相应相交信息的格式。

在本次编程练习中,你需要实现以下函数:

  • IntersectP(const Ray& ray, const Vector3f& invDir,const std::array<int, 3>& dirIsNeg) in the Bounds3.hpp: 这个函数的作用是判断包围盒 BoundingBox 与光线是否相交,你需要按照课程介绍的算法实现求交过程。
  • getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: 建立 BVH 之后,我们可以用它加速求交过程。该过程递归进行,你将在其中调用你实现的 Bounds3::IntersectP.

解答

Render() in Renderer.cpp

  1. 相比于上次观察点(eye_pos)变了。
  2. castray的调用方式有变化,是调用scene对象的castray方法,且参数是Ray。
void Renderer::Render(const Scene& scene)
{
    
    
    std::vector<Vector3f> framebuffer(scene.width * scene.height);

    float scale = tan(deg2rad(scene.fov * 0.5));
    float imageAspectRatio = scene.width / (float)scene.height;
    //相比于上次观察点变了
    Vector3f eye_pos(-1, 5, 10);
    int m = 0;
    for (uint32_t j = 0; j < scene.height; ++j) {
    
    
        for (uint32_t i = 0; i < scene.width; ++i) {
    
    
            float x = (2 * (i + 0.5) / (float)scene.width - 1) *
                      imageAspectRatio * scale;
            float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;
            Vector3f dir = Vector3f(x, y, -1); 
            dir=normalize(dir);
            
            //相比于上次不是直接调用castray了,是先定义光线ray,然后在传到scene的castray方法中。
            Ray ray(eye_pos,dir);
            framebuffer[m++]=scene.castRay(ray,0);
        }
        UpdateProgress(j / (float)scene.height);
    }
    UpdateProgress(1.f);

    // save framebuffer to file
    FILE* fp = fopen("binary.ppm", "wb");
    (void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
    for (auto i = 0; i < scene.height * scene.width; ++i) {
    
    
        static unsigned char color[3];
        color[0] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].x));
        color[1] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].y));
        color[2] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].z));
        fwrite(color, 1, 3, fp);
    }
    fclose(fp);    
}

Triangle::getIntersection in Triangle.hpp

前面的代码框架已经给出了,在此只要对Intersection类的对象赋值即可。

  1. 坐标、距离(t)、是否有交点是函数内算出来的。
  2. 材质、法线和obj指针都是这个三角形内的成员属性。
inline Intersection Triangle::getIntersection(Ray ray)
{
    
    
    Intersection inter;
    //说明光线不是射入的
    if (dotProduct(ray.direction, normal) > 0)
        return inter;
    double u, v, t_tmp = 0;
    //pvec相当于s1
    Vector3f pvec = crossProduct(ray.direction, e2);
    double det = dotProduct(e1, pvec);
    if (fabs(det) < EPSILON)
        return inter;

    double det_inv = 1. / det;
    //tvec相当于s1
    Vector3f tvec = ray.origin - v0;
    u = dotProduct(tvec, pvec) * det_inv;
    if (u < 0 || u > 1)
        return inter;
    //qvec相当于s2
    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;
    
    //填充交点坐标,t,是否相交等交点的基本值
    inter.happened=true;
    //Ray中重载了(),ray()t_tmp=ray.origin+ray.direction*t_tmp
    inter.coords=Vector3f(ray(t_tmp));
    inter.distance=t_tmp;
    //交点是在这个三角形上,所以把这个三角形的材质、法线和指针传给交点。
    inter.m=m;
    inter.normal=normal;
    inter.obj=this;    

    return inter;
}

IntersectP in the Bounds3.hpp

主要用如下公式来计算 t e n t e r t_{enter} tenter t e x i t t_{exit} texit:
在这里插入图片描述

  1. 注意dirIsNeg存储的光线方向,如果是方向是负数的话,说明较大的那个面进,较小的那个面出,所以要调换一下位置。
  2. 乘倒数比除法快。
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
                                const std::array<int, 3>& dirIsNeg) const
{
    
    
    float tx_enter=invDir.x*(pMin.x-ray.origin.x);
    float tx_exit=invDir.x*(pMax.x-ray.origin.x);
    if(dirIsNeg[0])
        std::swap(tx_enter,tx_exit);
    float ty_enter=invDir.y*(pMin.y-ray.origin.y);
    float ty_exit=invDir.y*(pMax.y-ray.origin.y);
    if(dirIsNeg[1])
        std::swap(ty_enter,ty_exit);
    float tz_enter=invDir.z*(pMin.z-ray.origin.z);
    float tz_exit=invDir.z*(pMax.z-ray.origin.z);
    if(dirIsNeg[2])
        std::swap(tz_enter,tz_exit);
    return tx_exit>=0&&tx_enter<=tx_exit&&ty_exit>=0&&ty_enter<=ty_exit&&tz_exit>=0&&tz_enter<=tz_exit;
}

getIntersection in BVH.cpp

使用BVH结构求ray打到的离光线原点最近的交点。

  1. 递归条件为叶子节点,所以只要左右孩子指针为空就可以直接返回。
  2. 最先打到的点就是要计算的点,即t最小的点。
  3. 按住ctrl看看BVHBuildNode的定义会更好的理解代码。
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    
    
    //如果是叶子,直接返回。
    if (node->left==nullptr&&node->right==nullptr)
    {
    
    
        return node->object->getIntersection(ray);
    }
    Intersection inter;   
    //记录ray的方向
    std::array<int,3UL> dirIsNeg;
    dirIsNeg[0]=ray.direction.x<0;
    dirIsNeg[1]=ray.direction.y<0;
    dirIsNeg[2]=ray.direction.z<0;
    //分别看看和左右包围盒有无交点
    if (node->left->bounds.IntersectP(ray,ray.direction_inv,dirIsNeg))
    {
    
    
        inter = getIntersection(node->left,ray);
    }
    //如果左右都有值,就选择离的近的那个。
    if (node->right->bounds.IntersectP(ray,ray.direction_inv,dirIsNeg))
    {
    
    
        Intersection tmpInter=getIntersection(node->right,ray);
        if (inter.distance>=tmpInter.distance)
            inter = tmpInter;
    }
    return inter;
}

结果

结果是build目录下的ppm格式图片。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43399489/article/details/121697524