学习笔记22

——————————————————————————————————————————————————————————————————————————————————————————————————————————————

Rendering12

首先上一节的透明效果,我们没有处理任何阴影相关的问题,现在如果透射阴影得到的是上边的结果,

其实这个现象,我们可以通过debugger来分析:

这是第一步render,从这里可以看出在渲染这个地板的时候,他的阴影就出来了,这里少了一块,原因其实就向阴影的原理就好了是shadow map/

某一块应该被透射阴影的,但是没有透射,应该是没有通过深度的测试。

其实就是我们在生成shadow map的时候,这个上边的透明物体我们是按照完全的opaque来写入他的深度的。

所以在缺口的那一部分,我们写入的是实际上边透明物体的深度。所以等到渲染这个时候,这样分析好像有问题啊。

这里的问题好像是,写入的时候直接就有问题,第二个在缺口那部分直接没有写入缓冲区,所以最终颜色是这个地板

???????????????这里有些问题,好像怎么分析都是不对的。。。。

所以接下来要对阴影进行一个裁剪。

而clip操作,我们就需要用到alpha,也就是需要我们去采样albedo。

那么shadow caster的代码就要改动,另外由于之前的shadow caster是分为点光源和其他光源的,写的时候直接就是#if电光,#else其他光。

也就是写了两份的代码。如果我们现在加上去这里的采样alpha代码(也需要各种判断)需要些两遍,所以这里做了重写,把之前的两分支相同的地方进行了合并,

合并成一份,然后再加入alpha采样代码即可。

因为这个shadow的cginc,使用的时候就是在主程序的pass中展开的,所以定义变量自然就是在这里。

这里算是一个简化宏的判断。

这里他在给UV加判断的时候,并没有在vertex数据里面添加,这个不知道是处于什么考虑。。。。

所以操作流程:

搞一个UV的判断宏,然后根据判断来进行UV坐标的获取,获取之后在fragment中进行采样得到alpha,然后有了alpha就能clip

最后别忘了这俩,由于我们使用了宏,所以我们就必须要把他给写上预编译指令,这样才能和面板联系起来。

因为面板那边会指定关键字,而关键字发挥作用就是通过这里,这里根据指定的关键字,去定义相应的宏,然后后面的条件判断才有意义。

这里有一个易错点,就是对于uv的类型,虽说考虑阴影的时候只有这么一对uv,但是有时候打错了或者着急了写成了float,

这时候就会导致赋值错误,紧接着采样就会出错,所以以后出现了这些纹理采样结果有问题的情况,那就看看类型写对没。

关于这个mul矩阵,和Unity封装的区别

虽w分量默认是1但编译器不默认,可能要走验证操作,所以用官方的,直接就用1作为第四个分量,不检测验证。

这里的partial shadow,先说他的主要思路,说之前先说这类透明物体的阴影的思路,其实本例子中,他们都是透射一个正方体的阴影。

而我们采取clip,在生成shadow map的时候把物体的一些像素给clip掉,然后就相当于这个物体的这一块不再投射阴影。

(因为clip之后,他后面的东西在检测是否在阴影中时,就会发现shadow map中自己是当前方向距离光最近的,所以被直射,就没有了阴影衰减,也就前面的不再向她投射阴影了)

说白了,就是给我们一个大黑板,然后我们在上边挖孔,挖完空剩下贴在当前物体后面的物体上,这就是阴影。

而上边阴影的实现方式就是根据 当前物体透明之后的轮廓 来对这个黑板子进行一个挖孔。

但是上边的挖孔方式,是一个binary的,体现不出透明物体的阴影,也就是阴影之间也有亮暗的区分。

但是挖孔这件事本身就是binary的,如何有一个亮暗的区分,其实这里就使用了partial shadow。

也就是通过在板子上挖很小很小的小孔,然后控制挖的疏密,这样最终展示的阴影效果就会有那种 亮暗不同的阴影。

这里我们还是需要根据面板中设置来确定使用的模式,所以还是需要多加俩宏来进行判断。

当我们搞fade和半透明模式的时候,这里仍然需要UV,所以说,我们按照上边修改UV那个宏的定义。

对于半透明物体的shadow,有点类似soft shadow,就是阴影部分确实被遮挡了,但是又没有完全不受光,

是一个受光但不完全受光,遮挡但不完全遮挡的效果。所以真实情况下就算使用点光源去照射半透明物体,阴影也是比较soft的

而按照之前我们说的挖孔,永远都不能实现这种效果。

 if a surface lets half the light through

所以他就巧妙地利用上边的方式让光透过一半。这里说的棋盘模式,应该就是指的一黑一白,所以让光透过一半。

当这个黑白非常小的时候,考虑极限,当黑趋近于白的时候,也就相当于表面上的任何一个点,他受到一份光照,然后会透过去一半,剩下一半自己接受。

Further More

我们可以选用更多的pattern,改变黑白的比例,然后多个pattern之间进行混合,这样最终就得到了不同的透过阴影。

也就是不同亮度的阴影。

然后Unity为我们提供的dither pattern

我们可以看出这个他是从1到16的。

那么我们应该怎么去采样这个东西呢?首先还是根据我们的目的,先不说采样哪一个level,随便一个level,我们想要的是

在光照方向上,他们是按照dither pattern中的格式进行一个透光和不透光。

注意是光照方向上,而不是基于物体表面上,这里举个例子理解,假设我们选择了一个pattern是黑白一比一的,那么我们选择这个pattern

想要的肯定是光穿过物体的是一半,物体表面反射吸收的是一半。

如果我们使用的模型是一个表面凹凸不平的,是个小人,如果我们去采用物体表面的UV去采样,那么并不能保证透过的一定是一半。

情况极端一些,比如那些耳朵啊,鼻子啊,凹凸不平对吧,巧了他们凸起和凹陷的都让黑色给摊上了,白色都弄在了凹凸之间过渡的侧面上。

那么这样你如果去透光,就会少于一半,因为透光多少,是看垂直于光方向的白色块面积是不是一半的。

所以说这么采样肯定不行。那么什么坐标采样是垂直于光的呢?没错屏幕坐标。因为这里搞的是shadow map,所以光就是处在摄像机的位置。

所以光的方向是和屏幕坐标的面是垂直的。所以如果按照屏幕坐标采样,也就是直接把这张黑白图垂直着光贴上去。那肯定能够保证

透光是一半。

所以这里就使用这个屏幕坐标,我们可以通过VPOS得到它。

对于这个屏幕坐标,可能有些问题:

这里的意思就是我们把他俩都写在interpolator里面,可能在一些平台会有冲突,赋值的时候可能有问题。

而又因为,顶点输出的position 这个是必须输出的,但是不必一定作为fragment 的输入

所以说,我们就可以把vert输出和frag输入分离开来,分开成两个struct。

vert输出,我们让他包含position,但是frag输入,由于我们用不到这个position,我们直接去掉,然后我们会使用到VPOS

给他加上去,这样frag输入结构中就只有VPOS,不会有冲突了。

屏幕坐标和uv 坐标,前者是先把东西渲染到屏幕上然后给屏幕贴图,只取物体剪影的那一块,后者是先按照展uv 的方式把贴图贴到物体上,然后弄到屏幕上

(但是这里使用屏幕坐标并不是为了贴图,而是为了挖洞,也是类似的。)

采样dithering 的时候,屏幕坐标前的系数越小,说明屏幕就缩小,采样的分辨率就会小了,采样到的黑点就会变大。就想着一个无限平铺的纹理,然后用一个屏幕大小的框经过系数缩放后去纹理上框出一个范围。然后就有了屏幕对应地方的纹理结果

上边是Vert

下面是frag的

上边为啥又多了Position(vert输出的position),黑色框说了,因为都是#if判断,有可能仨都不成立,那时候会出一些问题。

所以加一个else加入position,这样就可以避免问题了,而且他和VPOS是互斥的,这里也不会冲突完全没问题。

关于使用上,我们想要使用unity的那些dither需要一个sampler3D,名字是确定好的。

然后采用的坐标前俩维度就是屏幕坐标,第三个维度就是level。

然后他的返回值如果是0,那就那就是采样到了要挖空的地方。clip即可。

还有一个问题,就是我们的屏幕坐标,动辄上千,然后采样的纹理宽度是1,也就是需要上千份平铺,这样可能会太过于密集,所以我们可以调整一个系数:

他这里不出以外应该是箭头指向的那个pattern,但是这俩其实看不出太多的联系,因为pattern搞上千次tiled,

然后坐标屏幕去采样而已,并不是左边方块意义对应右边的

我们现在把alpha考虑进来,让他去修改pattern,alpha越大pattern应该大,也就是透光越少。

然后就可以得到上边的结果了,显然我们挖的洞太大了,影子效果不明显,所以这里可以修改系数:

然后这里就是说了俩问题,一个是分辨率,我们刚刚说了一种极限情况,是挖的空无限多,但是挖孔其实最多最多也就是屏幕像素的个数

所以说这里最终效果是受到分辨率的限制的。

然后就是高了一个filter之后,效果就更加逼真了。

最后就是探讨了,关于动态情况下的这种阴影,是非常糟糕的,很严重的swimming效果,还有这种半透明物体

在unity里面是不允许接受阴影的。

半透明+光源softshadow,hardshadow,cutout+hard shadow

其实考虑到半透明阴影的一些限制,有时候半透明物体可能会弃用半透明阴影,而选择cutout阴影。

要想实现这一个,可以在面板上开放一个开关。

开关对应这里的一个关键词,然后根据关键词对之前的逻辑进行一些调整。

这里我们想让不支持半透明的情况去采用cutout,一种方式就是在cutout的clip判断前面写上或的条件。

又或者是直接定义一个cutout对应的宏。(本题采用的方法。)

主要就是定义一个我们开关使用的函数,用来显示开关,这个开关显示必须是在半透明状态下。

然后

显示前后做好准备和处理工作。

这次我们还得把cutout给显示出来。

————————————————————————————————————————————————————————————————————————————————————————————————————————

Rendering 13

首先是关于延迟渲染的设置,这个之前接触过,主要是通过graphic的设置,然后对于每一个摄像机可以选择使用graphic setting中设置的结果。

也可以选择直接某一个模式覆盖掉graphic setting中的设置。

这里的depth map生成的时候,有dynamic batching。

两个灯的draw call 不一样,这个容易解释,因为灯的位置不一样,他俩照亮的物体的个数也不一样,可能从灯的视角下有一些遮挡。

这个当我们开启延迟渲染的时候,MSAA会被关闭,因为他是基于子像素的。延迟渲染不玩那个,所以要想抗锯齿,得找一些后处理方式。

出了Gbuffer的drawcall,其他多数都在阴影上。

首先看前向渲染,都做了哪些事情,可以看出其中有大量的重复操作。

然后如果我们考虑把一些重复使用的数据进行一个cache,这样就可以节省很多的计算。

其实发现,每一个像素除了lighting计算中的light本身没有去cache,其他的东西都cache了。

所以直接干脆在第一个pass里面不对进行任何的lighting计算,只用来向Gbuffer中写数据。

Hence,deferred shading

延迟渲染 - 知乎 (zhihu.com)

其实提炼核心:就是前向渲染,在多光源情况下会做很多的无效遍历,因为光源(多光源时,平行光没几个,多的都是点光源和聚光灯)范围没多大,却去遍历摄像机看到的整个场景,导致大量的无效计算。

而延迟渲染解决这个点的核心在于,就是把光针对场景物体的计算转移到了针对屏幕像素的计算上来。减免了大量的无效计算。

这里说他俩render separately,这里render对于geometry指的是从各种几何数据,各种变换插值,然后存储到Gbuffer中,这基本上几何的render任务就完成了,

而下面就是光照的任务。

这里延迟渲染就是把这里给解耦了。先进行几何计算然后存储,再进行光照的计算。

所以这里延迟渲染的光就都是像素光。

多光源下他俩的区别。

其实渲染完几何,就相当于得到了一个建模软件中的模型的截图,首先是建模软件的,因为还没有着色,所以颜色就啥都行,然后就是截图,因为几何处理完,基本上投影啥的都做了,所以已经是一个2D的了。

关于平行光,因为这个东西会照亮整个场景,所以他计算的时候,直接所有的像素都进行计算了。

关于聚光灯,就计算它能够照亮范围内的,当然这种灯可能被遮挡,那就直接不用算了。

点光源和聚光灯也是类似的。

当然这里为了减少一些不必要的fragment计算,采用了Internal-StencilWrite,也没咋明白是干啥的。

另外这里说:聚光灯和点光源去计算一部分的fragment,其实这个可以根据已经rendered 的几何,根据他们的深度信息啥的,可以做一个判断吧,但是这里面感觉有好多的问题,具体细节应该是如何执行的??

大概的思路是找一个包围盒,然后包围盒投影,看看投影砸在了哪些像素上,然后对这些像素进行光照计算就好了。

在渲染过程中,他会有一个颜色的inverted。

感觉也没说明白

几何渲染之后的信息,存储在Gbuffer中。

格式有差异。

我们可以通过修改显示模式,看到Gbuffer中存储的一些数据。

还有这个MRT,也就是Gbuffer的内容。

当我们使用下面这个显示模式的时候,也就是显示法线的模式。

因为这个模式是延迟渲染下的,所以只有延迟渲染的物体才会在当前模式下显示出来。

关于延迟渲染的pass,基本格式和之前类似的,上边高亮部分需要修改。

我们如果只按照上边修改方式,其实得不到一个正确的结果,正确的情况,应该是把geometry数据写到延迟pass输出结果中。

而不是把shaded的结果输出。

而实际需要输出:4个buffer,我们这里直接把shading结果作为输出,那就相当于只填了其中的第一个buffer。

出现了上图的结果。

首先因为深度原因,他背后的东西被裁剪掉了,而物体本身可能由于一些原因没有画出来,所以导致最后一步渲染的天空盒显示了出来。

当然这个也不太明白对不对。

先看这个需要输出的东西,由于这里情况不同,所以我们需要把输出的结果进行一个封装,并且在内部根据渲染模式来定义成员有什么

大小写问题。

在填充这些gbuffer的时候:

前俩通道比较单纯,就是一些表面的属性。

第三个用来存储属性,不同的是,他的A通道,是只有两位,其他的RGB都是10位,这样精度更高,A不使用。

这里的最后一个buffer是用来积累场景的灯光的结果的,也就相当于我们之前的颜色缓冲区/

这里待会进行计算的结果都累积到它里面。而有一个不会累加,就是自发光,所以我们要在这个pass里面把自发光的结果给加进来。

这样最终的结果才完整。

当然要彻底完整,这里还需要这个环境光,我们只需要在这里开放一下延迟渲染对间接光的计算,我们最终gbuffer3的结果中就把间接光

给包含进去了。

然后剩下的就是每一个灯光自己的计算结果,累加起来就是最终的实际效果。

最后我们把摄像机的模式改成LDR,

把HDR改成off就好了。

然后结果就错到离谱。

原因是这个数据有特定的解码方式,我们只修改了模式,并没有修改对应的解码方式,所以导致结果就错离谱了。

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

延迟渲染反射的话题,由于反射有点忘了,所以没看!等回头复习一起看!!!!!!!!!!!!!!!

猜你喜欢

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