作业来自官网
总览
在之前的练习中,我们实现了 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 Trac-
ing
用到的函数
- 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 的概率
解答
castRay() in Scene.cpp
- 先通过光线投射算法得到的光线对场景的物体求交,获得下一个着色点p。
- 着色点是光源就直接返回光源的emission值。
- 时刻注意是否相交,不相交直接返回0,可以减少重复计算。
- 调用方法后ray的属性是不一定填全的,有时候t就没有,调用前要看一下方法。
- 计算 cos θ ′ \cos \theta ' cosθ′时的wi应该是负的,好像作业文件里面的伪代码不太对?
注意 main.cpp调分辨率、Render.cpp调spp可以加快运行速度。
Vector3f Scene::castRay(const Ray &ray, int depth) const
{
//获得光线最开始到达的交点
Intersection p = intersect(ray);
if(!p.happened) return Vector3f(0,0,0);
//着色点是光源
if(p.m->hasEmission()){
return p.m->getEmission();
}
Vector3f wo = ray.direction;
Vector3f L_dir(0);
//在光源上的交点
Intersection inter_l;
//光源的pdf
float pdf;
//对光源进行取样,得到光源的pdf和光源上的点和光源的面积密度pdf
sampleLight(inter_l,pdf);
Vector3f x = inter_l.coords;
//获得着色点p到光源的方向与距离
float p2lightDist=(x-p.coords).norm();
Vector3f p2lightDir=(x-p.coords).normalized();
// 从p点向取样得到的光源方向射出一条光线wi
Ray wi(p.coords,p2lightDir);
//从p点向光源方向射出一条光线得到交点inter_tmp,为判断光源与着色点是否有物体做准备
Intersection inter_tmp=intersect(wi);
//判断中间没有物体阻隔
if ((inter_l.coords-inter_tmp.coords).norm()<EPSILON*EPSILON){
// 这里作业说明好像不是很正确,算cos theta prime时的wi应该时负的。
L_dir = inter_l.emit*p.m->eval(wo,wi.direction,p.normal)*dotProduct(wi.direction,p.normal)*dotProduct(-wi.direction,inter_l.normal)/(p2lightDist*p2lightDist)/pdf;
}
Vector3f L_indir=0.0f;
//俄罗斯轮盘获得随机概率
float P_RR = RussianRoulette;
int seed = rand()%10;
if (seed*1.0/10>P_RR)//生存概率
return L_dir;
//取样一个新的随机方向
Vector3f wi_new = p.m->sample(wo,p.normal);
Ray r(p.coords,wi_new);
Intersection q = intersect(r);
//如果q点的物体不是光源
if (q.happened&& !q.obj->hasEmit())
L_indir = castRay(r,depth+1)* p.m->eval(wo,wi_new,p.normal)*dotProduct(wi_new,p.normal)/p.m->pdf(wo,wi_new,p.normal)/P_RR;
return L_dir + L_indir;
}
IntersectP in the Bounds3.hpp
在这里犯了错误,上次作业遗留的,注意这里等号的条件也要包含在内,否则场景会渲染不全。
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;
}
结果
比较曲折,最开始得到全红的图片,然后又有几次得到全黑的图片,调试后得到了一张没渲染全的图片(是求交没考虑等号造成的)。
修改后就渲染比较全了(SPP=4)