实现完整的 Path Tracing 算法

需要修改这一个函数:

• 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
// 本函数为发射一条光ray和其当前深度depth,求最终的着色点颜色值 // 所用函数介绍:
// Intersection intersect(const Ray& ray) const;
// 返回光源与BVH的交点
// void sampleLight(Intersection &pos, float &pdf);
// 对所有光源按面积均匀采样一个点,并将pdf修改为采样的概率密度
// Vector3f Material::sample(const Vector3f &wi, const Vector3f &N);
// 按照该材质的性质和给定入射方向wi与法向量N,用某种分布采样一个出射方向
// float Material::pdf(V3f wi, V3f &wo, V3f N)
// 给定入射wi,出射wo,法向量N,计算采样得出的该方向上的概率密度
// Vector3f Material::eval(V3f wi, V3f &wo, V3f N)
// 计算f_r值
// Scene.RussianRoulette
// PRR概率 Intersection intersection = intersect(ray); // 首先获取着色点位置pos if(intersection.happened){
Vector3f L_dir(0.0), L_indir(0.0); Vector3f p = intersection.coords;
Vector3f wo = ray.direction; // 观测点打向着色点的方向
Vector3f N = intersection.normal; // 法线即为交点法线
Material *m = intersection.m; // 首先是直接光照,从光源发出
// 采样光源获取x点
Intersection light_intersection;
float pdf_light = 0.0;
sampleLight(light_intersection, pdf_light);
Vector3f x = light_intersection.coords; // 从p点向x点打一光线ps,方向为ws
Vector3f ws = (x - p).normalized();
Ray ray_px(p, ws); //从p点加一段距离避免判断光线被自己挡住
Intersection intersect_px = intersect(ray_px);
// 如果光线打到的点是光源则修改L_dir
if(intersect_px.happened && intersect_px.m->hasEmission()){
Vector3f NN = intersect_px.normal;
L_dir = light_intersection.emit * m->eval(wo, ws, N)
* dotProduct(ws, N) * dotProduct(-ws, NN) / intersect_px.distance / pdf_light;
} // 然后是间接光照,采用RR判断是否需要继续打光线,故不需要使用depth
if(get_random_float() <= RussianRoulette){
// 从p点向外打一根光线,如果有交点设为q
Vector3f wi = m->sample(wo, N).normalized();
Ray ray_pq(p, wi);
Intersection intersect_q = intersect(ray_pq);
// 如果打中且不是光源计算间接光照
if(intersect_q.happened && !intersect_q.m->hasEmission()){
L_indir = castRay(ray_pq, depth + 1) * m->eval(wo, wi, N)
* dotProduct(wi, N) / m->pdf(wo, wi, N) / RussianRoulette;
}
}
return m->getEmission() + L_dir + L_indir;
} //如果没有交点,返回黑色
return Vector3f(0.0);
}

ps:需要注意的是,由于框架改变、坐标系改变、通过自发光消除黑边等原因我们需要修改其他的函数

void Scene::sampleLight(Intersection &pos, float &pdf) const in Scene.cpp

void Scene::sampleLight(Intersection &pos, float &pdf) const
{
float emit_area_sum = 0;
for (uint32_t k = 0; k < objects.size(); ++k) {
// 如果物体发光则为光源
if (objects[k]->hasEmit()){
// 发光面积累加上物体所有三角形面积
emit_area_sum += objects[k]->getArea();
}
}
float p = get_random_float() * emit_area_sum; //随机取一块面积
emit_area_sum = 0;
for (uint32_t k = 0; k < objects.size(); ++k) {
if (objects[k]->hasEmit()){
emit_area_sum += objects[k]->getArea();
// 如果当前所取面积和超过之前所取阈值 p,
// 则调用MeshTriangle类的采样函数
// 传入交点位置pos,和pdf,修改pos的发光位置和pdf值
if (p <= emit_area_sum){
objects[k]->Sample(pos, pdf);
break;
}
}
}
}
inline bool Bounds3::IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg) const in Bounds3.hpp

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 Vector3f t_enter_xzy = (pMin - ray.origin) * invDir;
Vector3f t_exit_xzy = (pMax - ray.origin) * invDir;
// 如果光线在某一个轴上的分量是负数,则对应轴上的面越大越先进入
if(!dirIsNeg[0]) std::swap(t_enter_xzy.x, t_exit_xzy.x);
if(!dirIsNeg[1]) std::swap(t_enter_xzy.y, t_exit_xzy.y);
if(!dirIsNeg[2]) std::swap(t_enter_xzy.z, t_exit_xzy.z); float tenter = std::max(std::max(t_enter_xzy.x, t_enter_xzy.y), t_enter_xzy.z);
float texit = std::min(std::min(t_exit_xzy.x, t_exit_xzy.y), t_exit_xzy.z); return tenter <= texit && texit >= 0;
}
Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const in BVH.cpp

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{
Intersection res;
// TODO Traverse the BVH to find intersection // 如果光线与改包围盒无交点,直接返回false的res
if(!node->bounds.IntersectP(ray, ray.direction_inv, std::array<int, 3>
{ray.direction.x > 0, ray.direction.y > 0, ray.direction.z > 0})){
return res;
}
// 如果当前节点为叶子节点,遍历node中的每一个物体使之于光线求交
if(node->object){
return node->object->getIntersection(ray);
} // 如果当前节点的盒子与光线相交则分别递归到左右
Intersection l = getIntersection(node->left, ray);
Intersection r = getIntersection(node->right, ray); //如果有两个交点,返回较近的那个交点
return l.distance <= r.distance ? l : r;
}
bool Material::hasEmission() in Material.hpp

bool Material::hasEmission() {
// 根据Material初始化时传入的第二个Vector3f的值决定(非0即合法即为光源)
if (m_emission.norm() > EPSILON) return true;
else return false;
}
inline Intersection Triangle::getIntersection(Ray ray) 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; // TODO find ray triangle intersection
// 若相交,则修改交点inter的数据
if (t_tmp < 0) return inter; inter.happened = true;
inter.coords = ray(t_tmp); // 根据光照内置()运算获取交点坐标
inter.distance = dotProduct(t_tmp * ray.direction, t_tmp * ray.direction); // 距离平方
inter.m = m;
inter.obj = this;
inter.normal = normal; return inter;
}

多线程加速:

在void Renderer::Render(const Scene& scene) in Render.cpp中,通过#pragma omp parallel for预处理指令使循环获得多线程并行处理能力

// The main render function. This where we iterate over all pixels in the image,
// generate primary rays and cast these rays into the scene. The content of the
// framebuffer is saved to a file.
void Renderer::Render(const Scene& scene)
{
std::vector<Vector3f> framebuffer(scene.width * scene.height); float scale = tan(deg2rad(scene.fov * 0.5));
float imageAspectRatio = scene.width / (float)scene.height;
Vector3f eye_pos(278, 273, -800);
int m = 0; // change the spp value to change sample ammount
int spp = 4;
std::cout << "SPP: " << spp << "\n";
for (uint32_t j = 0; j < scene.height; ++j) {
for (uint32_t i = 0; i < scene.width; ++i) {
// generate primary ray direction
float x = (2 * (i + 0.5) / (float)scene.width - 1) *
imageAspectRatio * scale;
float y = (1 - 2 * (j + 0.5) / (float)scene.height) * scale; Vector3f dir = normalize(Vector3f(-x, y, 1));
#pragma omp parallel for
for (int k = 0; k < spp; k++){
framebuffer[m] += scene.castRay(Ray(eye_pos, dir), 0) / spp;
}
m++;
}
UpdateProgress(j / (float)scene.height);
}
UpdateProgress(1.f); // save framebuffer to file
FILE* fp = fopen("binary.ppm", "wb");
(void)fprintf(fp, "P6\n%d %d\n255\n", scene.width, scene.height);
for (auto i = 0; i < scene.height * scene.width; ++i) {
static unsigned char color[3];
color[0] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].x), 0.6f));
color[1] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].y), 0.6f));
color[2] = (unsigned char)(255 * std::pow(clamp(0, 1, framebuffer[i].z), 0.6f));
fwrite(color, 1, 3, fp);
}
fclose(fp);
}

效果图

随机推荐

  1. 批量删除git tag

    批量删除远程tag $ git ls-remote -t --refs -q | awk '{print ":"$2}' | xargs git push origin To ss ...

  2. MySQL 亿级数据平滑迁移实战

    作者:来自 vivo 互联网服务器团队- Li Gang 本文介绍了一次 MySQL 数据迁移的流程,通过方案选型.业务改造.双写迁移最终实现了亿级数据的迁移. 一.背景 预约业务是 vivo 游戏中 ...

  3. 树上倍增求 LCA 模板

    void dfs(int x,int fa,int d){ deep[x]=d;dp[x][0]=fa; for(int i=1;i<=lg2[deep[x]];++i){ dp[x][i]=d ...

  4. 网站接入qq登录

    网站接入qq登录可以省去注册账号,增加网站的体验度.那么要实现我自己搭建的站点天天博客的qq单点登录需要以下几个步骤:1,申请appid和appkey 首先要去https://connect.qq.c ...

  5. 2024DASCTF

    DASCTF prese 一眼控制了平坦化,可以用d810梭一下 跟进一下main_crypto这个函数 主要是两部分,第一部分是生成一个256大小的数组,通过输入的长度和遍历生成的一个数组 第二部分 ...

  6. 开源项目管理工具 Plane 安装和使用教程

    说到项目管理工具,很多人脑海中第一个蹦出来的可能就是 Jira 了.没错,Jira 确实很强大,但是...它也有点太强大了,既复杂又昂贵,而且目前也不再提供私有化部署版本了. 再说说飞书,作为国产之光 ...

  7. .NET 8 + WPF 企业级工作流系统

    前言 推荐一款基于.NET 8.WPF.Prism.DryIoc.MVVM设计模式.Blazor以及MySQL数据库构建的企业级工作流系统的WPF客户端框架-AIStudio.Wpf.AClient ...

  8. 几步轻松定制私人AI助手

    这两年大模型的发展持续火热,以至于许多资本和学者认为AI出现了泡沫,根本原因还是因为大模型目前还没有出现能够结合行业切实落地的应用. 我才不关注泡沫不泡沫呢,我只关注大模型能给我带来哪些帮助即可.大模 ...

  9. 击败全球上千参赛队伍,合合信息获ICDAR“文本篡改检测”赛道冠军

    AI技术的快速发展激发了人们对于美好未来的畅享,也带来了潜在的危机,数据泄露.电信诈骗等系列风险与隐患开始浮出水面.利用科技手段构建可信的技术发展环境,保护使用者的信息及财产安全,正在成为行业共识. ...

  10. Java怎么把多个对象的list的数据合并

    1.示例一:创建几个包含Person对象的List,并将它们合并成一个新的List 在Java中,将多个对象的List合并通常涉及到遍历这些List并将它们的元素添加到一个新的List中.这里,我将给 ...