在光线追踪中,我们不仅要计算多次反射折射,还要计算每个光线的每个接触点与模型的每个三角形是否相交和相交点,计算量巨大,因此学习本节“加速光线追踪”算法来加速这个过程。
本质是将空间根据轴分为多个立方体区域,先判断光线进不进改立方体,在判断里边的模型,简单有效。
将每个立方体看做3对面或者3对轴坐标,直接卡位计算光线是否进入立方体,以二维为例:
t e n t e r = m a x ( t m i n ) t e x i t = m i n ( t m a x ) } = > { t e n t e r < t e x i t t e x i t > 0 \left.\begin{matrix}t_{enter} = max(t_{min})\\t_{exit} = min(t_{max}) \end{matrix}\right\}=>\left\{\begin{matrix}t_{enter} < t_{exit}\\t_{exit} > 0 \end{matrix}\right. tenter=max(tmin)texit=min(tmax)}=>{tenter<texittexit>0
但如果场景中全都是细小模型,则aabb方法加速效果不明显,这时需要对aabb算法加速。一般有对空间划分和对象划分的加速,其中均匀空间划分(Uniform Spatial Partitions)和KD-Tree空间划分两种算法都是针对空间划分的优化,但有可能一个对象被两个包围盒都包含,因此目前不常用。
另一种以对象作为划分依据的典型算法就是BVH,用树形结构将对象分为各个部分。
划分过程:
在BVH中划分过程中,取某个轴将各个物体按坐标排序后,理论上有N-1个分法。假设是S0空间被分为了S1和S2空间,SAH理论认为:
因此我们遍历计算 以每个轴为基准划分–每个划分法(n-1个)下所有的运行时间,找出最小值对应的划分方法,作为最后的空间划分方法。
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);//S1
double det = dotProduct(e1, pvec);
if (fabs(det) < EPSILON)
return inter;
double det_inv = 1. / det;
Vector3f tvec = ray.origin - v0;//S
u = dotProduct(tvec, pvec) * det_inv;
if (u < 0 || u > 1)
return inter;
Vector3f qvec = crossProduct(tvec, e1);//S2
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.distance = t_tmp;
inter.happened = true;
inter.m = m;
inter.obj = this;
inter.normal = normal;
inter.coords = ray(t_tmp);
return inter;
}
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
const std::array<int, 3>& dirIsNeg) const
{
Vector3f dirIsNegVec = Vector3f(dirIsNeg[0],dirIsNeg[1],dirIsNeg[2]);
Vector3f One(1.0,1.0,1.0);
Vector3f t_min, t_max;
t_min = ((pMin*dirIsNegVec+pMax*(One-dirIsNegVec)) - ray.origin)*invDir;
t_max = ((pMax*dirIsNegVec+pMin*(One-dirIsNegVec)) - ray.origin)*invDir;
float t_enter,t_exit;
t_enter = fmax(fmax(t_min.x,t_min.y),t_min.z);
t_exit = fmin(fmin(t_max.x,t_max.y),t_max.z);
return (t_enter < t_exit && t_exit >= 0);
}
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
Intersection isect;
Vector3f invDir(1.0/ray.direction.x,1.0/ray.direction.y,1.0/ray.direction.z);
std::array<int, 3> dirIsNeg = {int(ray.direction.x>0),int(ray.direction.y>0),int(ray.direction.z>0)};
if(!node->bounds.IntersectP(ray,invDir,dirIsNeg))
return isect;
if(node->left == nullptr && node->right == nullptr){
return node->object->getIntersection(ray);
}
Intersection hitleft = getIntersection(node->left,ray);
Intersection hitright = getIntersection(node->right,ray);
return hitleft.distance < hitright.distance ? hitleft : hitright;
}
else {
Bounds3 centroidBounds;
for (int i = 0; i < objects.size(); ++i)
centroidBounds = Union(centroidBounds, objects[i]->getBounds().Centroid());
double time_svh = 10000.0;
int index = 0;
int axis = 0;
double S0 = bounds.SurfaceArea();
for (int i = 0; i < 3; i++)
{
switch (i) {
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;
}
for(int k = 1; k < objects.size(); k++)
{
auto beginning = objects.begin();
auto middling = objects.begin() + k;
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()));
Bounds3 bound1,bound2;
for (int i = 0; i < leftshapes.size(); ++i)
bound1 = Union(bound1, leftshapes[i]->getBounds());
for (int i = 0; i < rightshapes.size(); ++i)
bound2 = Union(bound2, rightshapes[i]->getBounds());
double S1 = bound1.SurfaceArea();
double S2 = bound2.SurfaceArea();
double time = S1/S0 * leftshapes.size() * 1.0 + S2/S0 * rightshapes.size() * 1.0;
if(time < time_svh)
{
time_svh = time;
index = k;
axis = i;
}
}
}
switch (axis) {
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() + index;
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 = recursiveBuildSAH(leftshapes);
node->right = recursiveBuildSAH(rightshapes);
node->bounds = Union(node->left->bounds, node->right->bounds);
}