哪家公司做的网站好,坑梓网站建设哪家好,wordpress 美图主题,网站建设捌金手指下拉二七目录闲言碎语最终全部效果展示#xff08;均为10241024512ssp#xff09;课程总结与理解#xff08;Path Tracing#xff09;框架梳理任务一#xff1a;迁移相关代码任务二#xff1a;实现path tracing任务三#xff1a;多线程加速#xff08;包括其他加速的小trick均为1024×1024×512ssp课程总结与理解Path Tracing框架梳理任务一迁移相关代码任务二实现path tracing任务三多线程加速包括其他加速的小trick1.随机数构造器优化2.多线程加速3.debug改成release版本任务四微表面模型1. 微表面材质定义2. 微表面分布的表达2.1. D(h)法线分布函数**Normal Distribution FuctionNDF**2.1. G(i,o,h)几何遮挡函数**Shadowing and Making term**3.光在每个微表面散射的BRDF4.Torrance-Sparrow Model5.代码实现任务五完美反射模型看到顺便实现了作业没要求代码实现可能遇到的问题1.光源融入到整个天花板没有出现黑色2.箱子背阴处有很多黑色噪点3.有白色噪点ssp调高仍然有理论思考1.偏移为什么要分两种情况这么偏移2.为什么path tracing有软阴影3.把witted style和path tracing的结果对比一下两者的实现的不同点有哪些4.为什么程序中渲染方程的入射radiance是光源的emit是一个常量5.为什么说光栅化比光追快个人感悟参考资料闲言碎语
前言从这篇文章开始转知乎了感觉知乎的社区氛围更好emmm哈哈哈主要还是想接触一下知乎的图形学大佬们。知乎账号链接https://www.zhihu.com/people/xuu-27-24
emmm距离完成作业6已经过去了一个月多的时间我承认中间有段时间在摆烂怎么说呢感觉作业7真的知识体系非常庞大虽然实现的代码量不多但是里面真的很多细节很多trick对我这种小菜鸡感觉还是有挺大难度的。。。再加上感觉自己挺钻牛角尖的所以搞了挺久的也最终算是完成了作业7的全部要求害希望后面本科毕设不要太赶吧。
提个建议如果遇到实在解决不了的问题可以上games论坛看看里面有很多共性的问题上面的问题我几乎都遇到了。。。
最终全部效果展示均为1024×1024×512ssp
基础path tracing实现作业基本要求 微表面材质 完美镜面反射材质
课程总结与理解Path Tracing
课程方面主要是先讲辐射度量学然后推导出渲染方程之后介绍用来求解积分的蒙特卡洛方法最后讲如何在程序中求解渲染方程计算颜色包括很多的trick细节比如对光源进行直接采样提高采样效率俄罗斯轮盘赌解决无限递归等等从而实现path tracing。
path tracing流程整体的渲染流程实际上更多的是在Whitted-Style上的改进前期添加物体构建包围盒这些都是一样的。不一样的地方在于path tracing在最终着色的时候本质上是求解一个渲染方程需要做大量的采样近似积分结果里面小trick非常多。而Whitted-Style并没有做采样求积分就是简单的光线反射和折射无法像path tracing一样考虑全局光照。 总体上来说就是每个像素点发出多条光线每条光线会通过之前构建的包围盒找到与场景中物体的第一个交点然后根据渲染方程计算该交点的颜色并返回最后将全部发射的光线的颜色求平均就得到该像素的颜色。
其实这个渲染方程本身的原理还是挺复杂的可以研究的很深入尤其辐射度量学那块课程本身其实讲的并不是很细如果想细致了解的话可以去翻虎书虎书可以说对辐射度量学进行了深度的解刨直接从光子开始讲。。。
框架梳理
和作业6使用的框架其实差不多可以参考我之前写的https://blog.csdn.net/Xuuuuuuuuuuu/article/details/128556319
作业7的框架实际上主要就是castRay函数有所不同因为path tracing的最终实现是在castRay中实现。
任务一迁移相关代码
之前写的在这里https://blog.csdn.net/Xuuuuuuuuuuu/article/details/128556319把相关的代码贴进去就行。其中要注意的一个点IntersectP中的等于号一定要去掉要不然之后会出现部分物体不可见的情况去掉的位置在这里见下图 为什么这里要去掉等于号之前闫老师说图形学编程中很少考虑等于号的情况几乎是可以忽略的。这是因为这次的场景比较特殊以右边的绿色墙壁为例实际上它的Boundbox就是一个与某一轴平行长方形不是长方体不信的话可以printBoundbox的那两个点出来看看所以光线和这个Boundbox相交检测的时候算出来的t_enter和t_exit是相等的。此时如果相等时仍判断为不相交就会出错此时部分物体不可见如下图 任务二实现path tracing
代码量也不是很大主要就是跟着作业中给的伪代码照着敲但是想呈现出最终的结果有很多小细节要注意其中这里面各个向量的方向问题就很容易搞错一定要仔细琢磨清楚。因为里面要修的细节太多了我这边直接给出任务二完整的代码个人建议不要完全照抄可以自己先跟着作业提供的伪代码敲一下遇到问题再去自己想办法找资料解决在最终得到想要的结果后你会发现这个过程非常值得。具体的很多细节问题放到后文讨论。很多细节的问题建议直接看代码可以理解的更清楚。
Vector3f Scene::castRay(const Ray ray, int depth) const
{// TO DO Implement Path Tracing Algorithm hereVector3f hitColor this-backgroundColor;Intersection shade_point_inter Scene::intersect(ray);if (shade_point_inter.happened){Vector3f p shade_point_inter.coords;Vector3f wo ray.direction;Vector3f N shade_point_inter.normal;Vector3f L_dir(0), L_indir(0);//sampleLight(inter,pdf_light)Intersection light_point_inter;float pdf_light;sampleLight(light_point_inter, pdf_light);//Get x,ws,NN,emit from interVector3f x light_point_inter.coords;Vector3f ws normalize(x-p);Vector3f NN light_point_inter.normal;Vector3f emit light_point_inter.emit;float distance_pTox (x - p).norm();//Shoot a ray from p to xVector3f p_deviation (dotProduct(ray.direction, N) 0) ?p N * EPSILON :p - N * EPSILON ;Ray ray_pTox(p_deviation, ws);//If the ray is not blocked in the middleffIntersection blocked_point_inter Scene::intersect(ray_pTox);if (abs(distance_pTox - blocked_point_inter.distance 0.01 )){L_dir emit * shade_point_inter.m-eval(wo, ws, N) * dotProduct(ws, N) * dotProduct(-ws, NN) / (distance_pTox * distance_pTox * pdf_light);}//Test Russian Roulette with probability RussianRouolettefloat ksi get_random_float();if (ksi RussianRoulette){//wisample(wo,N)Vector3f wi normalize(shade_point_inter.m-sample(wo, N));//Trace a ray r(p,wi)Ray ray_pTowi(p_deviation, wi);//If ray r hit a non-emitting object at qIntersection bounce_point_inter Scene::intersect(ray_pTowi);if (bounce_point_inter.happened !bounce_point_inter.m-hasEmission()){float pdf shade_point_inter.m-pdf(wo, wi, N);if(pdf EPSILON)L_indir castRay(ray_pTowi, depth 1) * shade_point_inter.m-eval(wo, wi, N) * dotProduct(wi, N) / (pdf *RussianRoulette);}}hitColor shade_point_inter.m-getEmission() L_dir L_indir;}return hitColor;
}应该能得到下图所示的结果1024×1024×512ssp
任务三多线程加速包括其他加速的小trick
我觉得加速是渲染中一个非常重要的点也是我个人比较感兴趣的点。tmd没有加速之前代码的运行速度简直惨不忍睹。。。一开始1024×1024×512ssp的图渲染了七八个小时才渲染了百分之20渲染到百分之20我就直接放弃了。。。最终经过不懈努力总算把速度优化到了40分钟可以说快了几乎60-80倍。渲染速度的提高可以说极大地帮助了后面的调试
按照下面三步逐步进行优化我渲染一张512×512×8ssp的图的速度从13分钟提到了10秒钟。
1.随机数构造器优化
使用C的性能分析器https://blog.csdn.net/u011942101/article/details/123656944
不难发现主要是求交和采样两部分花了很多的时间求交这部分本来就耗时很长里面很多递归。但是为啥采样这部分要这么长时间 最后发现是这个函数特别耗时。一分析是因为构建开销过大实际上把里面三个变量都定义成static就行只会初始化一次避免重复构建没必要重新构建。经测试改成static后512×512×8ssp的渲染速度从13分钟提到了5分钟。 参考https://games-cn.org/forums/topic/zuoyeqidexingnengpingjingshisuijishushengcheng/
2.多线程加速
使用多线程加速之前问自己一个问题为什么这里可以使用多线程加速因为这里满足两个条件1.这个场景下程序可以写成多线程2.电脑多核。
实际上实现多线程加速不是很难因为整个场景太适合写成多线程加速了。。。就每个线程分别独立一定数量计算像素颜色就行。原则上就是要最大化每个线程的计算量对整个frame计算使用多线程。 具体使用过的是C里面的thread进行多线程代码如下
// 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::vectorVector3f 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 512;//16int thread_num 8;//我的电脑有8核所以开8个线程。注屏幕的高度一定要是线程数的倍数int thread_height scene.height / thread_num;std::vectorstd::thread threads(thread_num);std::cout SPP: spp \n;//多线程实现std::mutex mtx;float process0;float Reciprocal_Scene_height1.f/ (float)scene.height;auto castRay [](int thread_index) {int height thread_height * (thread_index 1);for (uint32_t j height - thread_height; j 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;//eye的位置对结果有影响Vector3f dir normalize(Vector3f(-x, y, 1));for (int k 0; k spp; k){framebuffer[j*scene.widthi] scene.castRay(Ray(eye_pos, dir), 0) / spp; }}mtx.lock();process process Reciprocal_Scene_height;UpdateProgress(process);mtx.unlock();}};for (int k 0; k thread_num; k){threads[k] std::thread(castRay,k);}for (int k 0; k thread_num; k){threads[k].join();}UpdateProgress(1.f);// save framebuffer to fileFILE* 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);
}
512×512×8ssp的渲染速度从5分钟提到了1分钟 真的很担心cpu被烧坏。。。这种情况下说明多线程编程成功CPU被疯狂地调用。 3.debug改成release版本
无心之举512×512×8ssp的渲染速度从1分钟提到了10秒钟。。。 只能说release比debug版本快了真的不止一点半点。
任务四微表面模型
101讲的不是很细因此我就花了不少时间仔细地看了下202和一些其他资料。
参考1https://zhuanlan.zhihu.com/p/434964126写得贼nb推导地很深入
参考2https://zhuanlan.zhihu.com/p/152226698这个更加全面一点涉及的更广但1更深
本质上微表面材质是描述高光项真正的brdf是由漫反射项高光项组成的如下参考2 各自的比例是由菲涅尔项计算的而菲涅尔项是折射和反射的比例这里漫反射等价于折射因为漫反射从微观角度是折射进入物体然后多次反弹弹出表面产生漫反射现象。
说白了就是一部分算折射然后反弹出来的能量一部分算反射出来的能量。 1. 微表面材质定义
说白了就是把一个物体表面看作有多个微表面组成的表面从而展示更加真实的材质细节。
下图的虚线就是物体表面而对其着色的时候微表面模型会将物体表面近似成下图实线尖尖样子进行着色具体就是通过定义微表面模型的prdf实现的。
思想和凹凸贴图那边有点想实际上没有改变物体的几何模型但是通过改变了prdf使物体有了多个微表面组成的感觉。所以有了这句话远处看是材质和外观近处看是几何。 以太空图为例远处看一个物体的时候看不到很微小的东西看到的是一个全局效果。 ok上面主要大致讲微表面模型大概是个什么东西下面就是具体深入的理解
微表面模型主要分成两个部分也就是说设计一个微表面模型就必须要考虑到这两部分
①微表面分布的表达全局比如法线分布、几何遮挡等
②光在每个微表面散射的BRDF细节单个微表面的材质即prdf可以是完美镜面反射又可以是理想漫反射
接下来依次介绍以上两部分然后再介绍最常见的Torrance-Sparrow模型。
2. 微表面分布的表达
包括D(h)和G(i,o,h)两块即法线分布和几何遮挡。
2.1. D(h)法线分布函数Normal Distribution FuctionNDF
不同的法线分布会产生完全不一样的材质。 D(h)本质上是面积微元dA上的法向为h的全部微表面的面积和与面积微元dA的比例。
注dA是宏观表面上的面积微元。
不难发现D(h)实际上是针对物体宏观表面上的一个面积微元的而不是整个物体。 D(h)的推导公式。 ①beckmann NDF
类似高斯分布函数里面就是有那两个量α应该是超参数控制整个分布的宽瘦θ就是h代进去计算得到的。为什么这么去设计这个函数是有它的意义在里面。 ②GGX NDF
相较于Beckmann NDF有了个长尾巴意味着会有更好的过渡。 2.1. G(i,o,h)几何遮挡函数Shadowing and Making term
描述微表面之间互相遮挡的几何现象的数学模型本质上是法向为h的微表面在w方向上可见的比例因为有部分被遮挡了见下面第一张图。
遮挡项因为光线或者视线非常平第二张图的球边界此时角度称为grazing anglef的分母会非常小因此f就会非常大边界就会非常亮此时就要引入遮挡项解决这个问题进行衰减因为这种情况下是最容易出现shadowing-masking现象的。
具体推导可以看上述的链接讲的非常清楚 3.光在每个微表面散射的BRDF
这里又可以有很多文章可以做可以把每个微表面看作理想镜面完美反射也可以看成理想玻璃表面又有折射又有反射还可以看成漫反射表面。
结合上述两个微表面的分布项再定义每个微表面的材质就能推导出相关的微表面模型。
下文以Torrance-Sparrow Model为例。
4.Torrance-Sparrow Model
把每个微表面看成理想镜面。
推导过程看不懂我太菜了就这样吧该章节一开始的链接里有。 5.代码实现
实现的是上述的Torrance-Sparrow ModelF项是代码中已经写好的菲涅尔项D项和G项选择GGX如下图所示 其中各参数的含义可以查看原文https://zhuanlan.zhihu.com/p/434964126或者可以直接看代码应该也能够理解。代码实现方面先在定义里面添加Microfacet材质如下
enum MaterialType { DIFFUSE, MICROFACET}然后在main函数中添加物体并对其赋予Microfacet材质如下 Material* microfacet new Material(MICROFACET, Vector3f(0.0f));microfacet-Ks Vector3f(0.45, 0.45, 0.45);microfacet-Kd Vector3f(0.3, 0.3, 0.25);microfacet-ior 12.85;Sphere sphere1(Vector3f(150, 100, 200), 100, microfacet);scene.Add(sphere1);sample和pdf直接照抄原本的就行。最后修改eval在其中实现Torrance-Sparrow Model模型即可如下
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) {// calculate the contribution of Microfacet modelfloat F, G, D;fresnel(wi, N, ior, F);float Roughness 1;//超参数控制粗糙度auto G_function [](const float Roughness, const Vector3f wi, const Vector3f wo, const Vector3f N){float A_wi, A_wo;A_wi (-1 sqrt(1 Roughness * Roughness * pow(tan(acos(dotProduct(wi, N))), 2))) / 2;A_wo (-1 sqrt(1 Roughness * Roughness * pow(tan(acos(dotProduct(wo, N))), 2))) / 2;float divisor (1 A_wi A_wo);if (divisor 0.001)return 1.f;elsereturn 1.0f / divisor;};G G_function(Roughness, -wi, wo, N);auto D_function [](const float Roughness, const Vector3f h, const Vector3f N){float cos_sita dotProduct(h, N);float divisor (M_PI * pow(1.0 cos_sita * cos_sita * (Roughness * Roughness - 1), 2));if (divisor 0.001)return 1.f;else return (Roughness * Roughness) / divisor;};Vector3f h normalize(-wi wo);D D_function(Roughness, h, N);// energy balanceVector3f diffuse (Vector3f(1.0f) - F) * Kd / M_PI;Vector3f specular;float divisor ((4 * (dotProduct(N, -wi)) * (dotProduct(N, wo))));if (divisor 0.001)specular Vector3f(1);elsespecular F *G * D / divisor;//std::cout F:F \n;//std::cout diffuse:diffuse\n;//std::cout specular: specular \n;return diffusespecular;}elsereturn Vector3f(0.0f);break;}}
}
最终效果均为1024×1024×512ssp 如果黑色噪点很多尝试修改一下sphere的求交判断阈值有可能是精度的问题如下 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;}如果白色噪点很多在castRay函数的最后限制一下hitColor的范围避免出现特别大的值影响采样质量。如下 hitColor shade_point_inter.m-getEmission()L_dir L_indir;hitColor.x (clamp(0, 1, hitColor.x));hitColor.y (clamp(0, 1, hitColor.y));hitColor.z (clamp(0, 1, hitColor.z));任务五完美反射模型看到顺便实现了作业没要求
核心是重要性采样参考这篇文章实现的https://blog.csdn.net/ycrsw/article/details/124408789各种细节这里面已经讲得非常清楚了因此就不再赘述直接给出代码和Microfacet材质的实现流程差不多首先添加材质如下
enum MaterialType { DIFFUSE, MICROFACET,MIRROR};main函数中添加物体赋予材质 Material* mirror new Material(MIRROR, Vector3f(0.0f));mirror-Ks Vector3f(0.45, 0.45, 0.45);mirror-Kd Vector3f(0.3, 0.3, 0.25);mirror-ior 12.85;MeshTriangle floor(../models/cornellbox/floor.obj, white);MeshTriangle shortbox(../models/cornellbox/shortbox.obj, white);MeshTriangle tallbox(../models/cornellbox/tallbox.obj, mirror);MeshTriangle left(../models/cornellbox/left.obj, red);MeshTriangle right(../models/cornellbox/right.obj, green);MeshTriangle light_(../models/cornellbox/light.obj, light);最后修改eval在eval中实现完美镜面反射Mirror如下
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 MIRROR:{float cosalpha dotProduct(N, wo);if (cosalpha 0.0f) {float divisor cosalpha;if (divisor 0.001) return 0;Vector3f mirror 1 / divisor;float F;fresnel(wi, N, ior, F);return F * mirror;}elsereturn Vector3f(0.0f);break;}case MICROFACET:{float cosalpha dotProduct(N, wo);if (cosalpha 0.0f) {// calculate the contribution of Microfacet modelfloat F, G, D;fresnel(wi, N, ior, F);float Roughness 1;//超参数控制粗糙度auto G_function [](const float Roughness, const Vector3f wi, const Vector3f wo, const Vector3f N){float A_wi, A_wo;A_wi (-1 sqrt(1 Roughness * Roughness * pow(tan(acos(dotProduct(wi, N))), 2))) / 2;A_wo (-1 sqrt(1 Roughness * Roughness * pow(tan(acos(dotProduct(wo, N))), 2))) / 2;float divisor (1 A_wi A_wo);if (divisor 0.001)return 1.f;elsereturn 1.0f / divisor;};G G_function(Roughness, -wi, wo, N);auto D_function [](const float Roughness, const Vector3f h, const Vector3f N){float cos_sita dotProduct(h, N);float divisor (M_PI * pow(1.0 cos_sita * cos_sita * (Roughness * Roughness - 1), 2));if (divisor 0.001)return 1.f;else return (Roughness * Roughness) / divisor;};Vector3f h normalize(-wi wo);D D_function(Roughness, h, N);// energy balanceVector3f diffuse (Vector3f(1.0f) - F) * Kd / M_PI;Vector3f specular;float divisor ((4 * (dotProduct(N, -wi)) * (dotProduct(N, wo))));if (divisor 0.001)specular Vector3f(1);elsespecular F *G * D / divisor;//std::cout F:F \n;//std::cout diffuse:diffuse\n;//std::cout specular: specular \n;return diffusespecular;}elsereturn Vector3f(0.0f);break;}}
}哦对由于要重要性采样采样方向只需要采完美反射的方向即可所以采样的那两个函数也需要改变不能像Microfacet一样不变。如下
Vector3f Material::sample(const Vector3f wi, const Vector3f N){switch(m_type){case DIFFUSE:{// 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;}case MIRROR:{Vector3f localRay reflect(wi, N);return localRay;break;}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;}}
}float Material::pdf(const Vector3f wi, const Vector3f wo, const Vector3f N){switch(m_type){case DIFFUSE:{// uniform sample probability 1 / (2 * PI)if (dotProduct(wo, N) 0.0f)return 0.5f / M_PI;elsereturn 0.0f;break;}case MIRROR:{if (dotProduct(wo, N) 0.0f)return 1.0f;elsereturn 0.0f;break;}case MICROFACET:{// uniform sample probability 1 / (2 * PI)if (dotProduct(wo, N) 0.0f)return 0.5f / M_PI;elsereturn 0.0f;break;}}
}哦对了最后的最后因为要做重要性采样castRay函数也要进行修改否则会有过曝现象mirror材质不再需要直接光L_dir采样了就直接算L_indir就行而且在算L_indir的时候记得那个把里面的避免光源采样的判断条件去了。如下
Vector3f Scene::castRay(const Ray ray, int depth) const
{// TO DO Implement Path Tracing Algorithm hereVector3f hitColor this-backgroundColor;Intersection shade_point_inter Scene::intersect(ray);if (shade_point_inter.happened){Vector3f p shade_point_inter.coords;Vector3f wo ray.direction;Vector3f N shade_point_inter.normal;Vector3f L_dir(0), L_indir(0);Vector3f p_deviation (dotProduct(ray.direction, N) 0) ?p N * EPSILON :p - N * EPSILON;switch (shade_point_inter.m-getType()){case MIRROR:{//Test Russian Roulette with probability RussianRouolettefloat ksi get_random_float();if (ksi RussianRoulette){//wisample(wo,N)Vector3f wi normalize(shade_point_inter.m-sample(wo, N));//Trace a ray r(p,wi)Ray ray_pTowi(p_deviation, wi);//If ray r hit a object at qIntersection bounce_point_inter Scene::intersect(ray_pTowi);if (bounce_point_inter.happened){float pdf shade_point_inter.m-pdf(wo, wi, N);if (pdf EPSILON)L_indir castRay(ray_pTowi, depth 1) * shade_point_inter.m-eval(wo, wi, N) * dotProduct(wi, N) / (pdf * RussianRoulette);}}break;}default:{//sampleLight(inter,pdf_light)Intersection light_point_inter;float pdf_light;sampleLight(light_point_inter, pdf_light);//Get x,ws,NN,emit from interVector3f x light_point_inter.coords;Vector3f ws normalize(x - p);Vector3f NN light_point_inter.normal;Vector3f emit light_point_inter.emit;float distance_pTox (x - p).norm();//Shoot a ray from p to xRay ray_pTox(p_deviation, ws);//If the ray is not blocked in the middleffIntersection blocked_point_inter Scene::intersect(ray_pTox);if (abs(distance_pTox - blocked_point_inter.distance 0.01)){L_dir emit * shade_point_inter.m-eval(wo, ws, N) * dotProduct(ws, N) * dotProduct(-ws, NN) / (distance_pTox * distance_pTox * pdf_light);}//Test Russian Roulette with probability RussianRouolettefloat ksi get_random_float();if (ksi RussianRoulette){//wisample(wo,N)Vector3f wi normalize(shade_point_inter.m-sample(wo, N));//Trace a ray r(p,wi)Ray ray_pTowi(p_deviation, wi);//If ray r hit a non-emitting object at qIntersection bounce_point_inter Scene::intersect(ray_pTowi);if (bounce_point_inter.happened !bounce_point_inter.m-hasEmission()){float pdf shade_point_inter.m-pdf(wo, wi, N);if (pdf EPSILON)L_indir castRay(ray_pTowi, depth 1) * shade_point_inter.m-eval(wo, wi, N) * dotProduct(wi, N) / (pdf * RussianRoulette);}}break;}}hitColor shade_point_inter.m-getEmission()L_dir L_indir;hitColor.x (clamp(0, 1, hitColor.x));hitColor.y (clamp(0, 1, hitColor.y));hitColor.z (clamp(0, 1, hitColor.z));}return hitColor;
}最终结果均为1024×1024×512 这时候有人会问了tmd重要性采样怎么这么麻烦那我不用把ssp调高点不就行了emmm如果头铁不做重要性采样我也实验了一下就是下图的结果ssp已经是2048了但是噪点还是非常多就是采样效率太低了做了重要性采样后ssp为512的结果可以完美爆杀这张图。 代码实现可能遇到的问题
1.光源融入到整个天花板没有出现黑色
光源融入到整个天花板了tmd感觉网上没有人和我这个情况是一样的。。。
光源尼玛的就是融入整个天花板了tmd感觉网上没有人和我这个情况是一样的。。。
测试的时候发现其实这个光源是能被检测出来的只是着色的时候变成这样了然后分析了好几个小时的源码艹一直找不到原因最后debug了半天总算知道为什么了。
就是当对光源着色时目前是分为两个部分一个是直接光一个是间接光。
直接光的话应该是不会提供颜色的因为直接光同样是在光源上采样相当于ws和N是几乎垂直的cos(ws,N)项就会使得整个直接光的值接近0。代码中点偏移的处理只会让两者夹角大于90此时cos算出来甚至还是小于0的。综上直接光不会给其提供颜色。
间接光会对其提供颜色并且也是导致光源颜色和天花板颜色接近的直接原因因为计算间接光时光源本身是 漫反射材质所以这边代码会让光源应该会有light-Kd差不多的颜色而这个值又和天花板的颜色其实差不多所以看上去才只能看到天花板。
这个地方我看其他博主的解决方法都很简单粗暴检测出是光源就直接返回光源本身颜色但实际上光源也是会吸收反射其他光的如果场景中有其他光源那这种处理有可能就会出问题。**因此就需要保证光源是可以正常接收直接光和间接光的不能直接返回光源本身颜色。**此时观察之前的公式少了一个自身发光项下面第二张图之前一直不知道这个有啥用此时起作用了醍醐灌顶最后把这个加上去就可以正常显示了。
emm之前好几个小时一直纠结在直接光上面想方设法要避免直接光其实并不是这个原因导致的害也是尼玛的运气好多实验了一下结果和想的不一样就再思考分析了下就想出来原因了。。。
也明白了个道理发现问题并打算编写代码去解决的时候先看看能不能设计实验用代码验证一下这个问题的提出是不是正确的如果这个问题本身就是错误的那就没有必要花很多时间去写代码解决。我这次就花了很多时间去思考怎么避免直接光实际上这个问题本身就是不成立的我应该试一下没有直接光的话光源区域会不会变黑如果变黑了就说明我之前提出的问题是成立的。 我草事后去games论坛上居然真的找到了个一样的了 https://games-cn.org/forums/topic/zuoye7wenti/
2.箱子背阴处有很多黑色噪点
没对好伪代码中的那些向量方向定义说白了就是没完全按照伪代码来改了之后就好了。 修改后的结果
3.有白色噪点ssp调高仍然有
画面上有少部分白色噪点。 用ps工具放大发现是某个像素为白色此时SPP已经是512所以应该不是采样不够的问题。而且如果是采样不够应该是会在其周围同时出现大量噪点而不是只出现这么一个这么突出的例如下面第三张图。 这意味着计算光照时出现了一个非常大的值。排查代码发现原因很可能在于除数过小无限接近于0或等于0这里就是等于0结果接近无穷大。使这个像素的全部采样都收到这个无限大值的影响直接变成白色。这显然是我们不想要的。这就对应作业里的提示pdf接近0。解决方案就是pdf低于某个指定阈值时L_indir为0代码如下
float pdf shade_point_inter.m-pdf(wo, wi, N);
if(pdf EPSILON)L_indir castRay(ray_pTowi, depth 1) * shade_point_inter.m-eval(wo, wi, N) * dotProduct(wi, N) / (pdf *RussianRoulette);修改后的结果 通过这个案例分析一下为什么要考虑数值精度emmm如果不考虑数值精度很有可能一次数值的不正确就会产生难以想象的结果例如上述除数为0导致无限大inf的出现会使得当次像素颜色计算的其他采样结果无效相当于一个极偏值对整体结果产生了巨大影响而我们想要的是能够避免极偏值的影响类似机器学习里面的回归分类问题。所以这个时候就要对其特殊处理降低他对其他结果的影响。
理论思考
1.偏移为什么要分两种情况这么偏移
考虑了背光情况因为世间万物都有厚度如果光在前面从背面看就是会被挡住如下面第二张图。
2.为什么path tracing有软阴影
阴影部分应该都是没有直接光的那么导致软阴影的原因就是环境光接受的间接光是不一样的离得近的会被挡住更多的间接光离得远的间接光挡住的比较少所以就会有软阴影。
而witted style就没有软阴影这是因为没有考虑全局光就是只考虑了那一个方向。
3.把witted style和path tracing的结果对比一下两者的实现的不同点有哪些
区别很大path tracing是基于witted style的改进很容易看到这两者共同的地方。明显path tracing是合理的并且可以基于物理还能考虑全局光照能量守恒保证结果更加精确。具体的可以看 Ray Tracing笔记那一章。
反正我觉得两者最大的区别就是path tracing通过渲染方程考虑了全局光照而witted style无法考虑全局光照没有全局积分一个点的颜色就由那么几根光线决定实现那种完美镜面效果倒是好的很。。。它因此很难实现很多的细节大部分材质表现也没有path tracing好。
4.为什么程序中渲染方程的入射radiance是光源的emit是一个常量
emmm如果要深究非常复杂后面的光学理论看得我头痛大致就下面这么理解就够了。
反正radiance在程序中就是一根光线。
5.为什么说光栅化比光追快
光栅化没有大量的采样光追里面因为要求解积分需要大量的采样。
个人感悟
emmm可以说这是我从小到大除一些项目以外做某门课作业花的最长的时间了很感谢闫老师的101课程提供了优质的作业通过作业7对光线追踪技术有了一个基础的基本认识也在做的过程中不断发现问题解决问题提高自己的coding和思考能力真的很感谢闫老师我有一种预感这门课将会为中国图形学持续输送数以万计的人才。这次作业做完后差不多打好了离线渲染的一些基本的底子打算明天开始正式做本科的毕设了之前10月到12月一直在摆烂1月到2月在补渲染的基础知识所以说本科毕设正式开始时间就差不多是现在2月希望来得及害加油加油
也希望这个博客可以对做这个作业的人有一定帮助
参考资料
1.https://blog.csdn.net/u011942101/article/details/123656944代码性能分析器
2.https://blog.csdn.net/ycrsw/article/details/124408789
3.https://blog.csdn.net/qq_41765657/article/details/121942469
4.https://blog.csdn.net/ycrsw/article/details/124565054
5.https://blog.csdn.net/weixin_44491423/article/details/127552276
6.https://games-cn.org/forums/topic/guanyuguangxianzhuizong3zhongradiancedeshizi/
7.https://www.zhihu.com/question/28476602/answer/41003204
8.https://games-cn.org/forums/topic/guanyuxuanranfangchengdiguidingyideyiwen/
9.https://www.jianshu.com/p/0cfc3204af77
10.https://zhuanlan.zhihu.com/p/434964126
11.https://zhuanlan.zhihu.com/p/152226698