GAMES101:作业7记录

1. 总览

在之前的练习中,我们实现了 Whitted-Style Ray Tracing 算法,并且用 BVH等加速结构对于求交过程进行了加速。在本次实验中,我们将在上一次实验的基础上实现完整的 Path Tracing 算法。至此,我们已经来到了光线追踪版块的最后一节内容。

请认真阅读本文档,按照本文档指示的流程完成本次实验。

2. 调通框架

2.1 修改的内容

相比上一次实验,本次实验对框架的修改较大,主要在以下几方面:

  • 修改了 main.cpp,以适应本次实验的测试模型 CornellBox
  • 修改了 Render,以适应 CornellBox 并且支持 Path Tracing 需要的同一 Pixel多次 Sample
  • 修改了 Object,Sphere,Triangle,TriangleMesh,BVH,添加了 area 属性与Sample 方法,以实现对光源按面积采样,并在 Scene 中添加了采样光源的接口 sampleLight
  • 修改了 Material 并在其中实现了 sample, eval, pdf 三个方法用于 Path Tracing 变量的辅助计算

2.2 你需要迁移的内容

你需要从上一次编程练习中直接拷贝以下函数到对应位置:

  • Triangle::getIntersection in Triangle.hpp: 将你的光线-三角形相交函数粘贴到此处,请直接将上次实验中实现的内容粘贴在此。
  • IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg) in Bounds3.hpp: 这个函数的作用是判断包围盒 BoundingBox 与光线是否相交,请直接将上次实验中实现的内容粘贴在此处,并且注意检查 t_enter = t_exit 的时候的判断是否正确。
  • getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp: BVH
    查找过程,请直接将上次实验中实现的内容粘贴在此处.

3. 开始实现

在本次实验中,你只需要修改这一个函数:

  • 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 的概率

4. 实现

4.1 Triangle::getIntersection() 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;if (t_tmp < 0)return inter;// TODO find ray triangle intersectioninter.happened = true;inter.coords = ray.origin + t_tmp * ray.direction;inter.normal = this->normal;inter.distance = t_tmp;inter.obj = this;inter.m = this->m;return inter;
}

4.2 IntersectP(const Ray& ray, const Vector3f& invDir, const std::array<int, 3>& dirIsNeg) in Bounds3.hpp代码迁移

注意这里最后的判断使用的是tEnter <= tExit

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 intersectsVector3f vec_tEnter = (pMin - ray.origin) * invDir;Vector3f vec_tExit = (pMax - ray.origin) * invDir;if (!dirIsNeg[0])std::swap(vec_tEnter.x, vec_tExit.x);if (!dirIsNeg[1])std::swap(vec_tEnter.y, vec_tExit.y);if (!dirIsNeg[2])std::swap(vec_tEnter.z, vec_tExit.z);float tEnter = std::max(vec_tEnter.x, std::max(vec_tEnter.y, vec_tEnter.z));float tExit = std::min(vec_tExit.x, std::min(vec_tExit.y, vec_tExit.z));if (tEnter <=  tExit && tExit >= 0)return true;elsereturn false;}

如果没有加等于号,有的光线擦过物体了,但是没有返回颜色。于是渲染会变成这样:

在这里插入图片描述

4.3 getIntersection(BVHBuildNode* node, const Ray ray)in BVH.cpp 代码迁移

Intersection BVHAccel::getIntersection(BVHBuildNode* node, const Ray& ray) const
{// TODO Traverse the BVH to find intersectionif (!node->bounds.IntersectP(ray, ray.direction_inv, std::array<int, 3>({ray.direction.x > 0, ray.direction.y > 0, ray.direction.z > 0})))return Intersection();if (node->left == nullptr && node->right == nullptr)return node->object->getIntersection(ray);Intersection hitLeft = BVHAccel::getIntersection(node->left, ray);Intersection hitRight = BVHAccel::getIntersection(node->right, ray);return hitLeft.distance < hitRight.distance ? hitLeft : hitRight;
}

4.4 castRay(const Ray ray, int depth) in Scene.cpp实现

基本按照伪代码来实现了:

Path Tracing 的实现说明伪代码:

在这里插入图片描述

这里注意处理两个边界条件,一是光线没有打到物体,则返回黑色,二是光纤打到了光源,则返回光源的颜色。这里的变量都是按照伪代码来编写的可以一一对应,下面是伪代码的直接光照和间接光照的示意图:

在这里插入图片描述

在这里插入图片描述

之所以说wo 定义与课程介绍相反,是因为课程上是这样定义wo的(间接光照也是差不多的):

在这里插入图片描述

Vector3f Scene::castRay(const Ray &ray, int depth) const
{Intersection p = intersect(ray);if (!p.happened)return Vector3f(0.0);if (p.m->hasEmission())return p.m->getEmission(); Vector3f L_dir(0.0, 0.0, 0.0);Vector3f L_indir(0.0, 0.0, 0.0);Intersection x;float pdf_light = 0.0;sampleLight(x, pdf_light);Vector3f vec_pTox = x.coords - p.coords;Vector3f ws = vec_pTox.normalized();float dist_pTox2 = dotProduct(vec_pTox, vec_pTox);Vector3f emit = x.m->getEmission();Vector3f N = p.normal;Vector3f NN = x.normal;Vector3f wo = ray.direction;Ray ray_pTox(p.coords, ws);Intersection interRay_pTox = intersect(ray_pTox);if (interRay_pTox.distance  + 0.01 > vec_pTox.norm()){L_dir = emit * p.m->eval(wo, ws, N) * dotProduct(ws, N) * dotProduct(-ws, NN) / dist_pTox2 / pdf_light;}if (get_random_float() <= RussianRoulette){Vector3f wi = p.m->sample(wo, N);Ray rayWi(p.coords, wi);Intersection q =intersect(rayWi);if (q.happened && !q.m->hasEmission())L_indir = castRay(rayWi, depth + 1) * p.m->eval(wo, wi, N) * dotProduct(wi, N) / p.m->pdf(wo, wi, N) / RussianRoulette;}return L_dir + L_indir;}

要注意对光源采样的交点x里只是设置了交点的emit,如果代码要统一通过x.m->getEmission()(x是一个Intersection的变量,是p到x光线的交点),我们需要跳到sampleLight看一下:

void Scene::sampleLight(Intersection &pos, float &pdf) const
{...for (uint32_t k = 0; k < objects.size(); ++k) {if (objects[k]->hasEmit()){...objects[k]->Sample(pos, pdf);break;...}}
}

再转到Sample函数看一下,Sphere.hppTriangle.hpp的Sample函数做个小小的更改,都加上一句pos.m=m

Sphere.hpp
void Sample(Intersection &pos, float &pdf){float theta = 2.0 * M_PI * get_random_float(), phi = M_PI * get_random_float();Vector3f dir(std::cos(phi), std::sin(phi)*std::cos(theta), std::sin(phi)*std::sin(theta));pos.coords = center + radius * dir;pos.normal = dir;pos.emit = m->getEmission();pos.m = m;pdf = 1.0f / area;}
Triangle.hpp
void Sample(Intersection &pos, float &pdf){float x = std::sqrt(get_random_float()), y = get_random_float();pos.coords = v0 * (1.0f - x) + v1 * (x * (1.0f - y)) + v2 * (x * y);pos.normal = this->normal;pos.m = m;pdf = 1.0f / area;}
void Sample(Intersection &pos, float &pdf){bvh->Sample(pos, pdf);pos.emit = m->getEmission();pos.m = m;}

spp=16(渲染46min)

在这里插入图片描述

spp=256(耗时14h多好像)

在这里插入图片描述

4.5 提高部分:多线程

另外要加快渲染的速度,可以把global.hpp里的get_random_float函数的变量dev、rng和dist前面都加上static,这样就不用每次调用函数的时候重复建立,提高计算的速度。

使用多线程推荐先看一下教程:C++11 多线程(std::thread)详解

这里参考了大佬ycr的帐号的博客【GAMES101】作业7(提高)路径追踪 多线程、Microfacet(全镜面反射)、抗锯齿

思路:我们可以将屏幕像素分成多块给多个线程执行,比如我们的scene尺寸为784*784,要用32个线程并发执行时,就将每块设置为(784/32) * 784的大小。

原来的Render代码:

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 ammountint spp = 1;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 directionfloat 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));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);...
}

4.5.1 使用std::thread库

头文件添加:

#include <thread>
#include <mutex>

添加全局变量:

const float EPSILON = 0.00001;//添加下面的变量
std::mutex mtx;
int progress = 0;

这里写的是按行划分

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 ammountint spp = 16;std::cout << "SPP: " << spp << "\n";int nums_threads = 32;int theread_height = scene.height / nums_threads;std::vector<std::thread> th;//定义renderRows 函数作为多线程的入口auto renderRows = [&](int start_height, int end_height){for (uint32_t j = start_height; j < end_height; ++j) {for (uint32_t i = 0; i < scene.width; ++i) {// generate primary ray directionfloat 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));for (int k = 0; k < spp; k++){framebuffer[j * scene.width + i] += scene.castRay(Ray(eye_pos, dir), 0) / spp;  }}//添加锁锁住进度条更新相关语句,避免多线程同时访问全局变量的时候出现冲突mtx.lock();progress++;UpdateProgress(progress / (float)scene.height);mtx.unlock();}};for (int i = 0; i < nums_threads; ++i){th.emplace_back(std::thread(renderRows, i * theread_height, (i + 1) * theread_height));}for (int i = 0; i < nums_threads; ++i){th[i].join();//用join方法等待所有线程结束,防止有的线程还没结束主程序就结束了}...
}

使用std::thread库来实现多线程的时候CMakeLists.txt也要修改否则会报错:

cmake_minimum_required(VERSION 3.10)
project(RayTracing)set(CMAKE_CXX_STANDARD 17)set(THREADS_PREFER_PTHREAD_FLAG ON) # 新添加语句
find_package(Threads REQUIRED) # 新添加语句add_executable(RayTracing main.cpp Object.hpp Vector.cpp Vector.hpp Sphere.hpp global.hpp Triangle.hpp Scene.cppScene.hpp Light.hpp AreaLight.hpp BVH.cpp BVH.hpp Bounds3.hpp Ray.hpp Material.hpp Intersection.hppRenderer.cpp Renderer.hpp)target_link_libraries(RayTracing ${CMAKE_THREAD_LIBS_INIT}) # 新添加语句

spp为16耗时1分多钟就渲染完了:

在这里插入图片描述
上面是按行分,也可以按行列分块,我这里按行分了32块,列分了32块:

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 ammountint spp = 16;std::cout << "SPP: " << spp << "\n";int nums_threads = 32;int thread_height = scene.height / nums_threads;int thread_width = scene.width / nums_threads;std::vector<std::thread> th;//定义renderBlocks 函数作为多线程的入口auto renderBlocks = [&](int start_height, int end_height, int start_width, int end_width){for (uint32_t j = start_height; j < end_height; ++j) {for (uint32_t i = start_width; i < end_width; ++i) {// generate primary ray directionfloat 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));for (int k = 0; k < spp; k++){framebuffer[j * scene.width + i] += scene.castRay(Ray(eye_pos, dir), 0) / spp;  }progress++;}//添加锁锁住进度条更新相关语句,避免多线程同时访问全局变量的时候出现冲突mtx.lock();UpdateProgress(progress / (float)scene.height / (float)scene.width);mtx.unlock();}};for (int i = 0; i < nums_threads; ++i){for (int j = 0; j < nums_threads; ++j){th.emplace_back(std::thread(renderBlocks, i * thread_height, (i + 1) * thread_height, j * (thread_width), (j + 1) * thread_width));}}for (int i = 0; i < nums_threads * nums_threads; ++i){th[i].join();//用join方法等待所有线程结束,防止有的线程还没结束主程序就结束了}UpdateProgress(1.f);...
}

不过好像差别不是很大。

4.5.2 openmp加速

Games101 作业7 绕坑引路 (Windows)提到了使用openmp

在这里插入图片描述

在这里插入图片描述

使用openmp操作简单,只需要在需要并行化的for前面加上:

#pragma omp parallel for

添加头文件:

#include <omp.h>

和前面一样定义全局变量:

const float EPSILON = 0.00001;//添加下面的变量
omp_lock_t lock;
int progress = 0;Render函数:```cpp
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 ammountint spp = 16;std::cout << "SPP: " << spp << "\n";int nums_thread = 32;int thread_height = scene.height / nums_thread;//定义renderRows 函数作为多线程的入口auto renderRows = [&](int start_height, int end_height){for (uint32_t j = start_height; j < end_height; ++j) {for (uint32_t i = 0; i < scene.width; ++i) {// generate primary ray directionfloat 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));for (int k = 0; k < spp; k++){framebuffer[j * scene.width + i] += scene.castRay(Ray(eye_pos, dir), 0) / spp;  }}//添加锁锁住进度条更新相关语句,避免多线程同时访问全局变量的时候出现冲突omp_set_lock(&lock);progress++;UpdateProgress(progress / (float)scene.height);omp_unset_lock(&lock);}};#pragma omp parallel forfor (int i = 0; i < nums_thread; ++i){renderRows(i * thread_height, (i + 1) * thread_height);}UpdateProgress(1.f);...
}

然后对应的CMakeLists.txt设置为:

cmake_minimum_required(VERSION 3.10)
project(RayTracing)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_FLAGS "${CAMKE_CXX_FLAGS} -O3 -fopenmp")add_executable(RayTracing main.cpp Object.hpp Vector.cpp Vector.hpp Sphere.hpp global.hpp Triangle.hpp Scene.cppScene.hpp Light.hpp AreaLight.hpp BVH.cpp BVH.hpp Bounds3.hpp Ray.hpp Material.hpp Intersection.hppRenderer.cpp Renderer.hpp)

spp16耗时15s渲染完成,spp256耗时3min多钟,真的很快了:

在这里插入图片描述

4.6. 提高部分:微表面材质

主要的参考资料,强烈建议看完,结合它的代码一起看会搞懂其中的原理:

  1. 微平面模型-理论

在Material.cpp修改enum数组添加MICROFACET材质。

主要使用公式:
在这里插入图片描述
eval函数就是编写fr的(上面括号里的项,一项是散射项,一项是镜面反射项):

在这里插入图片描述

我们分别求上面的D、F、G

D项选择的是( α \alpha α是粗糙度, h \mathbf{h} h是半程向量, h = ( v + l ) / ∣ ∣ v + l ∣ ∣ \mathbf{h}=(\mathbf{v}+\mathbf{l})/||\mathbf{v}+\mathbf{l}|| h=(v+l)/∣∣v+l∣∣ l \mathbf{l} l v \mathbf{v} v是物体上点到光源的单位向量(对应-wi,wi和wo其实作业有点混乱,不过理解含义就好)和物体点到相机的单位向量(对应wo), n \mathbf n n是宏观法向量):
在这里插入图片描述

对应的D函数写在eval函数里:

Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){switch(m_type){case DIFFUSE:{...}case MICROFACET:{...auto DistFunc = [&]() -> float{float alpha2 = alpha * alpha;Vector3f h = (-wi + wo).normalized();float dotNH = std::max(dotProduct(N, h), 0.0f);float dnorm = M_PI * std::pow((dotNH * dotNH * (alpha2 - 1) + 1), 2);return alpha2 / dnorm;};...float D = DistFunc();  ...}}
}

G项选择的是( k k k和上面的 α \alpha α有关):

在这里插入图片描述
对应的G函数写在eval函数里:

Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){switch(m_type){case DIFFUSE:{...}case MICROFACET:{...auto GeoFunc = [&alpha](const Vector3f& w, const Vector3f& n) -> float{float k = (alpha+ 1.0) * (alpha + 1.0) / 8;float dotNw = dotProduct(n, w);return dotNw / (dotNw * (1 - k) + k);};...float G = GeoFunc(N, -wi) * GeoFunc(N, wo);...}}
}

F项对应的是菲涅尔项(参考了计算机图形学十二:Whitted-Style光线追踪原理详解及实现细节):

在这里插入图片描述

上面的菲涅尔项已经帮我们写好了:

void fresnel(const Vector3f &I, const Vector3f &N, const float &ior, float &kr) const{float cosi = clamp(-1, 1, dotProduct(I, N));float etai = 1, etat = ior;if (cosi > 0) {  std::swap(etai, etat); }// Compute sini using Snell's lawfloat sint = etai / etat * sqrtf(std::max(0.f, 1 - cosi * cosi));// Total internal reflectionif (sint >= 1) {kr = 1;}else {float cost = sqrtf(std::max(0.f, 1 - sint * sint));cosi = fabsf(cosi);float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));kr = (Rs * Rs + Rp * Rp) / 2;}// As a consequence of the conservation of energy, transmittance is given by:// kt = 1 - kr;}
Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){switch(m_type){case DIFFUSE:{...}case MICROFACET:{float F;fresnel(wi, N, 1.9, F);//注意这里传入的是wi,不是-wi,传入的是相机到视点的向量...}}
}

OK我们可以开始编写了

枚举量里加入微表面材质:

enum MaterialType { DIFFUSE, MICROFACET};

根据提示:

在这里插入图片描述

sample和pdf可以沿用,都是加一行case的事:

sample函数:

Vector3f Material::sample(const Vector3f &wi, const Vector3f &N){switch(m_type){case DIFFUSE:case MICROFACET:{// uniform sample on the hemispherefloat x_1 = get_random_float(), x_2 = get_random_float();float z = std::fabs(1.0f - 2.0f * x_1);float r = std::sqrt(1.0f - z * z), phi = 2 * M_PI * x_2;Vector3f localRay(r*std::cos(phi), r*std::sin(phi), z);return toWorld(localRay, N);break;}}
}

pdf函数:

float Material::pdf(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){switch(m_type){case DIFFUSE:case MICROFACET:{// uniform sample probability 1 / (2 * PI)if (dotProduct(wo, N) > 0.0f)return 0.5f / M_PI;elsereturn 0.0f;break;}}
}

eval函数我们根据公式写代码就行了:

在这里插入图片描述

Vector3f Material::eval(const Vector3f &wi, const Vector3f &wo, const Vector3f &N){switch(m_type){case DIFFUSE:{// calculate the contribution of diffuse   modelfloat cosalpha = dotProduct(N, wo);if (cosalpha > 0.0f) {Vector3f diffuse = Kd / M_PI;return diffuse;}elsereturn Vector3f(0.0f);break;}case MICROFACET:{float cosalpha = dotProduct(N, wo);if (cosalpha > 0.0f) {Vector3f diffuse = Kd / M_PI;Vector3f spectacular;float alpha = 0.9;//粗糙度可以修改auto DistFunc = [&]() -> float{float alpha2 = alpha * alpha;Vector3f h = (-wi + wo).normalized();float dotNH = std::max(dotProduct(N, h), 0.0f);float dnorm = M_PI * std::pow((dotNH * dotNH * (alpha2 - 1) + 1), 2);return alpha2 / dnorm;};auto GeoFunc = [&alpha](const Vector3f& w, const Vector3f& n) -> float{float k = (alpha+ 1.0) * (alpha + 1.0) / 8;float dotNw = dotProduct(n, w);return dotNw / (dotNw * (1 - k) + k);};float D = DistFunc();float G = GeoFunc(N, -wi) * GeoFunc(N, wo);float F;fresnel(wi, N, ior, F);spectacular = D * F * G / (4 * std::max(dotProduct(wo, N) * dotProduct(-wi, N), 0.001f));return spectacular + ( Vector3f(1.0f) - F) * diffuse;}elsereturn Vector3f(0.0);}}
}

在main函数我们加个球:

	Material* light = new Material(DIFFUSE, (8.0f * Vector3f(0.747f+0.058f, 0.747f+0.258f, 0.747f) + 15.6f * Vector3f(0.740f+0.287f,0.740f+0.160f,0.740f) + 18.4f *Vector3f(0.737f+0.642f,0.737f+0.159f,0.737f)));light->Kd = Vector3f(0.65f);Material* ball = new Material(MICROFACET, Vector3f(0.0f));ball->ior = 1.6; //设置折射率ball->Kd = Vector3f(0.3, 0.3, 0.25);Sphere sphere(Vector3f(150, 100, 300), 100, ball);MeshTriangle floor("../models/cornellbox/floor.obj", white);/* MeshTriangle shortbox("../models/cornellbox/shortbox.obj", white);MeshTriangle tallbox("../models/cornellbox/tallbox.obj", white); */MeshTriangle left("../models/cornellbox/left.obj", red);MeshTriangle right("../models/cornellbox/right.obj", green);MeshTriangle light_("../models/cornellbox/light.obj", light);scene.Add(&floor);/* scene.Add(&shortbox);scene.Add(&tallbox); */scene.Add(&sphere);scene.Add(&left);scene.Add(&right);scene.Add(&light_);scene.buildBVH();

另外看到有博客说要修改球与光线相交的判断,否则会有很多噪点。

Intersection getIntersection(Ray ray){Intersection result;result.happened = false;Vector3f L = ray.origin - center;float a = dotProduct(ray.direction, ray.direction);float b = 2 * dotProduct(ray.direction, L);float c = dotProduct(L, L) - radius2;float t0, t1;if (!solveQuadratic(a, b, c, t0, t1)) return result;if (t0 < 0) t0 = t1;if (t0 < 0) return result;if (t0 > 0.5){result.happened=true;result.coords = Vector3f(ray.origin + ray.direction * t0);result.normal = normalize(Vector3f(result.coords - center));result.m = this->m;result.obj = this;result.distance = t0;}return result;}

α = 0.2 , i o r = 1.6 \alpha=0.2,ior=1.6 α=0.2ior=1.6

在这里插入图片描述

把折射率调高一点,粗糙度设置低一些:

α = 0.05 , i o r = 1.9 \alpha=0.05,ior=1.9 α=0.05ior=1.9
在这里插入图片描述
把折射率再调高一点

α = 0.05 , i o r = 4 \alpha=0.05,ior=4 α=0.05ior=4

在这里插入图片描述

α = 0.9 , i o r = 1.6 \alpha=0.9,ior=1.6 α=0.9ior=1.6,已经很接近使用diffuse材质了

在这里插入图片描述

而使用diffuse材质没有镜面反射:

在这里插入图片描述

这篇文章还做了镜面材质【GAMES101】作业7(提高)路径追踪 多线程、Microfacet(全镜面反射)、抗锯齿,有兴趣可以看看,镜面材质只考虑反射项,没有散射项,全镜面反射只有一个方向的光线能被眼睛接收,所以pdf就设置为1,frenel函数里要去掉散射项的影响,直接光照设置为0(防止过曝),间接光照把非发光物体的判断条件给去掉,接收所有物体入射的光。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/237804.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用swift创建第一个ios程序

一、安装xcode 先到app store中下载一个Xcode app 二、创建项目 1、项目设定 创建ios app 2、工程结构 三、修改代码实现按键联动 四、运行测试

AI大模型引领未来智慧科研暨ChatGPT在地学、GIS、气象、农业、生态、环境等领域中的高级应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

YOLOv8 Ultralytics:使用Ultralytics框架进行FastSAM图像分割

YOLOv8 Ultralytics&#xff1a;使用Ultralytics框架进行FastSAM图像分割 前言相关介绍前提条件实验环境安装环境项目地址LinuxWindows 使用Ultralytics框架进行FastSAM图像分割参考文献 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容…

SpringCloud:Ribbon

文章目录 Ribbon快速入门Ribbon负载均衡算法常见的负载均衡算法更改算法规则修改配置 饥饿加载 Ribbon ribbon是一个客户端负载均衡器&#xff0c;会从注册中心拉取可用服务&#xff0c;当客户端需要获取服务请求时&#xff0c;ribbon能够解析服务地址并实现负载均衡 快速入门 …

伴鱼实时数仓建设案例

伴鱼实时数仓建设案例 文章目录 伴鱼实时数仓建设案例伴鱼实时作业应用场景伴鱼实时数仓的建设体系DWD 层复杂场景数据处理方案1. 数据的去重2. join场景两条实时数据流相关联对于关联历史数据 3. 从数据形态观查join DWS 数据层数据处理方案未来与展望 随着伴鱼业务的快速发展…

mysql进阶 - 存储过程

目录 1. 用途&#xff1a; 2. 相关语法 2.1 创建 2.1.1 语法 2.1.2 示例 2.2 查看存储过程 2.3 调用 2.4 修改存储过程 2.5 删除存储过程 1. 用途&#xff1a; 存储过程广泛存在于一些遗留系统&#xff0c;可以减少代码的编写。而近些年&#xff0c;存储过程很少再用…

【Maven】001-Maven 概述

【Maven】001-Maven 概述 文章目录 【Maven】001-Maven 概述一、Maven 概述1、为什么学习 MavenMaven 作为依赖管理工具Maven 作为构建工具其它 2、Maven 介绍3、Maven 软件工作模型图 一、Maven 概述 1、为什么学习 Maven Maven 作为依赖管理工具 依赖管理&#xff1a; Mave…

【Docker】Docker安装入门教程及基本使用

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Docker实战》。&#x1f3af;&#x1f3af; &…

WEB前端人机导论实验-实训3超链接与多媒体文件应用

1.项目1 设计简易灯箱画廊 A.题目要求&#xff1a; 编程实现简易灯箱画廊&#xff0c;鼠标单击任一个图像超链接&#xff0c;在底部浮动框架中显示大图像&#xff0c;效果如下的页面。 B.思路: &#xff08;1&#xff09;CSS样式&#xff1a; a.在样式中对body元素进行居中…

【复现】大华 DSS 数字监控系统 SQL 注入漏洞_18

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 大华DSS是大华的大型监控管理应用平台&#xff0c;支持几乎所有涉及监控等方面的操作&#xff0c;支持多级跨平台联网等操作。 可…

GPT-4与DALL·E 3:跨界融合,开启绘画与文本的新纪元

在人工智能的发展浪潮中&#xff0c;MidTool&#xff08;https://www.aimidtool.com/&#xff09;的GPT-4与DALLE 3的集成代表了一个跨越式的进步。这一集成不仅仅是技术的结合&#xff0c;更是艺术与文字的完美融合&#xff0c;它为创意产业带来了革命性的变革。本文将探讨GPT…

高校站群内容管理系统开发语言各有优势

站群管理系统开发可以选择多种编程语言&#xff0c;具体选择哪种语言最好需要考虑多个因素&#xff0c;包括开发团队的技术栈、项目需求、性能要求、安全性等。下面列举一些常见的编程语言及其适用场景&#xff1a; PHP&#xff1a;PHP是一种广泛使用的服务器端脚本语言&#…

PLC、工业设备如何远程访问?贝锐蒲公英云智慧组网实现数据互通

在工业4.0时代&#xff0c;工业数字化的核心在于数据的互联互通&#xff0c;而在整个工业数字化、智能化的过程中&#xff0c;往往面临数据采集困难、设备运行情况难以知晓、部署管理难度大、后期维护成本高等问题。 显然&#xff0c;PLC设备在整个工业数字化进程中扮演的角色…

算法通关村番外篇-跳表

大家好我是苏麟 , 今天来聊聊调表 . 跳表很少很少实现所以我们只了解就可以了 . 跳表 链表在查找元素的时候&#xff0c;因为需要逐一查找&#xff0c;所以查询效率非常低&#xff0c;时间复杂度是O(N)&#xff0c;于是就出现了跳表。跳表是在链表基础上改进过来的&#xff0…

启动redis出现Creating Server TCP listening socket 127.0.0.1:6379: bind: No error异常

1.进入redis安装目录&#xff0c;地址栏输入cmd 2.输入命令 redis-server.exe redis.windows.conf redis启动失败 解决&#xff0c;输入命令 #第一步 redis-cli.exe#第二步 shutdown#第三步 exit第四步 redis-server.exe redis.windows.conf 显示以下图标即成功

ajax+axios——统一设置请求头参数——添加请求头入参——基础积累

最近在写后台管理系统&#xff08;我怎么一直都只写管理系统啊啊啊啊啊啊啊&#xff09;&#xff0c;遇到一个需求&#xff0c;就是要在原有系统的基础上&#xff0c;添加一个仓库的切换&#xff0c;并且需要把选中仓库对应的id以请求头参数的形式传递到每一个接口当中。。。 …

奇异值分解在图形压缩中的应用

奇异值分解在图形压缩中的应用 在研究奇异值分解的工程应用之前&#xff0c;我们得明白什么是奇异值&#xff1f;什么是奇异向量&#xff1f; 奇异值与奇异向量 概念&#xff1a;奇异值描述了矩阵在一组特定向量上的行为&#xff0c;奇异向量描述了其最大的作用方向。 奇异值…

Android14之刷机模式总结(一百七十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

熊猫电竞赏金电竞系统源码 APP+H5双端 附搭建教程 支持运营级搭建

简介: 熊猫电竞赏金电竞系统源码 APP+H5双端 附搭建教程 支持运营级搭建 可搭建!运营级!首次公开! 赏金赛源码,用户通过平台打比赛,赢了获得奖金奖励, 金币赛、赏金赛、vip赛等种赛事 可开王者荣耀、和平精英比赛 支持1v1、单排、双排组、战队排等多种比赛模式 …

isis实验

根据要求制作大概&#xff1a; 使用isis配置路由器&#xff1a; 配置好物理接口地址后配置isis 为实现r1访问r5的环回走r6,需要在r6上制作路由泄露&#xff1a; 在r5上产生r1的路由明细&#xff1a; 全网可达&#xff1a;