实现完整的 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. 2021 CCPC 威海

    gym 知乎 确定了我先写缺省源,gjk 正开,zsy 倒开的策略 先读了 EFGH,发现是概率.博弈.计数,只能做 H,感觉我已经到点了.队友签了 AJ zsy 说 M 是多项式快速幂并准备开冲,看 ...

  2. Nuitka打包

    Nuitka打包PySide6/PyQt 本文仅供本人知识总结使用,所以内容会比较浅显,不喜勿喷. 文章目录 Nuitka打包PySide6/PyQt 一.需要C++编译器 二.安装Nuitka 三. ...

  3. freertos学习笔记(十)事件标志组

    事件标志组 相当于用户平时定义的Flag,事件标志,不过freertos支持将该标志组作为启动task的条件 概述 分为8位和24位的模式(通过设置宏来配置) 每一位有0和1两个状态 用法 用于平常程 ...

  4. Ubuntu 笔记本设置合盖不息屏

    编辑 logind.conf 文件 你可以通过编辑 /etc/systemd/logind.conf 文件来控制盖子关闭时的行为: 找到以下几行(如果不存在,可以手动添加): #HandleLidSw ...

  5. 【YashanDB知识库】YAS-02024 lock wait timeout, wait time 0 milliseconds

    [标题]错误码处理 [问题分类]锁等待超时 [关键字]YAS-02024 [问题描述]执行语句时候,因锁等待超时执行语句失败 [问题原因分析]数据库默认锁等待时间为0秒,如果执行语句存在锁等待过长会执 ...

  6. C#/.NET/.NET Core技术前沿周刊 | 第 4 期(2024年9.1-9.8)

    前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...

  7. Failed to convert value of type 'java.lang.String' to required type

    DEBUG 微信小程序Java后台 Failed to convert value of type 'java.lang.String' to required type 产生这种条件的原因一般是使用 ...

  8. sql 分组查询并新增序号

    在SQL中,你可以使用ROW_NUMBER()函数来为结果集中的每一行新增一个序号.这个序号是基于某个排序条件的分区排序结果. 以下是一个简单的例子,假设我们有一个名为students的表,它有两列: ...

  9. WebRTC 简单入门与实践

    一.前言 WebRTC 技术已经广泛在各个行业及场景中被应用,但对多数开发者来说,实时音视频及相关技术却是比较不常接触到的. 做为一名 Web 开发者,WebRTC 这块的概念着实花了不少时间才搞明白 ...

  10. vue-router的History 模式常用的三种配置方式(去掉地址栏中的#号)

    第一种:nginx配置 conf主要的配置代码: http { # nginx负载均衡配置 upstream dynamic_balance { #ip_hash; server 192.168.10 ...