学习笔记20

通过一个语义来确定是使用贴图还是单个值。

这里添加一个自定义的语义,通过C#代码。添加之前,我们需要先在代码中获取到当前材质,。

获取之后,就可以设置关键词了,对于语义来说我们首先要有一个关键词让Unity知道,然后我们在program_multi的时候,就可以使用这个关键词,这样从而根据这个关键词进行相应的#if判断。

所以现在就是完成了第一步。

点击右上角之后,这里我们可以切换成debug模式,最开始我们在shader中没有#pragma这个关键词的时候,我们刚刚添加的关键词是放在无效的keyword里面的,添加之后就变成上边的形式了。

这里就是用multi那个,他会把涉及到的所有组合,都进行一个编译,这个是耗费时间的。而且有一些是没必要编译的。所以这一点我们可以优化一下。

具体就是使用一个shader_feature。这个的作用就是让有一些语义只在需要他们的时候才编译。

这里也没有看的太明白。

这里就是考虑了一点:当我们的程序运行的时候,他每一帧都会去创建一次那个关键词,但是这完全没有必要,首先这个关键词他确实是会变的,当我们的材质面板中的贴图赋上去或者删除下来的时候,关键词都需要重新去定义,所以确切的来说,我们应该在map被编辑的时候才去修改这个值。

也就是添加上如上的判断代码。

对于smoothness这个也是类似的,它往往也会准备一张贴图。

另外,由于上边这两个都只需要占用一个通道,所以这里可以和之前那个进行一个合并。

这个放在alpha里面,之前那个放RGB。(其实一个就够,但是不放白不放)

然后针对这个smoothness,我们做好了之后,其实和standard里面是有差别的。

其实smoothness在standard shader中的使用并不和metallic完全一致,它在有了贴图之后,仍然保留着_Smoothness的slider,这个仍然在贴图的基础上可以对光滑度进行一个调节。

但是当我们有贴图的时候,他这个smoothness变量并不是用来作为uniform的光滑度了。

而是作为一个调控贴图的系数。

也就是说这里本身对于光滑度的调整有三个东西:

一个是没有贴图的时候,我们用一个smoothness作为uniform的光滑度的值。

然后是有贴图的时候,我们采用贴图的alpha通道的值作为光滑度。

还有一个就是在有贴图的时候,用一个scalar作为整个贴图的强度系数,然后对光滑度有一个调控。

因为第一个和第三个不会同时出现,而且都是scalar,所以这里可以让_Smoothness同时表示这两个。

代码就这么写。

这里在写的时候,需要写两份getSmoothness,一个是fragment里面一个是fragment里面的indirect内部也需要。

有一个思路是定义一个变量然后先getsmoothness,然后fragment内直接用这个变量,然后indirect就把这个变量传进去。

这样就只需要调用一次,也就是采样只操作一次。

但是这就有点多此一举了,因为编译器,是不容忍重复的采样代码的,就算我们写了之后,他会帮我们自动优化的。

这里有很严重的artifacts

主要原因就是压缩造成的,而且这里的细窄的金属条倾斜吧唧的,这种情况更明显了。

仔细看看这里竖直的这个是和UV对齐的,artifact就基本没有,而右边倾斜的那个就非常明显了。

因为金属往往光滑一些,所以在金属流中都会配有一个smoothness图,来获得一个更好的效果。

但是如果不是金属流,没有metallic这个选项的时候,Smoothness的贴图应该如何去处理,就是放在albedo里面

因此搞一个switch:

创建完成这个枚举类型之后,我们要给他展示在面板上,通过一个函数:

这里我们就使用第一个形式的,他需要的第二个参数是一个重点,也就是我们要在这个下拉选框中显示什么。

而且注意他的返回值是用户选择的枚举选项。

也就是说如果用户没改,那就把原本的选项给我们,如果改了就会返回新选中的选项。

所以这里对于这个函数调用的前后,我们前面要准备当前要显示什么。在后要接受它新选中的选项,然后做一些相应操作。

先说总的逻辑:我们引入这个枚举选项,用户会选中一个,然后我们就会在程序中定义对应的keyword,然后shader中就可以根据需要是否去写对应的pragma,然后再if判断,是否写了pragma来做不同的处理。

根据上边总逻辑的指导C#中其实就只需要根据用户选中的选项,设置一下相应的keyword即可。

回到函数,先看函数调用后的,因为我们函数已经返回了用户选中什么了,然后我们只需要根据这个选中的结果,给设置相应的keyword就好了。

当然这里和之前类似,不需要每一帧都设置,只需要用户改动了选项才去设置。

这里因为你也不知道设置哪一个,直接都设置,这样不是用户选的那个的都会被取消关键词的设置,这样最后就会只设置用户指定的那个关键词。

然后函数调用之前,要准备当前source显示什么,这里就是考虑循环进来了,显示什么肯定是经过了上一帧选择之后的结果了。

上一帧经过选择之后的结果,就直接是source,但是这还不够,我们还要考虑第一帧,第一次应该显示什么这时候source应该是一个默认的情况,我们选择Uniform。

所以思路可以是定义一个成员变量,然后初始化为uniform,然后就ok了。因为第一次会使用这个初始化的uniform,以后就使用上一次赋值后的结果了。

当然这里不太清楚C#关于这个每一帧循环的东西,上边是我自己想的,不知道是否可以。

而这里作者使用了一种更保险的方式,就是在每帧的时候,都先把source定义为uniform,然后根据关键词是否定义来反推出上一帧的选择source。

如果说上一帧选了metallic或者albedo,那么必然会设置对应的关键词,然后source就会变成metallic或者albedo,如果上一帧是选了uniform就没有定义关键词,所以就source依旧保持是uniform。

并且初始情况也是可以包含的。

然后再考虑排版的格式,以及check

完整的code。

这里补充一点,首先我在写脚本的时候,这里拿了作者的代码做参考,这里由于我来的名字是一样的,也就是两个完全相同的类,

首先它居然没有报错。

然后发生的效果更加的奇怪,我们的脚本起作用的方式就是:在shader中写了一句用我们自定义的GUI。

也就是这俩c#代码都去控制面板,当修改下拉框的选项的时候,结果就是改不动,换成别的选项之后,根本没用。

原因大概就是这里有俩,然后可能有一些冲突啥的。

 

这里的操作没有反应,不知道他所表达的撤销是不是ctrl + Z的撤销。

其实到这里思路就明显了,

其实是我们事先在shader里面写好所有的keyword的。然后根据C#那边,也就是我们面板中定义了哪些keyword。

也就是我们面板中选谁,最后就会生成谁的变体。

(但是注意一点,就是这里好像有一个规则,当你定义的关键词,它找不到对应的变体的时候,他会自动生成list中的第一个关键词对应的变体。第一个如果找不到,也会定义第一个的变体)

所以这里好像第一个他就写了一个空的下划线,卧槽!66666666666666666666666666

Shader中做一下补充

这里要明白为啥有这个变体,其实可以从多pass的角度理解,因为这个自发光我们只需要考虑一次,而我们的fragment代码在base和add里面用的是同一套代码。

所以说要做一个区分,就可以从这个变体上下手,设置之前做一下判断,然后只在base pass中定义该变体。

(当然,原来有特意定义了一个FORWARD_BASE_PASS的宏,那么这里就另有用处了

另外注意上边的一个细节,他最后声明的_Emission是三维的变量。是可以这么玩的!!!!!!!!!!!!!

这里就是,考虑standard对于自发光,使用的并非是普通的颜色作为自发光,是HDR,而要想使用HDR,就必须进行tone mapping,色调映射。

用的话,这里就是有一个专门的函数。

比我们之前多了两个参数,一个是发光的范围,一个是alpha相关。

关于范围配置的类型和参数设置,就是范围和一个曝光范围3,中间就是把范围的给取一个倒数,

多哔哔一句,在调自发光颜色的时候,加点白色进去,不然效果看上不像是自发光。

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

Rendering

之前的效果还是有一些问题的额。

其中之一就是关于normal map的,normal map给表面带来的凹凸是幻觉。

这个当处理一些自阴影的情况时会有问题,因为阴影是通过shadow map生成的,shadow map就主要是通过depth buffer,而这个depth是模型实际的深度,normal map本质是高度图,他记录的一些高度信息在生成阴影的时候根本没有被考虑在内,而我们在渲染物体本体的时候却考虑了。所以这会让渲染的本体和他的阴影之间有一些不匹配。主要体现在自阴影上边。

实际应该有的模样是上边这样,表面的那些坑坑洼洼里面一半是比较暗的,是有阴影投射在里面的。

而按照我们实际的情况雀是:

从影子可以看出太阳光角度比较平缓,但是我们材质上的一些坑却没有任何的阴影投射。

只有坑的背光一面是因为背光原因接近黑色,但是完全没有阴影。

这里采用和metallic类似的方式,也用一个单独的shader feature

相关的GUI。

这里就是有遮蔽情况下的结果,没有的遮蔽的话直接返回1即可。

获得了最终的遮蔽值之后,因为他是对阴影的一个模拟,因为没法直接考虑到表面的坑,所以这里就用贴图来表示表面在光照下的被遮蔽的程度。

所以直接把他作为一个衰减来影响最后的光效果即可。

加上遮蔽之后,调节的时候,可以看出那些坑是暗了一些,但是真的没有暗多少。

和我们理想的效果还差了一些。(注意现在我们是PBR的思想,不能说还是亮,就把系数增大一些,就更暗了,但是这个搞就违背了PBR理论。因为我们需要按照纯物理来去生成贴图,再按公式来进行计算,不能随便乱搞)

其实这里还有环境光,间接光的因素,这里思路上有一个弯,可能会想,加上了环境光,暗部不是被更多的光照亮了吗,怎么会变暗。

其实是我们已经加了环境光是当前的亮度,而我们为已经加了的环境光加上一个遮蔽,这样就可以让坑更暗一些,更贴近真实效果。

现在的效果,就足够明显了。

但是这里又有值得思考的问题了,如上,我们的这个遮蔽map的计算,其实是只和表面的形状结构有关系的,和具体的灯光没有关系。

什么意思呢?先说结论,针对间接光这个遮蔽才有意义,直接光考虑遮蔽没有意义。

因为间接光是来自各个方向的,一个点是否在接受各个方向的间接光时,光被遮蔽程度和它附近的表面结构是密切相关的。比如凹陷下去的要比凸起的部分暗。(这里说的表面结构是考虑过法线贴图的)

而直接光就不一样了,直接光是否受到遮蔽其实和表面结构并没有很直接的关系,更多的是这个直接光的方向。

如果说一个坑,遮蔽strength可能很大,但是如果用光正对着坑照射过去,坑不应该被遮蔽,所以说这个遮蔽对于直射光来说就没有意义。

这也就是为啥通常只是说环境光遮蔽。

这里说到,有些场景也使用了直接光的AO,其实这就是artist control了,是更多的为了艺术效果,而违背PBR的。

关于屏幕空间的AO是一个后处理效果,基于所以灯光渲染之后的,所以他也是针对直接+间接光的结果进行处理,也是not realistic的。

 

这里把之前的金属那一张纹理和现在的AO混合在了一起。

但是它仍然是使用了两个sampler,AO并没有去才有metallic那张,但这么干也是有好处的,就是图片内存存储空间减少了,因为压缩技术,但是压缩会带来一定的精度损失,但是这种图其实对于细节的要求并不高,所以完全可以接受。

最后谈的就是把所有的东西都给他弄到一次采样中,也就是AO的结果不单独采样了,而是直接使用metallic采样的结果,但是这个就要对shader的代码框架做一个修改了,把一些该合并的代码合并一下才行。

这里由于我们的图上一直没有一些detail贴图,这里加进去之后,又有一件事需要考虑,就是我们并不想要在图的任意一个地方都加上detail,而是只在那些非金属的地方加上。

这里不要太相信自己的眼睛,注意人家的数据存储到了哪一个通道里面,这里是因为存储到了alpha通道,在网页上有一个透明效果,所以下图中alpha为0的也就是黑色的部分,在上边是完全透明的,所以和网页背景颜色是一样的。

首先想要干掉这些detail,需要干掉两个东西,一个是albedo上的detail,一个是detail normal上的。

这里由于是一个非0即1的值,其实这种非0即1,就可以用乘法来实现判断,结合lerp函数就是一个完美的非判断if语句。

对于Albedo的还是容易理解的。

这里当然有很多的写法了,if判断谁不会啊,

对于Normal的可能难理解一些。

首先明白normal表示的是什么,最开始混合的时候,normal本质是高度差,而混合我们采用的是导数相加。

而这里为什么和001进行混合其实,001表示的就是没有高度差的时候,也就是平坦的,相当于我们没有添加细节上去。

这块说的挺怪的。

然后干的事情就是添加三组关键词:

然后把他们相应的keyword 声明补上:

这里说的意思是,我们一般不会是每一帧去重新创建相应的keyword,而是每当贴图发生变动的时候才去创建,那么对于一个创建好的材质,如果他想要让程序创建出来keyword。

就得对新加了keyword的贴图搞一个刷新才行。

有时候修改贴图不能刷新,这时候修改它的scale试试。

这里解释了一些没有加keyword(standard shader种)的原因。

但是standard shader的标准不是完全正确的,我们还是要根据自己的需要去定制。

Unity是允许我们对多个材质同时编辑的。

当它编辑两个的时候出了一个问题,就是只对第一个起作用,原因就是GUI在设置关键词的时候只对第一个设置了,

要想解决这个问题,可以修改一下设置关键词的部分:

但是我没有这个问题。。。。

总结一下思路:

就是一个shader,里面可能有很多的情况,进行判断,如果每一种情况都写一个shader,那就太多了。

那么就引入了shader变体,这个就是通过#pragma A B表示着有两种可能。

那么下面你就可以判断,如果定义A怎么怎么着,如果定义B怎么怎么着。

然后我们只激活A和B中的一个,就可以去走其中的一个分支,这样一个shader就表示两种情况。

而只激活A和B中的一个,这件事情是由材质来做的,这样对于使用同一个shader的不同材质,他们之间只需要有着不同的激活选择,那么他们最终执行的shader代码就不同。这就会很方便。

这是一个#pragma,产生了两个分支,如果是更多的#pragma,他们之间互相组合,是一个指数级的搭配个数。

当我们设置 一个的时候,它相当于第一个是空,第一个是空,那么 如果两个都没有的情况下,那就会走第一个空。

如果我们这时候判断第二个,那就是false。

然后我们如果通过材质把第二个给激活了,这时候再去判断第二个就会为true。

注意这些有时候需要刷新一下,需要点一下材质刷新一下。

另外这里有一个问题点,就是我们通过GUI搞一下材质的keyword给激活了,激活之后判断为true,激活之后,其实一直就处于激活状态,如果我们需要关闭,需要手动调用

Disable那个,或者是我们自己封装好的接口,然后传入false。

unity shader 变种(多重编译 multi_compile) - 简书 (jianshu.com)

unity shader 变种(多重编译 multi_compile) - 简书 (jianshu.com)

(119条消息) 【Unity3D Shader编程】之十 深入理解Unity5中的Standard Shader(二)&屏幕油画特效的实现_浅墨_毛星云的博客-CSDN博客

(119条消息) shader变体是什么_[Unity/shaderlab]关于着色器变体_weixin_39678089的博客-CSDN博客

(119条消息) Unity Shader 变体处理与预加载流程_莫之的博客-CSDN博客_unity 预加载shader

[Unity/shaderlab]关于着色器变体 - 简书 (jianshu.com)

[Unity/shaderlab]关于着色器变体 - 简书 (jianshu.com)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值