闫令琪:Games101 现代计算机图形学-光线追踪(二):BVH算法及应用 & 作业Assignment06解析

光线追踪的整体框架更多地介绍在上篇文章,本次作业重点在于实现BVH算法,该算法在整个光线追踪框架中的作用如上篇文章中3.1小节中描述。对于包含多物体的复杂场景,快速找到光线与物体的交点是十分重要的。
闫老师的课上花了大半节课介绍了一些历史中存在的算法,什么KD-Tree等等,但是这些都用的很少了,主要是开阔眼界,引导思考吧,所以最后直接关注课上重点讲的BVH就好了。

1 BVH算法思路简述

1.1 AABB包围盒

对于本次作业的答案,明显大部分的光线都不会与兔子有交点,对于不会相交的光线,如果也要与兔子上所有的三角面进行求交判断,运算量=像素量*三角面数量。但是如果先把兔子框起来,判断是否与框有交点,在进而判断是否有三角面有交点,则能明显把运算量降下来。
在这里插入图片描述

在这里插入图片描述
我们把这种三个面分别平行于xy面、xz面、yz面的包围盒称为Axis-Aligned Bounding Box (AABB)轴对⻬包围盒

1.2 与包围盒相交的判定条件

在这里插入图片描述
首先计算光线和包围盒的公式非常简单,如上面PPT所示。分别求出光线在x、y、z三个方向的进出入时间,最后求得进出入包围盒的时间,

则光线和包围盒有交点的判据为:
①t_enter <= t_exit
②t_exit >=0

!!注意
其中第一个判据,一定要是<=,因为如果物体恰好是一个平面形状,平行于某个面,则这个物体的包围盒也会是一个平面,光线出入的时间都会是0

1.3 算法思路简述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
总结如下:

  1. 寻找物体的包围盒
  2. 判断光线是否与包围盒相交
  3. 如果相交,则对最先相交的物体进一步划分,继续判断与划分的两个包围盒哪个相交
  4. 重复上一过程,直到包围盒只有一个三角面,是二叉树上的叶子节点
  5. 计算光线与这个叶子节点里三角面的交点,并返回

2 BVH实现过程

场景包含多个物体,可以以物体为最小单元进行一次BVH划分和光线碰撞检测;
物体包含多个三角面,可以以三角面为最小单元进行一次BVH划分和光线碰撞检测。
如果有更多的层级,可以每一个层级都应用一次。

所以对BVH进行抽象,单独做成一个类,哪个层级需要调用,把BVH类当成这个层级类的一个属性即可。

2.1 BVH类 — 划分过程

这是一个二叉树的划分过程,首先定义树节点的结构体。

struct BVHBuildNode 
{
	包围盒 bound;
	左子节点 left;
	右子节点 right;
	包含的objects;
}

然后就是树的构建过程,直接伪代码表示如下:

BVHBuildNode* BVHAccel::recursiveBuild(std::vector<Object*> objects)
{
	BVHBuildNode* node = new BVHBuildNode();//用来返回的root节点
	获取objects的bound;
	if(objects.size == 1)
	{//初始化这个根节点
		node.objects = objects;
		node.left = node.right = bullptr;
		node.bound = bound;
		return node;
	}
	else if (objects.size == 2){
		node.left = recursiveBuild(objects[0]);
		node.right = recursiveBuild(objects[1]);
		.....//初始化这个节点
		return node;
	}
	else{
		获取bound上最长的维度dim;
		把objects里面的元素按照dim从小到大进行排序;
		把objects按照排序分成两部分objects_left , objects_right;
		node.left = recursiveBuild(objects_left);
		node.right = recursiveBuild(objects_right);
		.....//初始化这个节点
		return node;
	}

这里面划分的关键在于怎么对objects进行划分,作业框架里提供的思路是从维度最长的那个尺度进行一分为二,每次从一个维度上切一刀。

2.2 BVH类 — 碰撞检测

BVH算法除了建立一个二叉树,应用的时候其实是一个查找的过程。所以还需要给这个类添加一个检查光线与二叉树的碰撞函数。因为BVH的划分是一个递归过程,其实在查找中也同样是一个递归过程。

伪代码如下:

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
	//如果光线与node的bound不相交,则返回
	if(node->bound->Intersection(ray相信输入) == false)
		return ;
	//如果是子节点,直接计算子节点里面的碰撞检测
	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;
}

3 BVH的具体代码应用

光线查找二叉树的过程其实就是先判断光线与bound的碰撞检测,如果碰撞,在继续检测子节点的bound的相交,持续过程……一直到跟三角面的相交。所以这个过程其实是分别调用bound类的碰撞检测函数和objects的光线相交函数。
scene这个层级会建立一个它bvh,然后查找过程也会在他这个层级进行查到,如果到了子节点,进行下一个层级,重复该过程,直到找到三角面。
所以这个光线求交函数,在triangle类和其他类中是不一样的。

在这里插入图片描述

4 作业代码及结果

4.1 代码

Bounds3.hpp:

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 tmin_x = (pMin.x - ray.origin.x) * invDir.x;
    float tmin_y = (pMin.y - ray.origin.y) * invDir.y;
    float tmin_z = (pMin.z - ray.origin.z) * invDir.z;

    float tmax_x = (pMax.x - ray.origin.x) * invDir.x;
    float tmax_y = (pMax.y - ray.origin.y) * invDir.y;
    float tmax_z = (pMax.z - ray.origin.z) * invDir.z;

    if (dirIsNeg[0] <= 0)  std::swap(tmin_x, tmax_x);
    if (dirIsNeg[1] <= 0)  std::swap(tmin_y, tmax_y);
    if (dirIsNeg[2] <= 0)  std::swap(tmin_z, tmax_z);

    float tmin = std::max(tmin_x, std::max(tmin_y, tmin_z));
    float tmax = std::min(tmax_x, std::min(tmax_y, tmax_z));

    //这里是个大坑!必须改为<=,因为有很多bound可能是z维度是0,此时tmin=tmax
    if (tmin <= tmax && tmax>0)       
        return true;
    else
        return false;
}

注意上面的判断条件一定要是<=!!!

BVH.cpp:

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
    // TODO Traverse the BVH to find intersection
    Intersection inter;
    std::array<int, 3> dirIsNeg = { int(ray.direction.x > 0),int(ray.direction.y > 0), int(ray.direction.z > 0) };
    //如果光线跟bound不相交,终止
    if (node->bounds.IntersectP(ray, ray.direction_inv, dirIsNeg) == false) {
        return inter;
    }
    //如果相交,并且是叶子节点,则调用triangle的相交函数
    if (node->left == nullptr && node->right == nullptr) {
        return node->object->getIntersection(ray);
    }
    //如果是父节点,则递归比较
    Intersection hit1;
    Intersection hit2;
    hit1 = getIntersection(node->left, ray);
    hit2 = getIntersection(node->right, ray);
    return (hit1.distance < hit2.distance) ? hit1 : hit2;
}

不同类及其函数的相互调用关系整理如上一小节图所示。没有那张图总感觉理不清这些类之间的关系。

4.2 作业结果

作业结果如图所示:
在这里插入图片描述
作业06中其实只有一个物体,BVH的完整应用过程在作业07中更明显。

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值