在之前的练习中,我们实现了 Whitted-Style Ray Tracing 算法,并且用 BVH等加速结构对于求交过程进行了加速。在本次实验中,我们将在上一次实验的基础上实现完整的 Path Tracing 算法。至此,我们已经来到了光线追踪版块的最后一节内容。
2.1修改的内容
相比上一次实验,本次实验对框架的修改较大,主要在以下几方面:
• 修改了 main.cpp,以适应本次实验的测试模型 CornellBox
• 修改了 Render,以适应 CornellBox 并且支持 Path Tracing 需要的同一 Pixel多次 Sample
• 修改了 Object,Sphere,Triangle,TriangleMesh,BVH,添加了 area 属性与Sample 方法,以实现对光源按面积采样,并在 Scene 中添加了采样光源的接口 sampleLight
• 修改了 Material 并在其中实现了 sample, eval, pdf 三个方法用于 Path Tracing 变量的辅助计算2.2你需要迁移的内容
你需要从上一次编程练习中直接拷贝以下函数到对应位置:
• Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,请直接将上次实验中实现的内容粘贴在此。
• IntersectP(const Ray& ray, const Vector3f& invDir,const std::array& dirIsNeg) in the Bounds3.hpp: 这个函数的2作用是判断包围盒 BoundingBox 与光线是否相交,请直接将上次实验中实现的内容粘贴在此处,并且注意检查 t_enter = t_exit 的时候的判断是否正确。
• getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: BVH查找过程,请直接将上次实验中实现的内容粘贴在此处.2.3编译运行
基础代码只依赖于 CMake,下载基础代码后,执行下列命令,就可以编译这个项目:
mkdir build cd ./ build cmake .. make在此之后,你就可以通过 ./Raytracing 来执行程序。请务必确保程序可以正常编译之后,再进入下一节的内容。
在本次实验中,你只需要修改这一个函数:
• castRay(const Ray ray, int depth)in Scene.cpp: 在其中实现 Path Tracing 算法
可能用到的函数有:
• intersect(const Ray ray)in Scene.cpp: 求一条光线与场景的交点
• sampleLight(Intersection pos, float pdf) in Scene.cpp: 在场景的所有光源上按面积 uniform 地 sample 一个点,并计算该 sample 的概率密度
• sample(const Vector3f wi, const Vector3f N) in Material.cpp: 按照该材质的性质,给定入射方向与法向量,用某种分布采样一个出射方向
• pdf(const Vector3f wi, const Vector3f wo, const Vector3f N) in Maerial.cpp: 给定一对入射、出射方向与法向量,计算 sample 方法得到该出射方向的概率密度
• eval(const Vector3f wi, const Vector3f wo, const Vector3f N) in Material.cpp: 给定一对入射、出射方向与法向量,计算这种情况下的 f_r 值可能用到的变量有:
• RussianRoulette in Scene.cpp: P_RR, Russian Roulette 的概率
IntersectP(const Ray& ray, const Vector3f& invDir,const std::array
- inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
- const std::array<int, 3>& dirIsNeg) const
- {
- Vector3f tmin = (pMin - ray.origin) * invDir;
- Vector3f tmax = (pMax - ray.origin) * invDir;
- if (dirIsNeg[0])
- std::swap(tmin.x, tmax.x);
- if (dirIsNeg[1])
- std::swap(tmin.y, tmax.y);
- if (dirIsNeg[2])
- std::swap(tmin.z, tmax.z);
- float texit = std::min(tmax.x, std::min(tmax.y, tmax.z));
- float tenter = std::max(tmin.x, std::max(tmin.y, tmin.z));
- return tenter <= texit&& texit >= 0;
- }
- BVHBuildNode* BVHAccel::recursiveBuild(std::vector
- {
- 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;
- node->area = objects[0]->getArea();
- return node;
- }
- else if (objects.size() == 2) {
- node->left = recursiveBuild(std::vector{objects[0]});
- node->right = recursiveBuild(std::vector{objects[1]});
-
- node->bounds = Union(node->left->bounds, node->right->bounds);
- node->area = node->left->area + node->right->area;
- return node;
- }
- else {
- Bounds3 centroidBounds;
- for (int i = 0; i < objects.size(); ++i)
- centroidBounds =
- Union(centroidBounds, objects[i]->getBounds().Centroid());
- 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
- auto rightshapes = std::vector
-
- assert(objects.size() == (leftshapes.size() + rightshapes.size()));
-
- node->left = recursiveBuild(leftshapes);
- node->right = recursiveBuild(rightshapes);
-
- node->bounds = Union(node->left->bounds, node->right->bounds);
- node->area = node->left->area + node->right->area;
- }
-
- return node;
- }
getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: BVH查找过程,请直接将上次实验中实现的内容粘贴在此处.
- Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
- {
- // TODO Traverse the BVH to find intersection
- Intersection inter;
- Vector3f invdir(1 / ray.direction.x , 1 / ray.direction.y , 1 / ray.direction.z);
- //判断射线的方向正负,如果负,为1;bounds3.hpp中会用到。
- std::array<int , 3> dirIsNeg;
- dirIsNeg[0] = ray.direction.x < 0;
- dirIsNeg[1] = ray.direction.y < 0;
- dirIsNeg[2] = ray.direction.z < 0;
- //没有交点
- if (!node->bounds.IntersectP(ray,invdir,dirIsNeg)){
- return inter;
- }
- //有交点,且该点为叶子节点,去和三角形求交
- if (node -> left ==nullptr && node -> right == nullptr){
- return node -> object -> getIntersection(ray);
- }
- //该点为中间节点,继续判断,并返回最近的包围盒交点
- Intersection hit1 = getIntersection(node->left , ray);
- Intersection hit2 = getIntersection(node->right , ray);
- return hit1.distance < hit2.distance ? hit1:hit2;
-
- }
Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,请直接将上次实验中实现的内容粘贴在此。
-
- 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);
- double det = dotProduct(e1, pvec);
- if (fabs(det) < EPSILON)
- return inter;
-
- double det_inv = 1. / det;
- Vector3f tvec = ray.origin - v0;
- u = dotProduct(tvec, pvec) * det_inv;
- if (u < 0 || u > 1)
- return inter;
- 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;
- // TODO find ray triangle intersection
- inter.normal = normal;
- inter.coords = ray(t_tmp);
- inter.distance = t_tmp;
- inter.happened = true;
- inter.m = m;
- inter.obj = this;
-
- return inter;
- }
castRay(const Ray ray, int depth)in Scene.cpp: 在其中实现 Path Tracing 算法
-
- // Implementation of Path Tracing
- Vector3f Scene::castRay(const Ray &ray, int depth) const
- {
- // TO DO Implement Path Tracing Algorithm here
- //创建变量以储存直接和间接光照计算值
- Vector3f L_dir = {0,0,0} , L_indir = {0,0,0};
- //1.判断是否有交点:光线与场景中物体相交?
- Intersection intersection = Scene::intersect(ray); //求一条光线与场景的交点
- if (!intersection.happened) //没交点
- return {};
- //2.ray打到光源了:说明渲染方程只用算前面的自发光项,因此直接返回材质的自发光项
- if (intersection.m->hasEmission()){//一、交点是光源:
- // if (depth == 0)//第一次打到光
- return intersection.m->getEmission();
- // return {};//弹射打到光,直接返回0,0.0
- }
-
- //---------二、交点是物体:1)向光源采样计算direct----------
- Intersection lightpos;
- float lightpdf = 0.0f;
- //对场景中的光源进行采样,得到采样点light_pos和pdf_light
- sampleLight(lightpos,lightpdf) ;//获得对光源的采样,包括光源的位置和采样的pdf(在场景的所有光源上按面积 uniform 地 sample 一个点,并计算该 sample 的概率密度)
- //计算光源到物体点的距离
- Vector3f collisionlight = lightpos.coords - intersection.coords;
- float dis = dotProduct(collisionlight,collisionlight);
-
- //判断光源与物体间有无遮挡,从光源发射一条相同方向的光线打到场景上,比较最后两者的距离
- Vector3f collisionlightdir = collisionlight.normalized(); //光源到物体的光线方向
- Ray light_to_object_ray (intersection.coords,collisionlightdir); //光源到物体的光线
-
- Intersection light_to_anything_ray = Scene::intersect(light_to_object_ray);
- auto f_r = intersection.m -> eval(ray.direction,collisionlightdir,intersection.normal);//材质,课上说了,BRDF==材质,ws不参与计算
- //判断光源与物体间有无遮挡
- if (light_to_anything_ray.distance - collisionlight.norm() > -0.005){//无遮挡
- //渲染方程
- L_dir = lightpos.emit * f_r * dotProduct(collisionlightdir , intersection.normal) * dotProduct(-collisionlightdir,lightpos.normal) / dis / lightpdf;
-
- }
-
- //--------二、交点是物体:2)向其他物体采样递归计算indirect---------;
- //俄罗斯轮盘赌
- if (get_random_float() > RussianRoulette)//打到物体后对半圆随机采样使用RR算法
- return L_dir;
- //随机生成一个w0方向
- Vector3f w0 = intersection.m ->sample(ray.direction , intersection.normal).normalized();//这里的w0其实没参与计算,返回的是一个随机的方向
- Ray object_to_object_ray(intersection.coords , w0);
- Intersection islight = Scene::intersect(object_to_object_ray);
- if (islight.happened && !islight.m->hasEmission()){ //光线击中的不是点光源
- float pdf = intersection.m ->pdf(ray.direction,w0,intersection.normal);
- f_r = intersection.m->eval(ray.direction,w0,intersection.normal);//材质,课上说了,BRDF==材质,ws不参与计算
- //渲染方程
- L_indir = castRay(object_to_object_ray,depth+1)*f_r*dotProduct(w0 , intersection.normal) / pdf / RussianRoulette;
- }
- return L_dir + L_indir;
-
-
- }

PS:在windows上运行的时候需要修改global.cpp文件的以下函数,不然可能会跑不了
inline float get_random_float()
{
static std::random_device dev;
static std::mt19937 rng(dev());
static std::uniform_real_distribution
dist(0.f, 1.f); // distribution in range [0,1] return dist(rng);
}