GAMES101 HW6
这里回顾GAMES101 HW6,这次作业的内容是加速结构。
课程主页:
课程作业:
课程视频:
参考资料:
Render
微调之前的代码即可:
for (uint32_t j = 0; j < scene.height; ++j) {
for (uint32_t i = 0; i < scene.width; ++i) {
// generate primary ray direction
float x = (2 * (i + 0.5) / (float)scene.width - 1) *
imageAspectRatio * scale;
float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;
// TODO: Find the x and y positions of the current pixel to get the
Vector3f dir = Vector3f(x, y, -1); // Don't forget to normalize this direction!
dir = normalize(dir);
// change
Ray ray(eye_pos, dir);
framebuffer[m++] = scene.castRay(ray, 0);
// Don't forget to normalize this direction!
}
UpdateProgress(j / (float)scene.height);
}
getIntersection
也是沿用之前的代码,注意这里需要传递物体的信息:
inline Intersection Triangle::getIntersection(Ray ray)
{
Intersection inter;
if (dotProduct(ray.direction, normal) > 0)
return inter;
double u, v, t_tmp = 0;
// 对应s1
Vector3f pvec = crossProduct(ray.direction, e2);
// 对应a
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.origin + t_tmp * ray.direction;
// 交点所在平面法线
inter.normal = normal;
// 距离
inter.distance = t_tmp;
// 交点对应的物体(三角形)
inter.obj = this;
// 材质
inter.m = m;
return inter;
}
IntersectP
这里首先回顾判断物体的BVH求交的过程,关键思想为:
- 光线只有在进入所有成对的平板时才会进入盒子;
- 只要光线离开任何一对平板,光线就会离开盒子;
- 所以对于每一对平板,计算tmin和tmax(负数也ok)
- 对于3D box,$t_{\mathrm{enter}} = \max{t_{\min}} $,$t_{\mathrm{exit}} = \min{t_{\max}}$
- 如果$t_{\mathrm{enter}} < t_{\mathrm{exit}}$,我们知道光线在盒子里停留了一段时间(所以必须相交!);
- 然而,射线不是直线;
- 应该检查$t$对应的实际情形是否正确;
- 如果$t_{\mathrm{exit}}$怎么办?
- 盒子在光线“后面”——没有交点!
- 如果$t_{\mathrm{exit}}\ge 0$并且$t_{\mathrm{enter}}<0$怎么办?
- 光线的起点在盒子内——有交点!
- 综上所述,ray和AABB当且仅当:
- $t_{\mathrm{enter}} < t_{\mathrm{exit}}$并且$t_{\mathrm{exit}}\ge 0$;
所以首先是计算每个方向的进入和离开时间,利用轴对齐情形的特殊计算公式:
注意该公式默认了法线的各个分量为正,对于负数情形,需要交换离开和进入的时间:
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
Vector3f origin = ray.origin;
Vector3f t_enter = (pMin - origin) * invDir;
Vector3f t_exit = (pMax - origin) * invDir;
// 根据方向调整enter和exit
if (dirIsNeg[0]) {
float d = t_enter.x;
t_enter.x = t_exit.x;
t_exit.x = d;
}
if (dirIsNeg[1]) {
float d = t_enter.y;
t_enter.y = t_exit.y;
t_exit.y = d;
}
if (dirIsNeg[2]) {
float d = t_enter.z;
t_enter.z = t_exit.z;
t_exit.z = d;
}
float t_min = std::max(t_enter.x, std::max(t_enter.y, t_enter.z));
float t_max = std::min(t_exit.x, std::min(t_exit.x, t_exit.z));
return (t_min < t_max && t_max >= 0);
}
getIntersection
实现如下算法即可:
Intersect(Ray ray, BVH node) {
if (ray misses node.bbox)
return;
if (node is a leaf node)
test intersection with all objs;
return closest intersection;
hit1 = Intersect(ray, node.child1);
hit2 = Intersect(ray, node.child2);
return the closer of hit1, hit2;
}
代码如下:
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
// TODO Traverse the BVH to find intersection
Intersection isect;
Vector3f invDir = ray.direction_inv;
std::array<int, 3> dirIsNeg {invDir.x < 0, invDir.y < 0, invDir.z < 0};
// 相交
if (node->bounds.IntersectP(ray, invDir, dirIsNeg)) {
if (node == NULL) {
return isect;
}
if (node->left == NULL && node->right == NULL) {
isect = node->object->getIntersection(ray);
return isect;
}
Intersection isect_left = getIntersection(node->left, ray);
Intersection isect_right = getIntersection(node->right, ray);
if (isect_left.distance < isect_right.distance) {
isect = isect_left;
} else {
isect = isect_right;
}
}
return isect;
}
SVH
SVH可以先参考博客),具体代码如下:
BVHBuildNode* BVHAccel::recursiveBuildSVH(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 = recursiveBuildSVH(std::vector{objects[0]});
node->right = recursiveBuildSVH(std::vector{objects[1]});
node->bounds = Union(node->left->bounds, node->right->bounds);
return node;
}
else {
// 原则
// 找到最宽松的轴进行划分
Bounds3 centroidBounds;
Bounds3 maxBounds;
for (int i = 0; i < objects.size(); ++i) {
centroidBounds = Union(centroidBounds, objects[i]->getBounds().Centroid());
maxBounds = Union(maxBounds, objects[i]->getBounds());
}
float total_size = maxBounds.SurfaceArea();
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;
}
// 策略
float min_cos = INFINITY;
// 计算面积
int n = objects.size();
// 从左往右前i个
std::vector<float> left_size(n + 1, 0);
// 从右往左前i个
std::vector<float> right_size(n + 1, 0);
std::vector<float> score(n + 1, 0);
Bounds3 left_bound;
Bounds3 right_bound;
for (int i = 1; i <= n; i++) {
left_bound = Union(left_bound, objects[i - 1]->getBounds());
left_size[i] = left_bound.SurfaceArea() / total_size;
}
for (int i = 1; i <= n; i++) {
right_bound = Union(right_bound, objects[n - i]->getBounds());
right_size[i] = right_bound.SurfaceArea() / total_size;
}
// 计算总分
for (int i = 0; i <= n; i++) {
score[i] = i * left_size[i] + (n - i) * right_size[n - i];
}
// 找到分数最小的位置
float s = INFINITY;
int index = -1;
for (int i = 0; i <= n; i++) {
if (score[i] < s) {
s = score[i];
index = i;
}
}
// 保证划分后都有元素
if (index == n) {
index--;
} else if (index == 0) {
index++;
}
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 = recursiveBuildSVH(leftshapes);
node->right = recursiveBuildSVH(rightshapes);
node->bounds = Union(node->left->bounds, node->right->bounds);
}
return node;
}
示例
几个注意点
- 第一个注意点是源代码注释中
dirIsNeg=[int(x>0),int(y>0),int(z>0)]
定义错误; - 第二个注意点是如果在getIntersection中讨论hit1和hit2是否为空,则会出现问题;
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Doraemonzzz!
评论
ValineLivere