本文为闫令琪老师的GAMES 101课程的作业7的个人实现,文中如有错漏欢迎指出。
作业要求
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: 给定一对入射、出射方向与法向量,计算该出射方向的概率密度 -
eval(const Vector3f wi, const Vector3f wo, const Vector3f N)
in Material.cpp: 给定一对入射、出射方向与法向量,计算这种情况下的f_r
值
代码实现
Path Tracing的理论知识,在此不多赘述。下图为作业文档中提供的伪代码,我们要做的就是把它写成C++代码:
castRay
// Implementation of Path Tracing
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
Vector3f L_dir, L_indir;
// TODO Implement Path Tracing Algorithm here
// ray and scene intersect at p
Intersection inter_p = intersect(ray);
if (!inter_p.happened)
{
return Vector3f();
}
if (inter_p.m->hasEmission())
{
return inter_p.m->getEmission();
}
Vector3f& p = inter_p.coords;
Vector3f& N = inter_p.normal;
Vector3f wo = (ray.origin - p).normalized();
Material* m = inter_p.m;
// sample light
Intersection inter;
float pdf_light;
sampleLight(inter, pdf_light);
// Get x, ws, NN, emit from inter
Vector3f& x = inter.coords;
Vector3f& NN = inter.normal;
Vector3f& emit = inter.emit;
Vector3f ws = (x - p).normalized();
float d = (x - p).norm();
// Shoot a ray from p to x
Ray r(p, ws);
Intersection i = intersect(r);
// If the ray is not blocked in the middle
if (i.distance - d > -0.001)
{
L_dir = emit * m->eval(wo, ws, N) * dotProduct(ws, N) *
dotProduct(-ws, NN) / (d * d * pdf_light);
}
// Test Russian Roulette with probability RR
float f = get_random_float();
if (f < RussianRoulette)
{
Vector3f wi = m->sample(wo, N).normalized();
// Trace a ray r(p, wi)
Ray r(p, wi);
Intersection i = intersect(r);
// If ray r hit a non-emitting object at q
if (i.happened && !i.m->hasEmission())
{
L_indir = castRay(r, depth + 1) * m->eval(wo, wi, N) *
dotProduct(wi, N) / m->pdf(wo, wi, N) / RussianRoulette;
}
}
return L_dir + L_indir;
}
绘制结果
直接光+间接光:
只有直接光:
简单的思考
1.原来的判断条件为t_enter < t_exit
,场景中有很多地方没被照亮
2.没有为t_enter
和t_exit
设初始值,当t_enter == t_exit == 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
float t_enter = std::numeric_limits<float>::min();
float t_exit = std::numeric_limits<float>::max();
for (int i = 0; i < 3; ++i)
{
float t_min = (pMin[i] - ray.origin[i]) * invDir[i];
float t_max = (pMax[i] - ray.origin[i]) * invDir[i];
if (!dirIsNeg[i])
{
std::swap(t_min, t_max);
}
t_enter = std::max(t_enter, t_min);
t_exit = std::min(t_exit, t_max);
}
return (t_enter <= t_exit) && (t_exit >= 0);
}
赶在2022上半年的最后一天,完成了Games101的第7个作业。(作业8是动画部分,暂且略过)。真心感谢这门课程,为我打开了图形学的大门。还有许多理解不到位的地方,未来继续学习。