作业来自官网
文章目录
总览
在之前的编程练习中,我们实现了基础的光线追踪算法,具体而言是光线传输、光线与三角形求交。我们采用了这样的方法寻找光线与场景的交点:遍历场景中的所有物体,判断光线是否与它相交。在场景中的物体数量不大时,该做法可以取得良好的结果,但当物体数量增多、模型变得更加复杂,该做法将会变得非常低效。因此,我们需要加速结构来加速求交过程。在本次练习中,我们重点关注物体划分算法 Bounding Volume Hierarchy (BVH)。本练习要求你实现 Ray-Bounding Volume 求交与 BVH 查找。
任务
- Render() in Renderer.cpp: 将你的光线生成过程粘贴到此处,并且按照新框架更新相应调用的格式。
- Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,并且按照新框架更新相应相交信息的格式。
在本次编程练习中,你需要实现以下函数:
- IntersectP(const Ray& ray, const Vector3f& invDir,const std::array<int, 3>& dirIsNeg) in the Bounds3.hpp: 这个函数的作用是判断包围盒 BoundingBox 与光线是否相交,你需要按照课程介绍的算法实现求交过程。
- getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: 建立 BVH 之后,我们可以用它加速求交过程。该过程递归进行,你将在其中调用你实现的 Bounds3::IntersectP.
解答
Render() in Renderer.cpp
- 相比于上次观察点(eye_pos)变了。
- castray的调用方式有变化,是调用scene对象的castray方法,且参数是Ray。
void Renderer::Render(const Scene& scene)
{
std::vector<Vector3f> framebuffer(scene.width * scene.height);
float scale = tan(deg2rad(scene.fov * 0.5));
float imageAspectRatio = scene.width / (float)scene.height;
//相比于上次观察点变了
Vector3f eye_pos(-1, 5, 10);
int m = 0;
for (uint32_t j = 0; j < scene.height; ++j) {
for (uint32_t i = 0; i < scene.width; ++i) {
float x = (2 * (i + 0.5) / (float)scene.width - 1) *
imageAspectRatio * scale;
float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale;
Vector3f dir = Vector3f(x, y, -1);
dir=normalize(dir);
//相比于上次不是直接调用castray了,是先定义光线ray,然后在传到scene的castray方法中。
Ray ray(eye_pos,dir);
framebuffer[m++]=scene.castRay(ray,0);
}
UpdateProgress(j / (float)scene.height);
}
UpdateProgress(1.f);
// save framebuffer to file
FILE* fp = fopen("binary.ppm", "wb");
(void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
for (auto i = 0; i < scene.height * scene.width; ++i) {
static unsigned char color[3];
color[0] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].x));
color[1] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].y));
color[2] = (unsigned char)(255 * clamp(0, 1, framebuffer[i].z));
fwrite(color, 1, 3, fp);
}
fclose(fp);
}
Triangle::getIntersection in Triangle.hpp
前面的代码框架已经给出了,在此只要对Intersection类的对象赋值即可。
- 坐标、距离(t)、是否有交点是函数内算出来的。
- 材质、法线和obj指针都是这个三角形内的成员属性。
inline Intersection Triangle::getIntersection(Ray ray)
{
Intersection inter;
//说明光线不是射入的
if (dotProduct(ray.direction, normal) > 0)
return inter;
double u, v, t_tmp = 0;
//pvec相当于s1
Vector3f pvec = crossProduct(ray.direction, e2);
double det = dotProduct(e1, pvec);
if (fabs(det) < EPSILON)
return inter;
double det_inv = 1. / det;
//tvec相当于s1
Vector3f tvec = ray.origin - v0;
u = dotProduct(tvec, pvec) * det_inv;
if (u < 0 || u > 1)
return inter;
//qvec相当于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;
if (t_tmp<=0)
return inter;
//填充交点坐标,t,是否相交等交点的基本值
inter.happened=true;
//Ray中重载了(),ray()t_tmp=ray.origin+ray.direction*t_tmp
inter.coords=Vector3f(ray(t_tmp));
inter.distance=t_tmp;
//交点是在这个三角形上,所以把这个三角形的材质、法线和指针传给交点。
inter.m=m;
inter.normal=normal;
inter.obj=this;
return inter;
}
IntersectP in the Bounds3.hpp
主要用如下公式来计算 t e n t e r t_{enter} tenter和 t e x i t t_{exit} texit:
- 注意dirIsNeg存储的光线方向,如果是方向是负数的话,说明较大的那个面进,较小的那个面出,所以要调换一下位置。
- 乘倒数比除法快。
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
const std::array<int, 3>& dirIsNeg) const
{
float tx_enter=invDir.x*(pMin.x-ray.origin.x);
float tx_exit=invDir.x*(pMax.x-ray.origin.x);
if(dirIsNeg[0])
std::swap(tx_enter,tx_exit);
float ty_enter=invDir.y*(pMin.y-ray.origin.y);
float ty_exit=invDir.y*(pMax.y-ray.origin.y);
if(dirIsNeg[1])
std::swap(ty_enter,ty_exit);
float tz_enter=invDir.z*(pMin.z-ray.origin.z);
float tz_exit=invDir.z*(pMax.z-ray.origin.z);
if(dirIsNeg[2])
std::swap(tz_enter,tz_exit);
return tx_exit>=0&&tx_enter<=tx_exit&&ty_exit>=0&&ty_enter<=ty_exit&&tz_exit>=0&&tz_enter<=tz_exit;
}
getIntersection in BVH.cpp
使用BVH结构求ray打到的离光线原点最近的交点。
- 递归条件为叶子节点,所以只要左右孩子指针为空就可以直接返回。
- 最先打到的点就是要计算的点,即t最小的点。
- 按住ctrl看看BVHBuildNode的定义会更好的理解代码。
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
//如果是叶子,直接返回。
if (node->left==nullptr&&node->right==nullptr)
{
return node->object->getIntersection(ray);
}
Intersection inter;
//记录ray的方向
std::array<int,3UL> dirIsNeg;
dirIsNeg[0]=ray.direction.x<0;
dirIsNeg[1]=ray.direction.y<0;
dirIsNeg[2]=ray.direction.z<0;
//分别看看和左右包围盒有无交点
if (node->left->bounds.IntersectP(ray,ray.direction_inv,dirIsNeg))
{
inter = getIntersection(node->left,ray);
}
//如果左右都有值,就选择离的近的那个。
if (node->right->bounds.IntersectP(ray,ray.direction_inv,dirIsNeg))
{
Intersection tmpInter=getIntersection(node->right,ray);
if (inter.distance>=tmpInter.distance)
inter = tmpInter;
}
return inter;
}
结果
结果是build目录下的ppm格式图片。