games101 作业6 详解SAH

可参考资料:https://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies

代码分析

作业6 的代码框架在作业五的基础上进行了进一步地丰富与改进

一开始还是创建场景 添加物体以及灯光 这次的物体只有一个用objloader读取的兔子的3d模型 但是在将该模型转换为meshtriangle数据结构的时候,也为该模型中的各个三角形片元创建BVH(Bounding volume hierarchies)的加速结构 这里虽然也为场景中的各个物体也构建了BVH 但是目前场景中只有一个物体

MeshTriangle bunny(Utils::PathFromAsset("model/bunnyAssignment6/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(const std::string& filename){
.......
std::vector<Object*> ptrs;
for (auto& tri : triangles)
ptrs.push_back(&tri); bvh = new BVHAccel(ptrs);
} BVHAccel::BVHAccel(std::vector<Object*> p, int maxPrimsInNode,
SplitMethod splitMethod)
: maxPrimsInNode(std::min(255, maxPrimsInNode)), splitMethod(splitMethod),
primitives(std::move(p))
{
time_t start, stop;
time(&start);
if (primitives.empty())
return; root = recursiveBuild(primitives); time(&stop);
double diff = difftime(stop, start);
int hrs = (int)diff / 3600;
int mins = ((int)diff / 60) - (hrs * 60);
int secs = (int)diff - (hrs * 3600) - (mins * 60); printf(
"\rBVH Generation complete: \nTime Taken: %i hrs, %i mins, %i secs\n\n",
hrs, mins, secs);
}

而BVH的构建过程就是一颗树的递归构建的过程 划分BVH节点的方法 就是在最长的维度上 对所有的三角形进行排序 找出位于中间的这个三角形的位置 左边的就是左节点 右边就是右节点,划分结束的终止条件就是只有一个物体

对应课件的ppt:



pbrt图示:

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;
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);
return node;
}
else {
//用每个三角形对应的box的中点 拼成一个box 然后找出这个box最长的维度 在这个维度上进行划分
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);
} return node;
}

之后就是光线追踪castRay 求交 然后进行着色计算 和作业5类似

但是这次求交要利用我们构建的BVH加速结构进行加速 这也是我们要完成的部分

分析与解决

与BVH求交

这里主要是对构建的BVH加速结构进行一个遍历 和节点的boundingbox求交 如果没有交点就返回 如果有 则查看该节点是不是叶子节点 不是叶子节点 则继续遍历该节点的子节点 如果是叶子节点 则对其中的物体进行相交测试 找出最近的交点返回:


Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
// TODO Traverse the BVH to find intersection
Intersection hit1;
Intersection hit2;
std::array<int, 3> dirIsNeg{ 0,0,0 };
for (int i = 0; i < 3; i++)
{
if (ray.direction[i] < 0) {
dirIsNeg[i] = 1;
}
}
if (!node->bounds.IntersectP(ray, ray.direction_inv, std::move(dirIsNeg)))
{
return Intersection{};
}
if (node->left == nullptr && node->right == nullptr) {
return node->object->getIntersection(ray);
}
hit1 = getIntersection(node->left, ray);
hit2 = getIntersection(node->right, ray); return hit1.distance > hit2.distance ? hit2 : hit1;
}

boundingbox求交

boundingbox是个长方体 并且与各个面与xyz轴平行 我们只需要求出和每组平面 的tenter 与 texit 并从enter中找出最大值 exit中找出最小值 这样可以防止错误的将交点找到长方体的延展面上 同时也要考虑dir光线为负的情况 这样的话会和坐标值较小的平面先相交:

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 txmin = (pMin - ray.origin).x * invDir.x;
float txmax = (pMax - ray.origin).x * invDir.x;
float tymin = (pMin - ray.origin).y * invDir.y;
float tymax = (pMax - ray.origin).y * invDir.y;
float tzmin = (pMin - ray.origin).z * invDir.z;
float tzmax = (pMax - ray.origin).z * invDir.z;
//如果方向某个维度是负的 会和pMax对应维度上的面上先相交
if (dirIsNeg[0]) {
std::swap(txmin, txmax);
}
if (dirIsNeg[1]) {
std::swap(tymin, tymax);
}
if (dirIsNeg[2]) {
std::swap(tzmin, tzmax);
}
float tenter = std::max(std::max(txmin, tymin), tzmin);
float texit = std::min(std::min(txmax, tymax), tzmax);
return tenter < texit && texit >= 0;
}

三角形求交

和作业5中的实现类似

这里需要返回交点的属性 比如材质 法线等等:

inline Intersection Triangle::getIntersection(Ray ray)
{
Intersection inter;
//如果法线与光线异侧 说明光线是从背侧射入
if (dotProduct(ray.direction, normal) > 0)
return inter;
double u, v, t_tmp = 0;
// D E2 = S1
Vector3f pvec = crossProduct(ray.direction, e2);
// S1 . E1
double det = dotProduct(e1, pvec);
if (fabs(det) < EPSILON)
return inter; double det_inv = 1. / det;
//S
Vector3f tvec = ray.origin - v0;
u = dotProduct(tvec, pvec) * det_inv;
if (u < 0 || u > 1)
return inter;
//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; // TODO find ray triangle intersection
if (t_tmp < 0) {
return inter;
}
inter.happened = true;
inter.coords = ray(t_tmp);
inter.normal = normal;
inter.distance = t_tmp;
inter.obj = this;
inter.m = this->m; return inter;
}

SAH

SAH 全称是Surface Area Heuristic 是在构建BVH过程中 进行划分的一种方法 之前我们也提到了一种简单的划分方法 之前的方法在下图所示的情况下:



会选择图中蓝线这样的划分 最终boundingbox会有部分重叠 从而降低光线求交的效率

SAH提出了一个函数来量化地估计划分区域的代价 以将一个boundingbox划分为AB两个为例:



其中t-trav是遍历树节点的代价 这里固定为一个常数0.125 pbrt的解释是我们将光线求交的代价设置为1 遍历效率大概是求交的1/8 重要的是它们之间的相对关系 而不是数值

t-isect代表着求交的代价 具体设置为每个区域内图元的数目

pa pb代表着光线穿过子节点的概率 使用A的boundingbox面积 与 总boundingbox面积的比值来确定

具体流程概括如下:

1.首先我们和之前的划分算法一样 找出最长的维度

2.这里我们定义一定数目的bucket 在整体的boundingbox上进行划分 计算出每个物体在哪个bucket的索引

3.每个bucket都代表着一种划分方法 我们遍历计算各方法的代价 然后找出最小代价对应的bucket索引:



4.借助最小索引将物体分为两堆

代码:参考https://github.com/mmp/pbrt-v3/blob/master/src/accelerators/bvh.cpp

BVHBuildNode* BVHAccel::SAHrecursiveBuild(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() <= 2) {
node = recursiveBuild(objects);
return node;
}
else {
Bounds3 centroidBounds;
for (int i = 0; i < objects.size(); ++i)
centroidBounds =
Union(centroidBounds, objects[i]->getBounds().Centroid());
int dim = centroidBounds.maxExtent();
//计算索引
constexpr int nBuckets = 8;
BucketInfo buckets[nBuckets];
for (int i = 0; i < objects.size(); ++i) {
int b = nBuckets *
centroidBounds.Offset(
objects[i]->getBounds().Centroid())[dim];
if (b == nBuckets) b = nBuckets - 1;
buckets[b].count++;
buckets[b].bounds =
Union(buckets[b].bounds, objects[i]->getBounds());
}
//计算每个划分的代价
float cost[nBuckets - 1];
for (int i = 0; i < nBuckets - 1; ++i) {
Bounds3 b0, b1;
int count0 = 0, count1 = 0;
for (int j = 0; j <= i; ++j) {
b0 = Union(b0, buckets[j].bounds);
count0 += buckets[j].count;
}
for (int j = i + 1; j < nBuckets; ++j) {
b1 = Union(b1, buckets[j].bounds);
count1 += buckets[j].count;
}
cost[i] = 0.125f +
(count0 * b0.SurfaceArea() +
count1 * b1.SurfaceArea()) /
bounds.SurfaceArea();
}
//找出最小代价
float minCost = cost[0];
int minCostSplitBucket = 0;
for (int i = 1; i < nBuckets - 1; ++i) {
if (cost[i] < minCost) {
minCost = cost[i];
minCostSplitBucket = i;
}
}
float leafCost = objects.size();
int mid = 0;
//如果包含的物体过多 或者缩小划分代价 则进行划分
if (objects.size() > maxPrimsInNode || minCost < leafCost) {
for (int i = 0; i < objects.size(); ++i) {
int b = nBuckets *
centroidBounds.Offset(
objects[i]->getBounds().Centroid())[dim];
if (b == nBuckets) b = nBuckets - 1; // 满足条件的元素放到数组前半部分
if (b <= minCostSplitBucket) {
std::swap(objects[i], objects[mid]);
mid++;
}
}
} auto beginning = objects.begin();
auto middling = objects.begin() + mid;
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 = SAHrecursiveBuild(leftshapes);
node->right = SAHrecursiveBuild(rightshapes); node->bounds = Union(node->left->bounds, node->right->bounds);
} return node;
}

结果展示

BVH:



BVH-SAH:



games101 作业6 详解SAH的更多相关文章

  1. hadoop2.7作业提交详解之文件分片

    在前面一篇文章中(hadoop2.7之作业提交详解(上))中涉及到文件的分片. JobSubmitter.submitJobInternal方法中调用了int maps = writeSplits(j ...

  2. hadoop2.7之作业提交详解(下)

    接着作业提交详解(上)继续写:在上一篇(hadoop2.7之作业提交详解(上))中已经讲到了YARNRunner.submitJob() [WordCount.main() -> Job.wai ...

  3. hadoop2.7之作业提交详解(上)

    根据wordcount进行分析: import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; impo ...

  4. hadoop之mapreduce详解(基础篇)

    本篇文章主要从mapreduce运行作业的过程,shuffle,以及mapreduce作业失败的容错几个方面进行详解. 一.mapreduce作业运行过程 1.1.mapreduce介绍 MapRed ...

  5. hadoop之mapreduce详解(进阶篇)

    上篇文章hadoop之mapreduce详解(基础篇)我们了解了mapreduce的执行过程和shuffle过程,本篇文章主要从mapreduce的组件和输入输出方面进行阐述. 一.mapreduce ...

  6. 详解Linux进程(作业)的查看和杀死

    目录: 引入进程 进程 线程 PS命令 TOP命令 其他查看进程命令 进程的优先级 作业控制机制 kill命令 一.引入进程 1.内存划分为:用户空间和内核空间 1.在用户空间里运行的进程,就是用户进 ...

  7. day22作业详解

    1.面向对象作业1 2.作业详解 点击查看详细内容 #1. class Li(object): def func1(self): print('in func1') obj = Li() obj.fu ...

  8. day6作业详解

    1.day6题目 1,老男孩好声⾳选秀⼤赛评委在打分的时候呢, 可以进⾏输入. 假设, 老男孩有10个评委. 让10个评委进⾏打分, 要求, 分数必须⼤于5分, 小于10分. 电影投票. 程序先给出⼀ ...

  9. day4列表作业详解

    1.day4题目 day4作业 1,写代码,有如下列表,按照要求实现每一个功能 li = ["alex", "WuSir", "ritian" ...

  10. 作业详解及流程控制之for循环

    作业详解及流程控制之for循环 目录 作业详解及流程控制之for循环 一.作业详解 1.根据用户输入内容打印其权限 2.编写用户登录程序 4.猜年龄的游戏 二.流程控制之for循环 三.while循环 ...

随机推荐

  1. Flutter 借助SearchDelegate实现搜索页面,实现搜索建议、搜索结果,解决IOS拼音问题

    搜索界面使用Flutter自带的SearchDelegate组件实现,通过魔改实现如下效果: 搜素建议 搜索结果,支持刷新和加载更多 IOS中文输入拼音问题 界面预览 拷贝源码 将SearchDele ...

  2. 认真学习css3--01-全是日志没有技术内容

    由于某些原因,还是要写写前端代码. 虽然这不是我的主要责任,但也有表率的必要性. css3这个东西没有什么大难度,难点主要集中在: 1)样式比较多 2)精准定位 3)性能优化 其中第2个是日常经常面对 ...

  3. get基于报错的sql注入

    get基于报错的sql注入发现 Less1: sqli-labs第一关提示说在网页上输入id,也就是?id=1. 但这个?是什么意思,它表示index.php?也就是默认页面.然后?id=1就是把id ...

  4. 全志科技A40i开发板规格书(四核ARM Cortex-A7,主频1.2GHz)

    1.评估板简介 创龙科技TLA40i-EVM是一款基于全志科技A40i处理器设计的4核ARM Cortex-A7高性能低功耗国产评估板,每核主频高达1.2GHz,由核心板和评估底板组成. 评估板接口资 ...

  5. NXP i.MX 6ULL工业开发板规格书( ARM Cortex-A7,主频792MHz)

    1 评估板简介 创龙科技TLIMX6U-EVM是一款基于NXP i.MX 6ULL的ARM Cortex-A7高性能低功耗处理器设计的评估板,由核心板和评估底板组成.核心板经过专业的PCB Layou ...

  6. (sql语句试题练习及 参考答案解题思路+个人解题思路)

    SQL字段说明及数据 ======================================================================= 一.部门表字段描述:dp_no 部 ...

  7. JVM(Java虚拟机) 整理(一):基础理论

    JVM整体结构 本文主要说的是HotSpot虚拟机, JVM 全称是 Java Virtual Machine,中文译名:Java虚拟机 简化一下: Java字节码文件 Class文件本质上是一个以8 ...

  8. debian12 安装ch343驱动

    前言 最近心血来潮,装了一台debian12玩,安装完毕arduino后发现没有ch343驱动,倒是在 ls /lib/modules/6.1.0-13-amd64/kernel/drivers/us ...

  9. SpringBoot快速插入Mysql 1000万条数据

    导读 有时候为了验证系统瓶颈,需要往数据库表中插入大量数据,可以写sheel脚本插入,前几天为了插入100万条数据,走的sheel脚本(点我直达),插入速度简直无法直视,花了3小时,才插入了10万条, ...

  10. Java 散列表HashTable

    什么是散列表hash table和使用场景 什么是散列表 散列表(Hash table,也叫哈希表),是根据关键码值(key value)而直接进行访问的数据结构.它通过把关键码值映射到表中一个位置来 ...