Dr. Sergey Kosov
University Lecturer and Entrepreneur
bool CScene::intersect(Ray& ray) const
{
bool hit = false;
for (auto& pPrim : m_vpPrims) // iterate over all the primitives
hit |= pPrim->intersect(ray); // check intersection
return hit;
}
std::vector<std::shared_ptr<IPrim>> m_vpPrims; // All scene primitives
Need auxiliary spatial index structures to accelerate this process
Bounding Volumes
BVH
Uniform Grid
Kd-Tree
Note: Bounding Volumes are used in all presented techniques as they are needed to define each primitive to the corresponding cell of an acceleration structure
E.g. sphere or box (axis-aligned, object-aligned)
A ray may hit enclosed primitive only if it hits the bounding volume first
Avoid expensive intersection test with the object
Constructing a bounding sphere is fast
Ray-sphere intersection test is easy
No need to change when object rotates
Cannot tightly enclose the object in some cases, e.g., long and thin objects, which leads to false positives
Loop through all of the vertices and find the min and max values in each dimension separately
Use the min / max values as the bottom-left / top-right corners of the bounding box
Bounded Volume
Bounded Volume
Bounded Volume
Bounded Volume
bool intersect(Ray& ray) {
calculate tnear.x , tfar.x , tnear.y , tfar.y , tnear.z , tfar.z on 3 axes;
tnear = max(tnear.x , tnear.y , tnear.z);
tfar = min(tfar.x , tfar.y , tfar.z);
if (tnear < tfar && tnear < ray.t) {
ray.t = tnear; // report intersection at tnear
return true;
}
return false; // no intersection
}
Constructing an AABB is simple
Simply scan over all vertices and find min and max values in each dimension in O(n) time
Ray-box intersection test is fast
Need to recalculate the bounding box any time an object rotates (unless the ray is transformed into object space)
Cannot tightly enclose the object (similar to spheres)
Don’t need to recompute the box when an object rotates
Can pre-compute OBB in object space and transform OBB to world space with the object (same is true for spheres)
Singular Value Decomposition
Compute the covariance matrix and the eigenvalues and the corresponding eigenvectors of the 3x3 matrix
Using these eigenvectors as the basis of the local coordinate system of the bounding box
$$ \vec{\mu}=\frac{1}{n}\sum^{n}_{i=0}\frac{\vec{a}_i+\vec{b}_i+\vec{c}_i}{3}, $$ where \( \vec{a}_i \), \( \vec{b}_i \) and \( \vec{c}_i \) are the vertices if ith triangle, and \( n \) is the number of triangles.
the element \( c_{jk} \) is computed as:
$$ c_{jk} = \frac{1}{3n}\sum^{n}_{i=0}\left(\hat{\vec{a}}^{j}_{i}\hat{\vec{a}}^{k}_{i} + \hat{\vec{b}}^{j}_{i}\hat{\vec{b}}^{k}_{i} + \hat{\vec{c}}^{j}_{i}\hat{\vec{c}}^{k}_{i} \right), $$
where \( \hat{\vec{a}}_i=\vec{a}_i - \vec{\mu} \) and \( \hat{\vec{a}}_i=\left( \hat{a}^{1}_{i}, \hat{a}^{2}_{i}, \hat{a}^{3}_{i} \right)^\top \)
All of the eigenvectors are mutually orthogonal since \( C \) is symmetric, thus we can take them as the basis vectors for the local coordinate system of the OBB
(similar to the AABB)
Calculate the maximum \( t_{near} \) and the minimum \( t_{far} \)
The planes of the slabs are not axis aligned any more
Recall how to compute intersection between a ray and an arbitrary plane in 3D space
Bounded Volume
Bounded Volume
Ray-AABB intersection
Ray-OBB intersection
... into the OBB coordinate system and perform ray-AABB intersection test
OBB
OBB
World Coordinate System
OBB Coordinate System
Fit the object tighter than AABB
Extra cost for ray-box intersection
Finding an OBB is computationally expensive (but may be done as a pre-computation)
Finding the minimal OBB is hard (but an approximation – such as the one we just proposed - is usually good enough)
Recursively split space into halves
Splitting with half-spaces in arbitrary position
Often defined by existing polygons
where every node is defined by its place in the overall hierarchy and described by its bounding volume
Contains the pointer to the primitives, ascribed to that node
In order to implement the KD Tree we need to implement the following classes and methods:
Method to build a KD Tree using the scene primitives:
void rt::CBSPTree::build(vpPrims)
Method to traverse a ray:
bool rt::CBSPTree::intersect(ray)
rt::CBSPNode(vpPrims)
rt::CBSPNode(splitDim, splitVal, *leftNode, *rightNode)
bool rt::CBSPNode::intersect(ray, t0, t1)
where arguments t0 and t1 define ray's entering and exiting points in / out the node's bounding volume
In order to implement the KD Tree we need to implement the following classes and methods:
which defines bounding volume for every tree node
Method for checking whether a primitive belongs to the tree node or not:
bool rt::CBoundingBox::overlaps(boundingBox)
Method to split a bounding with a hyperplane into two halfes:
std::pair<lbox, rbox> rt::CBoundingBox::split(splitDim, splitVal)
rt::clip(ray, &t0, &t1)
rt::IPrim::getBoundingBox(void)=0
ptr_bspnode_t CBSPTree::build(const CBoundingBox& box, const std::vector<ptr_prim_t>& vpPrims, size_t depth)
{
// Check for stoppong criteria
if (depth > m_maxDepth || vpPrims.size() <= m_minPrimitives)
return std::make_shared<CBSPNode>(vpPrims); // Create a leaf node
// else -> prepare for creating a branch node
// First split the bounding volume into two halfes
int splitDim = MaxDim(box.getMaxPoint() - box.getMinPoint()); // Calculate split dimension as the dimension where the aabb is the widest
float splitVal = (box.getMinPoint()[splitDim] + box.getMaxPoint()[splitDim]) / 2; // Split the aabb exactly in two halfes
auto splitBoxes = box.split(splitDim, splitVal);
CBoundingBox& lBox = splitBoxes.first;
CBoundingBox& rBox = splitBoxes.second;
// Second order the primitives into new nounding boxes
std::vector<ptr_prim_t> lPrim;
std::vector<ptr_prim_t> rPrim;
for (auto pPrim : vpPrims) {
if (pPrim->getBoundingBox().overlaps(lBox))
lPrim.push_back(pPrim);
if (pPrim->getBoundingBox().overlaps(rBox))
rPrim.push_back(pPrim);
}
// Next build recursively 2 subtrees for both halfes
auto pLeft = build(lBox, lPrim, depth + 1);
auto pRight = build(rBox, rPrim, depth + 1);
// Create a branch node
return std::make_shared<CBSPNode>(splitDim, splitVal, pLeft, pRight);
}
bool CBSPNode::intersect(Ray& ray, double t0, double t1) const
{
if (isLeaf()) {
for (auto& pPrim : m_vpPrims)
pPrim->intersect(ray);
return (ray.hit && ray.t < t1 + Epsilon);
}
else {
// distnace from ray origin to the split plane of the current volume (may be negative)
double d = (m_splitVal - ray.org[m_splitDim]) / ray.dir[m_splitDim];
auto frontNode = (ray.dir[m_splitDim] < 0) ? Right() : Left();
auto backNode = (ray.dir[m_splitDim] < 0) ? Left() : Right();
if (d <= t0) {
// t0..t1 is totally behind d, only go to back side
return backNode->intersect(ray, t0, t1);
}
else if (d >= t1) {
// t0..t1 is totally in front of d, only go to front side
return frontNode->intersect(ray, t0, t1);
}
else {
// travese both children. front one first, back one last
if (frontNode->intersect(ray, t0, d))
return true;
return backNode->intersect(ray, d, t1);
}
}
By Dr. Sergey Kosov