Games101:作业6
说明
本次作业主要实现对上一次作业代码的重构以及使用BVH加速求交的交点判断和递归调用
代码框架的修改
有影响的改动就是框架中定义了两个结构体一个是光线ray,一个是交点Intersection
交点结构体
主要为相交交点定义了六个变量,
struct Intersection
{
Intersection(){
happened=false;
coords=Vector3f();
normal=Vector3f();
//the distance between ori and intersection
distance= std::numeric_limits<double>::max();
obj =nullptr;
m=nullptr;
}
//是否相交
bool happened;
//交点坐标
Vector3f coords;
//交点法线
Vector3f normal;
//交点距离摄像机的距离
double distance;
//交点的物体类型
Object* obj;
//交点的材质
Material* m;
};
光线结构体
主要为发射光线定义了3个变量
//发射起点--摄像机
Vector3f origin;
//发射方向,和发射方向各分量的倒数
Vector3f direction, direction_inv;
//传播时间 res = o+td
double t;//transportation time,
double t_min, t_max;
重构求发射光线的代码
同样是利用castRay函数计算framebuff,而这次代码中
Vector3f Scene::castRay(const Ray &ray, int depth) const
需要传入ray和depth,深度可以默认初始化为0。ray则通过x,y来求解,通过传入发射起点和发射方向即可
Vector3f dir = Vector3f(x,y,-1);
dir = normalize(dir);
Ray r(eye_pos,dir);
framebuffer[m++] = scene.castRay(r,0);
重构求三角形与发射光线交点的代码
框架中已经给出了t_tmp,u,v并且已经判断了不存在的情况,需要做的工作就是利用参数为Intersection变量赋值
//if inter true
// TODO find ray triangle intersection
inter.happened = true;
//o+td -> o : ||td||
double distance = dotProduct(t_tmp*ray.direction,t_tmp*ray.direction);
inter.distance = distance;
Vector3f coords = (1-u-v)*v0 + u*v1 + v*v2; // or ray.origin+t_tmp*ray.direction;
inter.coords = coords;
inter.normal = this->normal;
inter.m = this->m;
inter.obj = this;
求解发射光线是否与AABB有交点
课上有说如果发射光线与包围盒有交点,那么说明tmin<tmax && tmax >= 0,所以需要通过发射光线与平面的交点来判断
对于平行于坐标轴的包围平面
而我们的pMin和pMax表示三个坐标方向的最大最小值。
Vector3f tmin_v = (pMin-ray.origin)*invDir;
Vector3f tmax_v = (pMax-ray.origin)*invDir;
还需要注意的是,如果发射方向在x,y,z某个值为负,说明这个方向的分量是随着时间减小的,那么就需要调换min 和 max在这个方向上的分量值
//if x,y,z someone < 0 means the ray dir is neg in this axis, and max min need swap
if(dirIsNeg[0] == 0)
{
//swap x
std::swap(tmin_v.x,tmax_v.x);
}
if(dirIsNeg[1] == 0)
{
//swap y
std::swap(tmin_v.y,tmax_v.y);
}
if(dirIsNeg[2] == 0)
{
//swap z
std::swap(tmin_v.z,tmax_v.z);
}
最后就是比较这三个对应的分量,求tmin的三者最大值,tmax的三者最小值。
//tmin is max (minx,miny,minz)
double tmin = std::max(tmin_v.x,std::max(tmin_v.y,tmin_v.z));
//tmax is min (maxx,maxy,maxz)
double tmax = std::min(tmax_v.x,std::min(tmax_v.y,tmax_v.z));
if(tmin < tmax && tmax >= 0) return true;
else return false;
BVH树结构加速求交
BVH是一个二叉树结构,而只在其叶子节点中存储了可能有交点的物体信息,非叶子节点存储的都是下一个左右子节点。
框架代码中传入了BVH的头节点,和发射光线,需要返回一个交点
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
BVHBuildNode节点的结构为
struct BVHBuildNode {
//AABB
Bounds3 bounds;
//划分的左节点
BVHBuildNode *left;
//划分的右节点
BVHBuildNode *right;
//包围盒中的物体
Object* object;
public:
int splitAxis=0, firstPrimOffset=0, nPrimitives=0;
// BVHBuildNode Public Methods
BVHBuildNode(){
bounds = Bounds3();
left = nullptr;right = nullptr;
object = nullptr;
}
};
这个是一个递归函数
- 考虑终止条件
当发射光线与AABB没有交点是即tmin tmax不满足时,直接返回intersection的默认值
当该包围盒中有物体时,表明该节点是叶子节点,返回光线与该物体的交点 - 递归
对左节点右节点递归 - 单层逻辑
对于求得的左右节点的两个交点,应当返回的是两个中距离观测点更近的一点(z-buffer)
Intersection inters;
std::array<int, 3> dirIsNeg = {int(1.0/ray.direction.x>0),int(1.0/ray.direction.y>0),int(1.0/ray.direction.z>0)};
//no intersection with AABB
if(!node->bounds.IntersectP(ray,ray.direction_inv,dirIsNeg))
{
return inters;
}
//calculate intersection with triangle
if(node->object)
{
return node->object->getIntersection(ray);
}
Intersection left_inter = getIntersection(node->left,ray);
Intersection right_inter = getIntersection(node->right,ray);
//the lower distance return
return left_inter.distance < right_inter.distance ? left_inter : right_inter;
SAH
框架中的BVH的构建方式
Bounds3 centroidBounds;
for (int i = 0; i < objects.size(); ++i)
//Centroid() 获取包围盒的中间位置
centroidBounds =
Union(centroidBounds, objects[i]->getBounds().Centroid());
//maxExtent() x,y,z哪个方向比较长,选择该方向进行分割
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);
SAH划分评估方式
目的是利用SAH对时间复杂度的优化,考虑不进行划分的情况,那么需要对每个物体进行求交\(Time_{cost} = \sum_{i=1}^n(Time_{oneobj})\)
而SAH利用概率的方法加速了这一求解过程,一个父节点被分成了左右两个子节点,而这两个子节点表示的包围盒有一定的面积,如果发射光线与父节点的包围盒有交点,那么发射光线能够与这两个子节点产生交点的概率就是面积之比,那么对于当前的时间复杂度就为一个期望值\(Time_{cost} = Time_{costforpa} + \frac{S_A}{S_C}Num_{objInA}(Time_{oneobj}) + \frac{S_B}{S_C}Num_{objInB}(Time_{oneobj})\)
SAH评估办法将使得可以选择一个较优划分方式来实现时间复杂度的最低。
初步做法就是,对每种划分方式利用SAH计算时间消耗,然后选择时间消耗最少的那种方式进行划分
还有一种优化方式就是对于当前的“最长轴”,均分成B份,然后可以选择不同的分割线,在这个轴上表现为划分为2份,计算这种选取方式的时间消耗,最后选择消耗最小的一种划分方式
每次选择一条虚线,计算以该条虚线分割的时间消耗,最后选择消耗最小的虚线构建左右子节点。
假设第5条虚线的分割导致时间消耗最小,那么b0,b1,b2,b3中的物体可以构成求解左节点的数组,剩余区域中的物体构成求解右节点的数组
BVHBuildNode* BVHAccel::recursiveSAHBuild(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 = recursiveSAHBuild(std::vector{objects[0]});
node->right = recursiveSAHBuild(std::vector{objects[1]});
node->bounds = Union(node->left->bounds, node->right->bounds);
return node;
}
//前面的1个节点和2个节点的逻辑适合BVH一样的
//假定划分了12个桶,然后ctrav = 0.125f,cisect = 1.0f;
else {
//if(objects.size() < 12) return recursiveBuild(objects);
Bounds3 centroidBounds;
//claim vector : cost and bounds
//save the cost choose different way
std::vector<double> cost(11);
//save the buckets we make Buckets是一个结构体 包含了 该划分区域的物体个数,该区域的边界以及物体
std::vector<Buckets> buckets(12);
for (int i = 0; i < objects.size(); ++i)
centroidBounds =
Union(centroidBounds, objects[i]->getBounds().Centroid());
int dim = centroidBounds.maxExtent();
//check the axis which use
//and we need calculate the obj belong which bucket
//we division pMin->pMax to 20 buckets, so we can calculate the obj's p where use
//p/(pMax - pMin) the percent in 20 which bucket = 20 * p/(pMax-pMin)
int b;
for(int i=0;i<objects.size();i++)
{
//which bucket
//choose which axis
switch(dim)
{
//x
case 0: {
b = 12*centroidBounds.Offset(objects[i]->getBounds().Centroid()).x;
break;
}
//y
case 1: {
b = 12*centroidBounds.Offset(objects[i]->getBounds().Centroid()).y;
break;
}
//z
case 2: {
b = 12*centroidBounds.Offset(objects[i]->getBounds().Centroid()).z;
break;
}
}
//if b >= 12 b = 11
if(b == 12) b = 11;
buckets[b].count++;
buckets[b].bounds = Union(buckets[b].bounds,objects[i]->getBounds());
buckets[b].objects.push_back(objects[i]);
}
//we need calculate the time cost
// the constant cost
float ctrav = 0.125f;
float cisect = 1.0f;
double SC = centroidBounds.SurfaceArea();
//the expression is c = ctrave + SA/SC*NUMA*cisect + SB/SC*NUMB*cisect
int countA = 0;
Bounds3 boundA;
for(int i = 0;i<11;i++)
{
int countB = 0;
Bounds3 boundB;
countA += buckets[i].count;
boundA = Union(boundA,buckets[i].bounds);
for(int j = i+1;j<12;j++)
{
countB += buckets[j].count;
boundB = Union(boundB,buckets[j].bounds);
}
//double SC = centroidBounds.SurfaceArea();
double timeA = (boundA.SurfaceArea()/SC) * countA * cisect;
double timeB = (boundB.SurfaceArea()/SC) * countB * cisect;
cost[i] = ctrav + timeA + timeB;
//std::cout << cost[i] << std::endl;
}
//get the min cost and the division way(i+1)
double mincost = cost[0];
int divisionWay = 0;
for(int i = 1;i<11;i++)
{
if(cost[i] < mincost)
{
mincost = cost[i];
divisionWay = i;
}
}
//create node;
int leftnum = 0;
std::vector<Object*> leftnode;
std::vector<Object*> rightnode;
//构建左右节点
for(int i = 0;i<=divisionWay;i++)
{
leftnode.insert(leftnode.end(),buckets[i].objects.begin(),buckets[i].objects.end());
}
for(int i = divisionWay+1;i<12;i++)
{
rightnode.insert(rightnode.end(),buckets[i].objects.begin(),buckets[i].objects.end());
}
// auto leftnode = std::vector<Object*>(objects.begin(),objects.begin()+leftnum-buckets[divisionWay].count+1);
// auto rightnode = std::vector<Object*>(objects.begin()+leftnum-buckets[divisionWay].count+1,objects.end());
assert(objects.size() == (leftnode.size() + rightnode.size()));
node->left = recursiveSAHBuild(leftnode);
node->right = recursiveSAHBuild(rightnode);
node->bounds = Union(node->left->bounds, node->right->bounds);
}
return node;
}
SAH中构建左右节点
主要是最开始我做错了(即上面最后的两行注释),错误其实很明显。本来左右节点的构建也就是递归需要传入的物体数组应该是由buckets划分好了的,而我最开始直接使用Object然后利用分割点对应的物体下标来划分,导致了错误。
所以我在Buckets结构体中加入了一个记录当前木桶包含的物体数组,最后对于左右节点数组的更新直接使用insert将前i个buckets中的物体数组接到左节点需要的数组后面就可以了。
下面是Buckets结构体
struct Buckets
{
//the bounds belong this bucket
Bounds3 bounds;
//the number of bounds which this bucket have
int count;
std::vector<Object*> objects;
};
Games101:作业6的更多相关文章
- GAMES101作业2
作业任务: 填写并调用函数 rasterize_triangle(const Triangle& t). 即实现光栅化 该函数的内部工作流程如下: 创建三角形的 2 维 bounding bo ...
- 【UE4】GAMES101 图形学作业2:光栅化和深度缓存
总览 在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣.所以这一次我们继续推进一步--在屏幕上画出一个实心三角形,换言之,栅格化一个三角形.上一次作业中,在视口变化之后,我 ...
- GAMES101课程 作业6 源代码概览
GAMES101课程 作业6 源代码概览 Written by PiscesAlpaca(双鱼座羊驼) 一.概述 本篇将从main函数为出发点,按照各cpp文件中函数的调用顺序和层级嵌套关系,简单分析 ...
- 【UE4】GAMES101 图形学作业5:光线与物体相交(球、三角面)
总览 在这部分的课程中,我们将专注于使用光线追踪来渲染图像.在光线追踪中最重要的操作之一就是找到光线与物体的交点.一旦找到光线与物体的交点,就可以执行着色并返回像素颜色. 在这次作业中,我们要实现两个 ...
- 【UE4】GAMES101 图形学作业4:贝塞尔曲线
总览 Bézier 曲线是一种用于计算机图形学的参数曲线. 在本次作业中,你需要实现de Casteljau 算法来绘制由4 个控制点表示的Bézier 曲线(当你正确实现该算法时,你可以支持绘制由更 ...
- 【UE4】GAMES101 图形学作业3:Blinn-Phong 模型与着色
总览 在这次编程任务中,我们会进一步模拟现代图形技术.我们在代码中添加了Object Loader(用于加载三维模型), Vertex Shader 与Fragment Shader,并且支持了纹理映 ...
- 【UE4】GAMES101 图形学作业1:mvp 模型、视图、投影变换
总览 到目前为止,我们已经学习了如何使用矩阵变换来排列二维或三维空间中的对象.所以现在是时候通过实现一些简单的变换矩阵来获得一些实际经验了.在接下来的三次作业中,我们将要求你去模拟一个基于CPU 的光 ...
- 【UE4】GAMES101 图形学作业0:矩阵初识
作业描述 给定一个点P=(2,1), 将该点绕原点先逆时针旋转45◦,再平移(1,2), 计算出变换后点的坐标(要求用齐次坐标进行计算). UE4 知识点 主要矩阵 FMatrix FBasisVec ...
- python10作业思路及源码:类Fabric主机管理程序开发(仅供参考)
类Fabric主机管理程序开发 一,作业要求 1, 运行程序列出主机组或者主机列表(已完成) 2,选择指定主机或主机组(已完成) 3,选择主机或主机组传送文件(上传/下载)(已完成) 4,充分使用多线 ...
- SQLServer2005创建定时作业任务
SQLServer定时作业任务:即数据库自动按照定时执行的作业任务,具有周期性不需要人工干预的特点 创建步骤:(使用最高权限的账户登录--sa) 一.启动SQL Server代理(SQL Server ...
随机推荐
- Redhat7更改网易yum源
说明 之前写了一篇关于Redhat6更换Yum源的文章,时隔已久很多包都变了,正好最近搭建环境需要用到Redhat7.3所以就再记录一下如何更换为国内最新最常用的yum源. 操作步骤 1.卸载系统自带 ...
- Spring Boot学生信息管理系统项目实战-2.字典管理和模板管理
1.获取源码 源码是捐赠方式获取,详细请QQ联系我 :) 2.实现效果 3.项目源码 只挑重点讲,详细请看源码. 3.1 字典管理 字典管理这里分为字典的编码和名称和字典数据的增删改查. 前端页面: ...
- Java实现文件下载断点续传(一)
参考文章:https://www.ibm.com/developerworks/cn/java/joy-down/ 1.原理介绍 想象一下我们下载一个10G的文件,当下载到9.99G的时候断网了... ...
- Mac技巧之苹果电脑上将一个软件进程的 CPU 占用率限制在指定范围内:cputhrottle
苹果电脑 Mac OS X 系统上,我们可以用 cputhrottle 这个免费工具,配合活动监视器和终端,把一个软件进程的 CPU 占用率限制在指定值(比如 20%)以内,以防止应为它 " ...
- CDN 加速原理
=> CDN 加速原理 HTTP 请求流程说明: 用户在浏览器输入要访问的网站域名,向本地 DNS 发起域名解析请求. 域名解析的请求被发往网站授权 DNS 服务器. 网站 DNS 服务器解析发 ...
- win32-StretchDIBits - PrintDlg
使用StretchDIBits将位图数据传输到printer的dc中 #include <Windows.h> #include <algorithm> int main() ...
- MySQL Boolean类型的坑
MySQL中,Boolean只是 tinyint(1) 的别名,也就是说,MySQL中并没有真正的bool类型. 而SQLAlchemy生成SQL的时候并没有检测到 这一点,这就导致一个问题,当使用 ...
- python内置模块argparse的使用
官网文档 https://docs.python.org/3/howto/argparse.html # 简易教程 https://docs.python.org/3/library/argparse ...
- Advanced .Net Debugging 2:CLR基础
一.简介 这是2024新年后我的第一篇文章,也是我的<Advanced .Net Debugging>这个系列的第二篇文章.这篇文章告诉我们为了进行有效的程序调试,我们需要掌握哪些知识.言 ...
- DataGear 制作支持表单交互和多图表联动的数据可视化看板
对于数据可视化,有时需要根据用户输入的查询条件展示限定范围的数据图表,DataGear的看板表单功能可以快速方便地实现此类需求. 下面的看板示例,包含一个柱状图.一个饼图和一个地图,用户可以通过看板表 ...