GAMES101 作业7——光线追踪3(实现Path Tracing算法)
作业描述
在之前的练习中,我们实现了 Whitted-Style Ray Tracing 算法,并且用 BVH 等加速结构对于求交过程进行了加速。在本次实验中,我们将在上一次实验的基础上实现完整的 Path Tracing 算法。至此,我们已经来到了光线追踪版块的最后一节内容,实现光线追踪。
你需要从上一次编程练习中直接拷贝以下函数到对应位置:
- Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,请直接将上次实验中实现的内容粘贴在此。
- IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg) in the Bounds3.hpp: 这个函数的作用是判断包围盒 BoundingBox 与光线是否相交,请直接将上次实验中实现的内容粘贴在此处,并且注意检查 t_enter = t_exit 的时候的判断是否正确。
- getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: BVH 查找过程,请直接将上次实验中实现的内容粘贴在此处.
在本次实验中,你只需要修改这一个函数:
- castRay(const Ray ray, int depth) in Scene.cpp: 在其中实现 Path Tracing 算法
注意:本次作业需要对global.hpp中的get_random_float
方法中的三个变量增加static
约束,否则代码会在运行2%后异常退出。
inline float get_random_float()
{
static std::random_device dev;
static std::mt19937 rng(dev());
static std::uniform_real_distribution<float> dist(0.f, 1.f); // distribution in range [1, 6]
return dist(rng);
}
本次作业的代码:
- 实现着色过程:
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 的概率
需要注意的几点:
- 我们将光照对物体表面某一点的贡献分为
光源
和其他反射物
,当光源直接能够打到物体上时,不需要再考虑其他反射物的贡献,因为这根光线直接击中了光源 - 需要判断每一根光线是否击中光源
- 没有击中光源才需要通过 Russian Roulette 来计算其他反射物的光照贡献(间接光照)
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
Intersection inter = intersect(ray);
if (inter.happened)
{
// 如果射线第一次打到光源,直接返回
if (inter.m->hasEmission())
{
if (depth == 0)
{
return inter.m->getEmission();
}
else return Vector3f(0, 0, 0);
}
Vector3f L_dir(0, 0, 0);
Vector3f L_indir(0, 0, 0);
// 随机 sample 灯光,用该 sample 的结果判断射线是否击中光源
Intersection lightInter;
float pdf_light = 0.0f;
sampleLight(lightInter, pdf_light);
// 物体表面法线
auto& N = inter.normal;
// 灯光表面法线
auto& NN = lightInter.normal;
auto& objPos = inter.coords;
auto& lightPos = lightInter.coords;
auto diff = lightPos - objPos;
auto lightDir = diff.normalized();
float lightDistance = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;
Ray light(objPos, lightDir);
Intersection light2obj = intersect(light);
// 如果反射击中光源
if (light2obj.happened && (light2obj.coords - lightPos).norm() < 1e-2)
{
Vector3f f_r = inter.m->eval(ray.direction, lightDir, N);
L_dir = lightInter.emit * f_r * dotProduct(lightDir, N) * dotProduct(-lightDir, NN) / lightDistance / pdf_light;
}
if (get_random_float() < RussianRoulette)
{
Vector3f nextDir = inter.m->sample(ray.direction, N).normalized();
Ray nextRay(objPos, nextDir);
Intersection nextInter = intersect(nextRay);
if (nextInter.happened && !nextInter.m->hasEmission())
{
float pdf = inter.m->pdf(ray.direction, nextDir, N);
Vector3f f_r = inter.m->eval(ray.direction, nextDir, N);
L_indir = castRay(nextRay, depth + 1) * f_r * dotProduct(nextDir, N) / pdf / RussianRoulette;
}
}
return L_dir + L_indir;
}
return Vector3f(0, 0, 0);
}
当设置SPP为60时结果如下图所示:
- 点赞
- 收藏
- 关注作者
评论(0)