附其他所有作业超链接如下:
Games101 作业0:作业0
Games101 作业1:作业1
Games101 作业2:作业2
Games101 作业3:作业3
Games101 作业4:作业4
Games101 作业5:作业5
Games101 作业6:作业6
Games101 作业7:作业7
完整代码获取途径:
https://github.com/liupeining/Games_101_homework
“在之前的练习中,我们实现了 Whitted-Style Ray Tracing 算法,并且用 BVH等加速结构对于求交过程进行了加速。在本次实验中,我们将在上一次实验的基础上实现完整的 Path Tracing 算法。至此,我们已经来到了光线追踪版块的最后一节内容。”
修改的内容:
你需要从上一次编程练习中直接拷贝以下函数到对应位置:
• Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,请直接将上次实验中实现的内容粘贴在此。
/*inline 函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,
它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。
为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。
栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。
*/
inline Intersection Triangle::getIntersection(Ray ray)
{
Intersection inter;
//如果结果大于0,那么这两个向量的夹角小于90度;
//这里normal是从三角指向眼的,因此若同方向则不可能穿过三角
if (dotProduct(ray.direction, normal) > 0)
return inter; // happen = false;
/*
Intersection的定义
Intersection(){
happened=false;
coords=Vector3f();
normal=Vector3f();
distance= std::numeric_limits::max();
obj =nullptr;
m=nullptr;
}
*/
double u, v, t_tmp = 0;
Vector3f pvec = crossProduct(ray.direction, e2); //S1
double det = dotProduct(e1, pvec); //S1*E1(顺便说一句,能推出S1*E1=-D*N)
if (fabs(det) < EPSILON) //const float EPSILON = 0.00001; 分母特别小将导致t特别大,相当于很远很远,看不见
return inter;
double det_inv = 1. / det; // 1/S1*E1
Vector3f tvec = ray.origin - v0; //S
u = dotProduct(tvec, pvec) * det_inv; //b1
if (u < 0 || u > 1)
return inter;
Vector3f qvec = crossProduct(tvec, e1); //S2
v = dotProduct(ray.direction, qvec) * det_inv; //b2
if (v < 0 || u + v > 1)
return inter;
t_tmp = dotProduct(e2, qvec) * det_inv; //t
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;
}
• IntersectP(const Ray& ray, const Vector3f& invDir, const std::array
这句提示其实非常关键!老师上课的时候经常提到,图形学很少关心相等的时候的条件,但是这里确实会影响到输出的结果。假如 t_enter = t_exit 时返回的是false,那么作业6并不会出任何问题,但作业7中会导致只能渲染出左侧的盒子和墙体。因此,如果你在作业6中写的是t_enter < t_exit return true,请务必改成小于等于。
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir,
const std::array<int, 3>& dirIsNeg) const
{
//invDir = 1 / D; t = (Px - Ox) / dx
float t_Min_x = (pMin.x - ray.origin.x)*invDir[0];
float t_Min_y = (pMin.y - ray.origin.y)*invDir[1];
float t_Min_z = (pMin.z - ray.origin.z)*invDir[2];
float t_Max_x = (pMax.x - ray.origin.x)*invDir[0];
float t_Max_y = (pMax.y - ray.origin.y)*invDir[1];
float t_Max_z = (pMax.z - ray.origin.z)*invDir[2];
//如果发现射线的方向是反的,调换t_min和t_max的位置。
if(dirIsNeg[0])
{
float t = t_Min_x;
t_Min_x = t_Max_x;
t_Max_x = t;
}
if(dirIsNeg[1])
{
float t = t_Min_y;
t_Min_y = t_Max_y;
t_Max_y = t;
}
if(dirIsNeg[2])
{
float t = t_Min_z;
t_Min_z = t_Max_z;
t_Max_z = t;
}
float t_enter = std::max(t_Min_x, std::max(t_Min_y, t_Min_z));
float t_exit = std::min(t_Max_x, std::min(t_Max_y, t_Max_z));
if(t_enter <= t_exit && t_exit >= 0)
return true;
return false;
}
• getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: BVH查找过程,请直接将上次实验中实现的内容粘贴在此处.
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
Intersection inter;
//invdir = 1 / D; bounds3.hpp中会用到。
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;
}
当然也可以粘贴SAH,详情见作业6的帖子。
https://blog.csdn.net/qq_41765657/article/details/121865049?spm=1001.2014.3001.5501
在本次实验中,你只需要修改这一个函数:
• 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 Material.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 的概率
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
Vector3f L_dir = {0, 0, 0}, L_indir = {0, 0, 0};
Intersection intersection = Scene::intersect(ray); //求一条光线与场景的交点
if (!intersection.happened) //没交点
return {};
if (intersection.m->hasEmission()) //一、交点是光源:
return intersection.m->getEmission();
/*
bool Material::hasEmission() {
if (m_emission.norm() > EPSILON) return true; 这里只有光才>0
else return false;
}
*/
//---------二、交点是物体:1)向光源采样计算direct----------
Intersection lightpos;
float lightpdf = 0.0f;
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);
if (light_to_anything_ray.distance - collisionlight.norm() > -0.005){ //没有遮挡,有横条就是数太小了!
//L_dir = L_i * f_r * cos_theta * cos_theta_x / |x - p | ^ 2 / pdf_light
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;
Vector3f w0 = intersection.m -> sample(ray.direction, intersection.normal).normalized();
Ray object_to_object_ray(intersection.coords, w0);
Intersection islight = Scene::intersect(object_to_object_ray);
if (islight.happened && !islight.m->hasEmission())
{ // shade(q, wi) * f_r * cos_theta / pdf_hemi / P_RR
float pdf = intersection.m->pdf(ray.direction, w0, intersection.normal);
f_r = intersection.m->eval(ray.direction, w0, intersection.normal);
L_indir = castRay(object_to_object_ray, depth + 1) * f_r * dotProduct(w0, intersection.normal) / pdf / RussianRoulette;
}
return L_dir + L_indir;
}
做出来图片有横条的原因在这里:这个数字不能过于接近0。
if (light_to_anything_ray.distance - collisionlight.norm() > -0.005){ //没有遮挡,有横条就是数太小了!
由于本人电脑虚拟机不太稳定,日常闪退,因此我渲染了474秒,取SPP=30,结果如下:
可以看到,不到七分钟的渲染结果还是非常不错的~(有锯齿是因为调整了输出尺寸,为了提高渲染速度)