(史上最易理解!)Games101作业6-代码分析和答案

描述

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

所以,这次作业主要是需要我们熟悉有BVH结构下的光线追踪是怎么实现的,学习代码框架

渲染的大致过程

实现渲染的过程是从main开始,先导入物体和光源到scene里面,建立好scene的bvh树,这个树全部是对于兔子的三角形的树,然后调用r.Render(scene)开始渲染,渲染时,是对于每一个像素建立光线使用 scene.castRay( r, 0 );来得到这个光线打到的颜色,其中在castRay里面,我们为了得到交点,使用了 Scene::intersect(ray);来得到交点,而intersect函数就是调用当前场景的BVH的成员函数getIntersection来递归找到交点,最后利用交点的信息着色,绘制图片!!!

从0开始的框架理解

想看作业的按照目录跳转!!我们先看看有什么东西,渲染逻辑在任务部分!

global.hpp

辅助函数,有解方程的函数,实现进度条的函数,这些只要知道就行了

light.hpp

光线类,起点和强度,其实是点光源

AreaLight类

这个是继承自点光源的,是实现了面光源:下面详细介绍这个类

  1. 继承关系:

    • AreaLight 继承自 Light 类,这意味着它继承了 Light 类的属性方法,比如,点光的位置,强度
  2. 构造函数:

    • 构造函数接受两个 Vector3f 参数:p 代表光源的位置,i 代表光源的强度。
    • 构造函数中初始化了光源的一些默认属性:
      • normal: 光源表面的法线向量,默认为 (0, -1, 0),指向负 y 轴方向。
      • u 和 v: 光源表面的两个正交向量,默认为 (1, 0, 0) 和 (0, 0, 1),分别指向 x 轴和 z 轴方向。
      • length: 光源的长度,默认为 100。
  3. 成员变量:

    • normal: 光源表面的法线向量。
    • u 和 v: 光源表面的两个正交向量。
    • length: 光源的长度。
  4. 成员函数:

    • SamplePoint(): 返回光源表面上随机选择的一个点。这个点是通过在光源的平面上随机采样得到的,具体来说,是在 u 和 v 向量方向上随机移动得到的

Ray.hpp类

光线类,就是光线的起点,方向和传播时间 t ,以及 t 时间对应的光的位置等等

Vector.hpp

手搓出向量,包括一些向量运算,还有对于两个向量线性插值函数lerp,单位化向量,两个向量最大化等等

Intersection.hpp

一个结构体,储存交点的信息,包括是否相交,交点坐标,交点的法向量,距离,相交的物体是什么,交点的材质是哪一个

struct Intersection
{
    Intersection(){
        happened=false;
        coords=Vector3f();
        normal=Vector3f();
        distance= std::numeric_limits<double>::max();
        obj =nullptr;
        m=nullptr;
    }
    bool happened;
    Vector3f coords;
    Vector3f normal;
    double distance;
    Object* obj;
    Material* m;
};

object.hpp

物体的父类,有很多虚函数,等着被子类实现的,包括两种判断是否有交点的函数,交点信息的成员变量,得到表面属性的函数,得到漫反射颜色的函数,得到这个物体的包围盒的函数


class Object
{
public:
    Object() {}
    virtual ~Object() {}
    virtual bool intersect(const Ray& ray) = 0;
    virtual bool intersect(const Ray& ray, float &, uint32_t &) const = 0;
    virtual Intersection getIntersection(Ray _ray) = 0;
    virtual void getSurfaceProperties(const Vector3f &, const Vector3f &, const uint32_t &, const Vector2f &, Vector3f &, Vector2f &) const = 0;
    virtual Vector3f evalDiffuseColor(const Vector2f &) const =0;
    virtual Bounds3 getBounds()=0;
};

Material.hpp

这是材质类,里面有材质的一些必要参数和方法函数,比如返回对应位置的颜色是什么,如果是纯色的材质还可以返回材质颜色,材质种类什么的

OBJ_Loader类

这个类很大,主要包括:

  • 向量定义和运算
  • 一个Vertex 结构体,包含顶点的位置,法向量和材质uv坐标。表示网格或三角形的每一个顶点
  • 材质结构体,有材质名字,反射指数Ns ,折射率Ni , 溶解率d , 光照模型illum以及后面的map什么什么的,都是文件地址,可以使用这些文件初始我们的材质信息
  • 网格结构体Mesh , 包括网格的名字,顶点数组,顶点索引, 材质结构体定义的网格的材质
  • 数学运算辅助函数math,比如求点乘,求模长 , 叉乘 , 求两个向量的角度,求投影长度
  • 算法辅助函数algorithm,比如有判断是否在三角形里面,是否在一条直线一侧,计算一个三角形的法线
  • 读取obj文件的类,不需要掌握

接下来看稍微变化一点的,也是核心部分,从主函数开始,我们遇到新的就介绍新的


Main函数

主函数,首先定义了一个场景,叫做scene,那这个场景里面有什么函数和属性或者变量呢?

Scene.hpp介绍(重要)

场景类是渲染器的核心组件之一,必须有这个场景才会在场景上进行渲染,主要作用负责管理场景中的所有对象、光源以及相关配置选项

成员变量:

  • 渲染宽高,相机张角fov
  • 背景色
  • 渲染深度
  • 所有物体的指针
  • 所有光源
  • 一整个加速结构BVH,是一个指针(毕竟是树结构嘛)

成员函数:

  • 添加物体和光源到物体和光源列表
  • 获取物体和光源的函数
  • intersect(const Ray& ray) const  作用是求指定光线和场景类物体表面的交点
  • buildBVH()   作用是构建BVH
  • castRay(const Ray &ray, int depth) const,实现递归计算渲染,最后返回着色渲染的颜色
  • trace()实现光线的传播,判断是否与物体相交,而且还告诉你相交的物体是什么
  • HandleAreaLight()处理面光的光照过程
  • 计算反射和折射和fresnel效应的函数,其中fresnel函数计算出来的kr是决定反射颜色和折射颜色最后混合时格各自的占比的

现在我们到cpp看看这些函数怎么实现的

Scene.cpp介绍

1.建立BVH

使用new BVHAccel()建立了BVH结构,BVH实现逻辑后面再看,其实要构建两个 BVH,一个是针对空间中物体的 BVH,另一个是针对单一物体的组成网格的 BVH,由于这次作业只有一个兔子,所以我们建立的其实是对于组成兔子的所有三角形的BVH

void Scene::buildBVH() {
    printf(" - Generating BVH...\n\n");
    this->bvh = new BVHAccel(objects, 1, BVHAccel::SplitMethod::NAIVE);
}

2.相交函数

直接返回指定光线在bvh加速下的交点,这个交点是Intersection类型的,这个类型里面有交点的所有信息,包括是否相交,交点坐标,交点的法向量,距离,相交的物体是什么,交点的材质是哪一个

Intersection Scene::intersect(const Ray &ray) const
{
    return this->bvh->Intersect(ray);
}

3.trace函数

这个bool函数的参数有:光线ray , 所有物体,当前最近的交点的t,以及交点所在的物体指针或者交点所在的三角形的索引(主要是区分光线和隐式表面的求交和三角形组成的物体的求交),这两个都是双向的,是会根据函数结果变化的

函数内容:一个一个物体看,如果和指定光线有交点同时交点还更近,就更新相交物体和t和物体的索引

最后这个函数:返回的是是否相交bool类型,同时得到的有最近相交的点对应的光传播的时间tNear物体以及索引,有了tNear代入光线传播方程就可以计算出交点了

bool Scene::trace(
        const Ray &ray,
        const std::vector<Object*> &objects,
        float &tNear, uint32_t &index, Object **hitObject)
{
    *hitObject = nullptr;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        float tNearK = kInfinity;
        uint32_t indexK;
        Vector2f uvK;
        if (objects[k]->intersect(ray, tNearK, indexK) && tNearK < tNear) {
            *hitObject = objects[k];
            tNear = tNearK;
            index = indexK;
        }
    }


    return (*hitObject != nullptr);
}

4.castRay()

大致思路:这个函数是递归渲染函数,对于传进来光线ray,直接放入场景看看有无交点,这个求交过程使用了bvh加速的 ,最后直接得到物体交点,根据平面类型和交点携带的信息渲染着色,其中有反射折射就利用计算出来的反射折射方向重新建立一个新的ray,递归调用castRay(),最后hitColor要根据kr分配各自颜色占比。

来看注释:

Vector3f Scene::castRay(const Ray &ray, int depth) const
{
    if (depth > this->maxDepth) { //大于递归就结束
        return Vector3f(0.0,0.0,0.0);
    }

    //求这根光线与场景的交点,这个求交过程使用了bvh加速的
    Intersection intersection = Scene::intersect(ray);
    Material *m = intersection.m;
    Object *hitObject = intersection.obj;
    Vector3f hitColor = this->backgroundColor;
//    float tnear = kInfinity;
    Vector2f uv;
    uint32_t index = 0;
    if(intersection.happened) {//有交点

        Vector3f hitPoint = intersection.coords;//取出交点
        Vector3f N = intersection.normal; // 取出法向量
        Vector2f st; // st coordinates
        hitObject->getSurfaceProperties(hitPoint, ray.direction, index, uv, N, st);
//        Vector3f tmp = hitPoint;
        switch (m->getType()) {
            //根据表面类型区别着色
            case REFLECTION_AND_REFRACTION:
            {
                Vector3f reflectionDirection = normalize(reflect(ray.direction, N));
                Vector3f refractionDirection = normalize(refract(ray.direction, N, m->ior));
                Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
                                             hitPoint - N * EPSILON :
                                             hitPoint + N * EPSILON;
                Vector3f refractionRayOrig = (dotProduct(refractionDirection, N) < 0) ?
                                             hitPoint - N * EPSILON :
                                             hitPoint + N * EPSILON;
                Vector3f reflectionColor = castRay(Ray(reflectionRayOrig, reflectionDirection), depth + 1);
                Vector3f refractionColor = castRay(Ray(refractionRayOrig, refractionDirection), depth + 1);
                float kr;
                fresnel(ray.direction, N, m->ior, kr);//分配两种颜色
                hitColor = reflectionColor * kr + refractionColor * (1 - kr);
                break;
            }
            case REFLECTION://只有反射
            {
                float kr;
                fresnel(ray.direction, N, m->ior, kr);
                Vector3f reflectionDirection = reflect(ray.direction, N);
                Vector3f reflectionRayOrig = (dotProduct(reflectionDirection, N) < 0) ?
                                             hitPoint + N * EPSILON :
                                             hitPoint - N * EPSILON;
                //就全是反射光
                hitColor = castRay(Ray(reflectionRayOrig, reflectionDirection),depth + 1) * kr;
                break;
            }
            default://不然就是phong着色
            {

                Vector3f lightAmt = 0, specularColor = 0;
                Vector3f shadowPointOrig = (dotProduct(ray.direction, N) < 0) ?
                                           hitPoint + N * EPSILON :
                                           hitPoint - N * EPSILON;
                //phong着色,一个一个光线着色
                for (uint32_t i = 0; i < get_lights().size(); ++i)
                {
                    auto area_ptr = dynamic_cast<AreaLight*>(this->get_lights()[i].get());
                    if (area_ptr)
                    {
                        // Do nothing for this assignment
                    }
                    else
                    {
                        Vector3f lightDir = get_lights()[i]->position - hitPoint;
                        // square of the distance between hitPoint and the light
                        float lightDistance2 = dotProduct(lightDir, lightDir);
                        lightDir = normalize(lightDir);
                        float LdotN = std::max(0.f, dotProduct(lightDir, N));
                        Object *shadowHitObject = nullptr;
                        float tNearShadow = kInfinity;
                        // is the point in shadow, and is the nearest occluding object closer to the object than the light itself?
                        bool inShadow = bvh->Intersect(Ray(shadowPointOrig, lightDir)).happened;
                        lightAmt += (1 - inShadow) * get_lights()[i]->intensity * LdotN;
                        Vector3f reflectionDirection = reflect(-lightDir, N);
                        specularColor += powf(std::max(0.f, -dotProduct(reflectionDirection, ray.direction)),
                                              m->specularExponent) * get_lights()[i]->intensity;
                    }
                }
                hitColor = lightAmt * (hitObject->evalDiffuseColor(st) * m->Kd + specularColor * m->Ks);
                break;
            }
        }
    }
    return hitColor;
}

现在场景看完了,我们继续在main里面往下看

接下来使用了 MeshTriangle 类型定义了一个兔子网格,这个网格来自场外的兔子样子的文件,然后在场景加了光和建立了BVH结构

MeshTriangle bunny("../models/bunny/bunny.obj");

scene.Add(&bunny);
scene.Add(std::make_unique<Light>(Vector3f(-20, 70, 20), 1));
scene.Add(std::make_unique<Light>(Vector3f(20, 70, 20), 1));
scene.buildBVH();

我们一个一个看:

这个MeshTriangle类型是哪来的?是在三角形类.hpp里面

Triangle.hpp介绍(重要)

这是三角形大类,里面定义了两个类 TriangleMeshTriangle, 这两个类都是从 Object 类派生的,用于表示渲染场景中的三角形对象

最开始有一个函数rayTriangleIntersect 函数 , 此函数计算一条光线是否与给定的由三个顶点 (v0, v1, v2) 定义的三角形相交。如果发生相交,则返回 true更新参数 tnearuv 以表示相交距离和三角形上交点的双线性坐标

Triangle

此类表示三维空间中的单个三角形。它存储三角形的顶点、边、法向量以及关联的材质,其中材质包括了材质的各种属性。

  • 构造函数只需要穿进去三个顶点和材质,就可以初始化这个三角形。
  • 后面是判断是否与三角形有交点的虚函数和返回交点信息的虚函数,都只是声明没有实现
  • getSurfaceProperties获得表面属性函数,这里只是单纯的将法向量返回。
  • evalDiffuseColor函数是返回对应点的漫反射颜色,但是没有实现
  • getBounds获得这个三角形范围的函数,没有实现
MeshTriangle

这是网格三角形类,继承自 Object 类,并且用于表示由多个三角形构成的网格,这个类主要用于从 OBJ 文件加载模型,并且构建一个 BVH来加速光线追踪过程中的碰撞检测,(注意着是网格物体的BVH,就是物体是网格三角形组成的话,也要将这些网格建一个BVH)

成员变量:

  • Bounds3 bounding_box:整个网格的边界框。
  • std::unique_ptr<Vector3f[]> vertices:网格的所有顶点。
  • uint32_t numTriangles:三角形的数量。
  • std::unique_ptr<uint32_t[]> vertexIndex:顶点索引数组,用于构建三角形。
  • std::unique_ptr<Vector2f[]> stCoordinates:纹理坐标数组。
  • std::vector<Triangle> triangles:网格中的所有三角形。
  • BVHAccel* bvh:指向 BVH 加速器的指针。

一句话概括这个类作用,根据你的obj文件里面的顶点和材质创建由三角形构成的网格,每一个三角形有材质什么的,还有整个网格的包围盒,并且为这个网格里的三角形建立了BVH,同时提供了利用BVH进行光线求交点 , 计算指定位置的颜色或者法向量,返回包围盒的一些成员函数

任务之一

计算得到交点的函数 getIntersection(传入的是光线) 

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)                              //det决定光是否与三角形平行
        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;                  //时间小于0返回
    inter.happened = true;          //当所有都满足时,填充交点信息
    inter.coords = ray(t_tmp);
    inter.normal = normal;
    inter.distance = t_tmp;
    inter.obj = this;  //物体就是调用这个函数的那一个对象
    inter.m = m;

    return inter;
}

下面我们回到main函数,在给场景加完灯和建立了场景的BVH后, 定义了一个渲染器r,然后就渲染出图了!

作业任务(按逻辑顺序讲解)

Bounds3.hpp

大致介绍一下包围盒这个类,完成这个类里面的一个任务

包围盒是由两个对角的顶点围起来组成的

  Vector3f pMin, pMax; 

默认的构造函数是初始包围盒的,这个包围盒前提是不知道范围的

  Bounds3()
  {
      double minNum = std::numeric_limits<double>::lowest();
      double maxNum = std::numeric_limits<double>::max();
      pMax = Vector3f(minNum, minNum, minNum);
      pMin = Vector3f(maxNum, maxNum, maxNum);
  }

还有一种初始包围盒的方法,一个点作为aabb

Bounds3(const Vector3f p) : pMin(p), pMax(p) {}

还有一种,使用两个点初始aabb,

Bounds3(const Vector3f p1, const Vector3f p2)
{
    pMin = Vector3f(fmin(p1.x, p2.x), fmin(p1.y, p2.y), fmin(p1.z, p2.z));
    pMax = Vector3f(fmax(p1.x, p2.x), fmax(p1.y, p2.y), fmax(p1.z, p2.z));
}

成员函数Diagonal()返回对角线向量 , SurfaceArea计算表面积,Centroid计算中心,Intersect计算两个aabb交集等等一堆函数

接下来就是实现函数inline bool Bounds3::IntersectP了,利用这个aabb类的各种成员变量和属性,判断光是否和aabb相交,做法就是计算时间,其中需要根据光线方向正负调整时间最大最小值

inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
                                const std::array<int, 3>& dirIsNeg) const
{

    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_y = (pMax.y - ray.origin.y) * invDir[1];
    float t_Max_z = (pMax.z - ray.origin.z) * invDir[2];

    if (dirIsNeg[0] == 0)
    {
        float t = t_Min_x;
        t_Min_x = t_Max_x;
        t_Max_x = t;
    }
    if (dirIsNeg[1] == 0)
    {
        float t = t_Min_y;
        t_Min_y = t_Max_y;
        t_Max_y = t;
    }
    if (dirIsNeg[2] == 0)
    {
        float t = t_Min_z;
        t_Min_z = t_Max_z;
        t_Max_z = t;
    }

    float t_in = std::max(t_Min_x, std::max(t_Min_y, t_Min_z));
    float t_out = std::min(t_Max_x, std::min(t_Max_y, t_Max_z));
    if (t_in < t_out && t_out >= 0)
        return true;
    else
        return false;
}

渲染过程是什么样的呢?我们来看渲染类

Renderer.hpp

渲染类,有一个装交点信息的包裹hit_payload,和构造函数

Renderer.cpp

开始定义了一个角度转化器和微小的值,然后就来到Render函数,接受一个场景,开始渲染:

定义framebuffer存颜色用于最后绘图,然后一个一个像素看,按照上一次作业的方法计算出x,y,计算出这个像素发出的光线的方向向量,建立好光线Ray , 使用Scene::castRay开始着色

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) {
            // generate primary ray direction
            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);
            Ray r( eye_pos, dir, 0);
            framebuffer[m++] = scene.castRay( r, 0 );
        }
        UpdateProgress(j / (float)scene.height);
    }
    UpdateProgress(1.f);
}

来到scene类里面的castRay,这是递归渲染函数,对于传进来光线ray,直接放入场景求交点,这个求交过程使用了bvh加速的 ,最后直接得到物体交点,根据平面类型和交点携带的信息渲染着色,最后返回这个着色的值,存进framebuffer

那在castRay里面是怎么利用BVH求交点的呢?这里使用了获取交点

Intersection intersection = Scene::intersect(ray);

来看Scene::intersect(ray)函数,直接利用这个场景的bvh里面的成员函数求交,最后返回交点

Intersection Scene::intersect(const Ray &ray) const
{
    return this->bvh->Intersect(ray);
}

那bvh是怎么建立的?

在Scene.cpp里面实现了建立BVH的函数,具体实现过程在bvh类里

void Scene::buildBVH() {
    printf(" - Generating BVH...\n\n");
    this->bvh = new BVHAccel(objects, 1, BVHAccel::SplitMethod::NAIVE);
}

接下来来看BVH.hpp 和  .cpp

BVH.hpp(重要)

是用于构建查询 BVH的数据结构

bvh节点定义

struct BVHBuildNode {
    Bounds3 bounds;  //这个bvh的包围盒,就是范围
    BVHBuildNode *left; //左bvh
    BVHBuildNode *right;//右bvh
    Object* object; //当是叶子节点时,才有这个bvh里面物体

public:
    int splitAxis=0, firstPrimOffset=0, nPrimitives=0;//分割轴,对象偏移,当前节点树包含的对象数量
    BVHBuildNode(){  //初始一下这些量
        bounds = Bounds3(); 
        left = nullptr;right = nullptr;
        object = nullptr;
    }
};

BVHAccel类:

开始定义了两种 BVH 构建策略:NAIVESAHNAIVE 表示简单的均匀分割,而 SAH 表示基于表面积的分割方法,这是一种更高效的构建策略

 enum class SplitMethod { NAIVE, SAH };

BVHAccel类的成员变量:

  • 树根
  • 最多的对象数
  • 分割法
  • 物体

BVHAccel类的成员函数:

  •  Intersect和getIntersection就是利用BVH结构查找光线ray在这个bvh结构里面的最近交点,其中getIntersection需要我们实现,该过程递归进行,你将在其中调
    用你实现的 Bounds3::IntersectP .

实现过程逻辑伪代码:

Node BVH_Intersect(Type_of_light light, BVH* tree)
{
	//先看看光线是否和盒子有相交
	if (Is_Intersect(light, tree->aabb));
	{
		//有交点先判断是不是叶子
		if (tree->lchild == nullptr && tree->rchild == nullptr)
			//是叶子直接计算这个叶子的aabb里面所有物体的最近交点并返回
			return NObjIntsection(light, tree->aabb);
		else
		{
			//递归看左右bvh树,收集最近节点
			Node l = NIntsection(light, tree->lchild);
			Node r = NIntsection(light, tree->rchild);
			return Min(l, r);//返回左右中近的那一个
		}
	}
}

根据这个很快可以写出代码:

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    std::array<int, 3> dirIsNeg;
    if (ray.direction[0] > 0)dirIsNeg[0] = 1; else dirIsNeg[0] = 0;
    if (ray.direction[1] > 0)dirIsNeg[1] = 1; else dirIsNeg[1] = 0;
    if (ray.direction[2] > 0)dirIsNeg[2] = 1; else dirIsNeg[2] = 0;

    Intersection inter;
    if (node->bounds.IntersectP(ray, ray.direction_inv, dirIsNeg))
    {
        if (node->left == nullptr && node->right == nullptr)
            return node->object->getIntersection(ray);
        Intersection l = getIntersection(node->left, ray);
        Intersection r = getIntersection(node->right, ray);

        if (l.distance < r.distance)return l;
        else return r;
    }
    else return inter;
}

BVHAccel类的构造函数:

构造函数实现过程,在BVH.cpp里,这里只留下代码核心的部分

 参数:p表示物体后面变成了primitives传入了递归函数,maxPrimsInNode表示停止建立bvh树的递归终止条件,就是bvh里面只有一个物体时,splitMethod表示全部是NAIVE分割方法

BVHAccel::BVHAccel(vector<Object*> p, int maxPrimsInNode,  SplitMethod splitMethod)
{
    if (primitives.empty())
        return;

    root = recursiveBuild(primitives); //开始递归建树
}

递归函数recursiveBuild的原理:

  • 如果 objects 向量中只有一个对象,则创建一个叶节点,该节点包含该对象的边界盒和指向该对象的指针。
  • 如果 objects 向量中有两个对象,则创建一个内部节点,该节点有两个子节点,每个子节点分别包含一个对象。
  • 如果 objects 向量中有两个以上的对象,则进行以下操作:
    • 计算所有对象的中心点的边界盒 centroidBounds
    • 确定扩展最大的维度 dim(x, y, 或 z)作为分割轴。
    • 根据中心点的坐标,沿着扩展最大的维度对原始对象进行排序。
    • 将排序后的对象向量分为两半,创建两个子向量 leftshapes 和 rightshapes
    • 递归地构建左子树和右子树。
    • 当前节点的边界盒为左子节点和右子节点的边界盒的并集。

最后返回树根到root,完成建树,最后得到图片:


总结

就这样我们分析完了所有的代码,知道了实现渲染的过程是从main开始,先导入obj光源到scene里面,scene再建立好bvh树,这个数全部是对于兔子的三角形的树,然后调用r.Render(scene)开始渲染,渲染时,是对于每一个像素建立光线使用 scene.castRay( r, 0 );来得到这个光线打到的颜色,其中在castRay里面,我们为了得到交点,使用了 Scene::intersect(ray);来得到交点,而intersect函数就是调用当前场景的BVH的成员函数getIntersection来递归找到交点,最后利用交点的信息着色,绘制图片!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值