学习笔记27

这里就是解释了我们的hack是如何和真实的兰伯特近似的。球选择点的时候,大概率是在肚子部分,底部很少,对应一些

Grazing angle的光线很少,而且偏离发现角度大的这些对应的贡献也会很少。

而这里所说的真正的兰伯特,采用的方式是在球上找点,然后cos计算。

所以需要获取球上的点,这个很简单,只需要让球心加上一个单位向量的偏移即可。所以就有了上边生成随机的单位向量的函数,

有了之后我们给他加到球心上。

扫描二维码关注公众号,回复: 14915904 查看本文章

这个计算看上去确实没有什么本质差别,但是实际跑出来的结果,看上去会少了一些黑点,上边是使用的这个兰伯特。

不光是我的,作者的图仔细看也是有更少的黑点了。

这里也说了这个兰伯特是更均匀的,也就是他俩对于靠近法线的部分都有一个较高的概率散射,但是兰伯特的这个更均匀一些。

这里他好像说的意思是我们的hack,是类似一个cos^3,而这个兰伯特是类似一个cos。但是为啥没说明白。。。

总之,换了兰伯特之后,阴影变少,球变得更亮,表面更光滑,这些都是因为散射变得更加的随机了。

注意上边我们都是一直在考虑法线偏移之后,如果说没有法线偏移呢?(意思是找target的时候都是用p来先和法线相加)

先看下法线的作用,就是当我们得到的随机单位向量是指向球内部的时候,如果直接那这个向量作为出射方向是不合理的。

所以这里如果先在发现上做一个偏移,然后我们考虑的所有的点都是在表面的外侧了,这样就不会出现不合理的结果了。

当然这里也可以在生成单位向量的时候,先排除掉那一半往内部发射的。这样不采用法线的偏移一样可以各个方向随机:

当考虑材质的时候,关于类的设计有两个思路,一个是设计一种材质,开放各种的参数调节不同的效果就好了。

还有一种就是多种材质,然后将第一种开放的参数一些直接调零(意思应该是根据类型hard code一部分)

这里采用后者。那么还是类似的思路,所有的材质都要解决一件事情,其实就是BRDF,也就是给到进来的光线,你给出反射的光线

包括方向,包括颜色等信息。

所以这里仍然是封装纯虚接口。

封装好之后,我们思考一下scatter函数的调用时机,他应该是在hit调用返回之后,才被调用。

之前我们的一切着色工作都是通过rec中记录的hit数据来完成的。现在我们计算的时候需要使用到对象的材质成员,这个就需要一个参数

的传递,而我们之前就是本着rec封装来减少参数的传递的。

所以这里把指针也放在record里面。所以整体的流程应该是:首先材质应该是object的成员,那么我们要给object赋值合适的材质,

object参与hit的计算,这时候要把材质放到rec中去。等hit执行完成之后,rec中有hit表面的法线,位置等数据,再加上这个scatter

函数就可以直接得出最终的color了。

这一块说的就是上边整个流程。

代码上也就是这么干/

有了父类材质的接口规范,我们就可以创建不同的子类材质了,然后合适的时机把这些子类材质给到不同的物体。

让物体有不同的属性。从而再计算color的时候,多态调用到不同的子类对象。

注意这里由于循环引用加了一个声明,record中需要material。而material中需要record。如果相互包含都文件,肯定不对。

这里说了俩事情,一个是衰减和吸收的,他说的衰减指的是白光进来,红光出去,这是衰减了。而吸收就直接白光进来,没有任何光出去。

那么这里可以只考虑衰减,那就是假设白光,那么反射出去的就是albedo。也可以都考虑,为了保证两种反射出去的总量一样,就可以

让p概率吸收,然后反射上除以一个p,也就是反射率高一些,但是有些地方直接不反射了。

然后还有一个就是关于scatter_diretion的问题,这个是随机生成的一个东西,就是当法线和我们的rand单位向量完全相反的时候,这里

就会有问题了,因为反射方向是0向量了,待会就会出现计算上的错误。

所以这里准备一个判断判断,舍弃掉这种情况。

上边的fabs是求浮点数的绝对值的。

Ray color函数就理解成,传递进去的参数是一个光线啊,他的作用就是这个光线是什么颜色的。

那么实际光路中他充当的是一个反射光,那么按照兰伯特来说,他的光就是入射光的颜色乘上兰伯特衰减系数就是反射光,就是我们

要的结果。

而入射光的颜色怎么算,那不还是ray color么,这样就递归起来了。

这样就可以给我们的物体加上颜色了。

这里就开始考虑镜面反射,这个推导其实是和以前菱形里面的推导一样的额。

关于反射方向的设计,这里有点不是很理解,因为这里的rec中记录的normal本身是一个经过判断选择后的,这个normal一定是和ray against的,所以说这个dot,好像一定是大于0的。

只有一种情况,相切,那就是等于0了。

所以他这里在改写这个时候,把如果上边相切,那么说明光照没有照射到这个点上,所以这根光线看过去应该是黑色。

当然,因为这里的scatter毕竟是一个规范的接口,因为各种材质计算各不相同,可能有一些特定的材质,需要这么一个返回值/

这里发现颜色又有一些不对。

中间的一个比较明显,而且比较好debug。

然后我就一直追踪他的颜色,如下:

最下面的lerp是递归后的,因为这里我简化了场景,并且采用了镜面反射,只有一个球,所以递归一次就不会再有碰撞了,所以就会返回,

我们可以看到他的返回值,返回之后,也就是raycolor的返回值,然后接着乘以atten就会再次返回,这次就会返回给main中的调用了。

这里设置的atten是1,本该在main那边接收到一样的值的,但是居然变了,所以问题就出现在这个return了。

其实这里单步调试是可以看到它在这里是走了一个vec的乘法重载的,没想到就是这个重载给写错了。

所以感觉现在debug的流程,基本上就是锁定像素点,然后问题出在颜色上就追着颜色看。基本上还是可以找得到原因的额。

因此在这还搞了一个函数,以后直接调用它debug就好了,但是可能需要手动把color里面的输出给删除了,因为输出有点占用时间。

现在效果就正确了,但是注意这里我们的球目前充当的是一个镜子球,也就是他完全是反射的,还没有折射呢。

考虑物体表面不是那么光滑的时候,对于两个差别很小的入射角,可能具有相同的反射光线。也就是上边的样子。

那么这里在光线追踪的时候,就可以认为是反射光线有一个小小的波动,反射的光线加上一个偏移(小球的半径对应偏移的多少)

小球的半径可以作为参数。

另外这里他说了一个问题,就是如果说整个球很大,导致局部表面很平,然后再加上grazing angle就很可能导致我们扰动之后的结果进入球内了。(但是他的代码中并没有考虑这种情况。这里如果我们不考虑,追踪的光线反射之后就真的进入球内传播,然后从别的面出来,

这就错了,因为这球如果是不透明的,就算是透明的进去也得有个折射角吧。如果要考虑,我们就得修改我们模型法线,让他始终朝外,

这样计算color的时候就算出来也是黑的,但是这个改成始终朝外就会导致透明球是真的会进入球内部的。。。。。这边又有问题了。)

关于这里的推导也是比较简单的,作者没有给他R'的具体推导过程,但是对于括号的那一部分。

我们如果考虑入射光线R为一个单位向量,那么ncosθ其实就是n方向上R投影的长度。(因为R和n都是单位向量)

然后用R和这个相加,自然是这两个边组成的三角形的第三边。

然后两个η相比,其实就是sin相比,对于斜边是1,sin值其实就是水平边的长度,那么水平长度之比再乘上上边的水平向量。

得到的就是折射部分的水平向量,也就是R'垂直。

剩下的就简单了。计算处R'的垂直和水平,然后得到R'了。注意这里得到向量,是两者相加。

In reality。

上边的算法有一个欠考虑的地方,就是当物体从玻璃或者水等介质到空气的时候,他是有一个全反射现象的。

那个时候,我们的反射角sinθ'变成了大于1的,这是无解的。

所以这里必须要做一个限制。一旦等于1 的时候,就说明到达了临界的状态。也就是全反射。如果没有到达就是可同时折射和反射。

具体情况参考介质。

这是sin求法。

实际生活中的反射率是会变化的,随着我们的视角而变化。其实这就是菲涅尔现象。

最后这里算是一个有趣的trick,就是我们搞一个半径为负数的球,这个球和另外一个球中心放在同一个位置,然后负数的绝对值要比另外一个球的半径小一些。

这会带来什么结果,我们trace 代码就知道了,首先radius他几乎所有的出现场合都是平方,也就是算相交的时候,这个加负号之后并不影响,只有下面这里,可以看出他的符号正好影响了rec中记录的法线。

总的来说,其实就是法线反过来了,但是上边代码不完整,后期的版本是:

有这个矫正之后才记录再rec中的。这里就有意思了,

首先我们光线从外面射入到内层的这个球的时候,front_face本该为true,但是这里由于法线是反的,所以这里就false了。

当然这里normal无论如何都会得到一个和ray against的方向,对于接下来的计算没有任何的影响。

关键的是,这里的front是被不合理的赋值了/

而这个东西却被我们刚刚使用了,我们会根据这个东西确定折射率之比。

总的来说这里由于front_face变量的颠倒,导致这里会认为我们的光是从内向外的。

也就认为这个内层小球内部是空气,外部是玻璃等等的。

而我们实际上在他外面确实套了一个玻璃,这样按照程序计算的,他就会认为这是一个玻璃球,但是内部空心的玻璃球。

在确定了长宽比的情况下,垂直和水平的FOV只要确定一个就好了,这里考虑垂直的。

关于FOV的修改,其实改的是虚拟的viewport,这个会影响发射的ray的角度。改的viewport面的大小。

关于摄像机的确定,这里就通过指定三个量,摄像机的位置,摄像机的focus point,up方向。

有了这仨,通过叉乘就能构建出一个正交的坐标轴。

这块说了摄像机的uw轴和选择的up始终是在一个平面的,这个很好想明白,因为我们通过cross的第一个轴,他是垂直于up和w确定的平面的,那么第三个轴也必然在这个面内,因为他必然垂直另外俩轴的平面。

有了这一点,其实就说明了,关于这个up轴的作用,我们通过调整他,就可以控制摄像机的轴所在的面。如果说他是瞎几把斜着的,那么整个摄像机的轴也肯定斜了。

因为他如果等比例大小缩放,他的距离摄像机前后的位置就要跟着改变。其实上边的写法,就是把这个面放在了focus distance上。

放在这个面上,和放在别的位置是一样的,没有任何影响,因为这个面的唯一作用就是确定ray方向,而等比例缩放+前后平移,ray不会因此改变。

这个光圈,这里决定的是棱镜的半径,也就是决定了focus blur的程度。

关于这里把相机简化的方式,由于经过透镜的所有光线都会聚焦于sensor,也就是我们的camera。所以不考虑从sensor到lens的那一段

猜你喜欢

转载自blog.csdn.net/yinianbaifaI/article/details/127702755