学习笔记05PBR

 

 

 

在实际项目中,更加的统一,流程规格化。

以前绘制贴图啥的,可能风格不同,

PBR是渲染方式,宏观的概念,并不指代具体材质。PBS是其核心。

前者standard是金属流,后者是高光流。

两种工作的产出贴图不一样,工作效果是一样的。

倒背如流!!!

BRDF是PBS的光照模型。

光到达金属表面,进去或者反射。进去的全部被吸收,所以金属射到我们眼里的光,都是它反射到我们眼里面的。

金属反射的颜色,注意是反射的高光颜色,可以是带有颜色的,RGB某一个颜色的。

非金属就不一样了,它发射的高光都是黑白灰的,不具有彩色。

注意只要是没进入物体内部反射出去的这部分都是高光反射。

所以金属只有高光反射。(纯金属)

漫反射是进去了,然后又从进入的面出来了。各个方向射出去。

生锈的金属算是非金属。

水并不导电,不是金属,纯水不导电,水导电是因为有杂质。

也就是把平面放大了看,看发生了什么,就是上边的那俩行为,而我们就要模拟这个行为,那么由于平面上各个部分也是不同的,所以我们用贴图来实现不同。

右边的图表示的是,把一个看似光滑的球表面一处给使劲放大,是可以看到它的凹凸不平的。

粗糙度对光线的影响。

上边说的粗糙度的影响,原理其实就是能量守恒,当粗糙度上升,其实主要是很多方向偏了,被散射走了,所以整体亮度会暗一些,没那么亮。

线性空间才能保证计算的渲染的正确性,才能对最终效果的真实性有一个保证。

线性空间和gamma空间是对立的。

gamma空间在亮度相同的情况下是更曝的,在1.5的时候就基本完全爆掉了,没有细节了 。而线性空间下,高光和暗部和中间的过渡地带还都是清晰可见的。

=============================================================================================

对于渲染方程,我们实际情况是对他进行一些简化的,因为完全按照这个来操作,时间消耗非常之大。

大致理解这个式子就是:输入的光,首先会由于入射角带来一个衰减,然后根据BxDF来算出反射的,但是对于入射的不只是一个方向,所以这里就积分起来所有的,就得到指定出射方向上的光亮度。

而上边最为重要的就是BxDF。

双向,视角方向和入射角方向,这俩固定了,再谈亮度。

BRDF针对不透明的,因为他只考虑反射部分。

BRDF是反射的部分,也就是简化成入射点和出射点为同一个点,然后向外反射的部分,平面上边的部分。

BTDF就是和BRDF类似的,考虑平面下面的部分,透射的部分。

BSDF就是这俩相加的结果。

BSSRDF就是引入了次表面散射等现象,这个次表面散射,其实是光进入介质之后,然后在出去的时候,出去的点已经远离了入射点的情况,这就会产生次表面散射的现象。这是BRDF没有涉及到的。

BxDF 函数 - 知乎 (zhihu.com)

聊一聊PBR的一些重要前置概念 - 简书 (jianshu.com)

简谈实时皮肤渲染之预积分SSS - 简书 (jianshu.com)

描述次表面散射(Subsurface Scattering):BSSRDF 简介 - 知乎 (zhihu.com)

学习PBR路程(二、渲染方程与BxDF) - 知乎 (zhihu.com)

健壮,就是你在正常范围内调参数的时候,效果不会偏的太离谱。

这里说了一个美术的让步,因为一般情况下这个高光的颜色是固定的,非金属都是黑白灰的,不同的金属有着各自固定的颜色。而让步美术,就是让这个颜色变成可调节的,更好的服务美术效果。

头发渲染各向异性高光 - 知乎 (zhihu.com)

关于Unity里面的金属流和高光流,就是在导出贴图的时候,他俩不一样。

注意通道的颜色不一样,意味着通道内装的东西不一样。

然后再材质里面,我们可以看到这里的两个standard。

这个是他的材质面板。他的纹理显示和我们默认properties写的结果不一样,是因为这里的都是用脚本来自定义的额。

导出。

这里把贴图导入到材质中。这个是Unity 自带的金属流PBR的四张贴图:

这里的Metallic和smoothness是放在了同一张贴图里面。前者占据RGB,后者占据A。但是这里R=G=B,三个是存储的相同的信息。这里说了一个针对什么OpenGL2.0啥的,如果压缩,ADC1什么玩意的格式,我们是不能使用alpha通道的,所以我们把贴图给调整一下,让金属度放在R,光滑度放在G通道。为了节省空间,B里面我们塞一个AO。

然后形成我们自己的一套贴图形式:

同时删除了自发光贴图。注意法线选择的是右边的那个版本;、

当我们制定了自己的贴图版本之后,就不能使用它Unity的shader了,因为贴图对不上号。

他这里光滑度贴图只能来自metallic或者albedo的alpha通道,而我们放在了金属度的G通道,所以这里如果再用它的就没法传递进去光滑度贴图。

所以我们需要自己写一个自己的材质。

线性空间的修改,线性空间只支持OpenGL2.0以上版本,安卓4.3以上版本。

===============================================================================================

在一个没有光的房子里,打第一盏灯,房间从暗变成亮的,这时候再打一盏相同的灯,房间会变亮,虽是相同的灯,但是变亮的程度却不如第一添加的时候变亮的程度大,也就是亮度并没有达到一盏灯时候的两倍。

这个是人眼的一些机制导致的。

现实中实际的亮度值,是线性递增的,一盏灯是一定的亮度值,加上第二盏灯就是二倍的亮度。

对于人眼看上去却不是,他会有一个下凹的形状。如上图。

对于一些电子脉冲的显示器,以一定的脉冲冲击显示器,得到一种亮度,加倍脉冲得到一种亮度,对比这俩亮度第二种亮度依旧不是第一种的二倍。

所以这里人眼和电子脉冲显示器存在这么一个共性的巧合。

这里有意思的是:我们用人眼去看显示器的时候:

就是显示器显示的是2.2次方,人眼再看就是再来一个2.2次方。也就是更暗。

这个情况并不好,所以做显示器的硬件厂商,就尝试处理这个问题。

就是把显示器显示之前给他做一个矫正处理,放大的曲线让他是:x的2.2分之一次方的曲线。

输出之前,先2.2分之一,然后输出显示出来再来一个2.2,这样显示的亮度就是实际亮度了,人眼再一看,又会下凹一下,但是这个下凹一下,是符合人眼正常情况下看光照的亮度变化的,这样才看上去合理真实。

如果一个图片是sRGB,他的信息就会使上凸的曲线,也就是他最终的颜色要比实际的亮。

设置sRGB与否都无所谓,他都认为你是,都会在你设置的颜色的基础上变得更亮来存储,注意这个是存储的亮度,看到的就不一定了,受到眼睛和显示器的影响。

存储之后会进入shader,那么进入shader去对颜色进行加工处理,这时候就体现出弊端了,shader中处理颜色并不是原线性空间下的颜色,而是gamma 0.45下的。那么这就是错的,shader输出的颜色自然也不是那么准确的,比如亮度在叠加的时候,可能会大于实际相加的亮度,0.5 + 0.5  >  1,这里的0.5和1是gamma0.45下的,如果在线性下本应该等于1,现在大于1,那就产生了过曝。

线性空间下,会在shader处理之前,移除gamma矫正。

这个就完全正确了。

注意这里我们的颜色信息贴图就必须勾选上sRGB了,注意是颜色信息,存储颜色的。如果其他的法线等数值贴图,就不必勾选了额。因为并不把它里面的像素作为屏幕像素直接显示。只是用来做一些计算罢了。

DCC产出的图会自动进行一个gamma矫正,也就是增亮一下,放在gamma 0.45下,所以上边开局就是上凸的。

这里可以这么理解,就是sRGB上边说了他是变亮的,那么不勾选,他就认为你没变亮,他认为你是线性的,所以直接进入shader,然后再凸,这个凸是为了下面的显示器显示后为线性的。

所以这里的问题就在于,他认为你是线性,但实际你是凸起的,DCC产出的就是凸起的。

========================================================================================

创建PBRshader的时候,最好用这个作为模板。

然后把材质都用上刚刚创建的shader。

这里是一个surface shader。我们要转换成顶点片段着色器。

对于surface shader,这里会有一个选项,就是show generated code。

打开后,刚刚的五六十行表面着色器,变成了一千多行的代码。

我们要把这个代码给替换掉刚刚的表面着色器代码。效果是一样的。

注意看这里的几个pass,把该删除的东西都给删除了。

这里替换的时候,右边Aa,意思是考虑大小写,大小写不一致的不算。第二个意思是全单词,意思是:vert_surf1 这个他就不会替换,只有找到了单独的vert_surf才会替换。

替换按钮。

这个什么编译相关的,讲优化的时候会讲这个。

然后下面俩一个是雾效的定义和前向渲染的定义。

剩下的都是在内部会直接引用的,我们不写也ok。可以直接删了。

下面是GPU实例化的开启与否,渲染优化相关的。低端机不支持。

这里我们不考虑GPU实例化,下面的可以直接删除。

这里直接把if删除就好了,让他必然执行这一段。

由于这里是一步步看,过程中会修改删除,这时候一定要避免出错,因为这个不是自己写的,找错很麻烦,所以修改一点就保存一下看看是否有错。没错了再往下。

三行直接删除。

删除。

位置改一下。

删除。

这里的lightmap是光照贴图,也就是把光照信息烘焙到贴图上,这里静态物体可能会使用,这里不考虑所以直接删除。

然后删除上边的if条件。

这里俩精度的,删除上边半精度的,保留高精度的即可,然后条件删除。

这个struct就是v2f,我们改个名字。

这两句是一样的,我们换成下面的

这里的pack0我们不知道是干啥的,可以直接搜索,看看他是怎么用的,搜索到其他的pack0可以看出这个就是uv。改个名。

删除

然后这里它的vert的参数结构体是一个appdatafull,而不是自己定义结构体,这个东西,我们可以再cginc中找到。

其实就是准备好的一些结构。

这种通用的并不好,因为因为可能用不到其中一些,造成空间浪费。

 

这个也是不需要的,因为lmap是光照贴图的采样,光照贴图相关的都不要。

先对参数修改名字。

c定义换到下面来

注意这里最核心的一句就是c的赋值这句,这里调用的函数就是PBR的实现操作。

下面的这个宏的实现,就是赋值为1.可以手动赋值。但是这里可以删除,用不着。其他地方可以实现alpha的赋值1。

这个核心的函数的三个参数在这里。

可以换成

关于

UNITY_INITIALIZE_POTPUT:

然后下面的一部分是对结构体里面的数进行一个填充,

这里它初始化,然后调用了surf填充。但是我们可以直接填充的,下面之前的surf定义直接删除就行了。

不用surf,这两句也删了

填充金属度的时候,需要加一个属性

这里的normal可能麻烦一些。

先补充上这三个轴。

window中有一个参考大全可以没事看看。

把normal补全。

然后解决报错问题。

然后是核心函数的第二个参数,这个比较简单,这里可以少用一个变量。

最后一个关于GI:

这里Unity GI里卖弄有一个直接光和一个间接光,都在上边了。

======================================================================================================\

这里自己写一个头文件,然后放在同级目录的文件夹下面,include上。 

需要写上CGIncludes。

 

头文件里面干啥:

复制这一段。

头文件固定格式。

贴进去。

这样我们就可以修改这个文件,之前那个系统文件最好不要直接改动。

然后这里自己写的头文件,由于是要对原来的函数进行一个重写,我们这里改个名字,加一个1,这样调用的时候调用加了1的,就不会出现重定义了。这里把UnityGl这个也复制了过来,同样也加个1.

仔细看下面的这个函数,他在调用UnityGI时的输入参数,第一个是GIInput,这个不用说了,名字就知道了,第二个就是我们模型的AO图,第三个就是normal,第四个:

他的类型是上边。

一个是粗糙度,一个是反射的UV采样,这里是UVW,就是环境光照采样cubemap那种,是用一个向量来进行采样的, UVW。

然后再看:

这里由于准备的数据都是光滑度,没有粗糙度,所以这里用1减去光滑度,也就是上边的函数的作用。

这里走到上边之后,会进入base函数,这个里面就是把直接光的东西给填上,这个就是从data中直接拿的。然后主要干的东西就是间接光的diffuse。

间接光部分还缺少specular,也就是下面的一句。

找到这给函数的实现,然后

这个判断条件对应的是下面:

是反射探针,reflection probe,开启box projection。它的意思说了一下,但是没听明白。

这个判断的这一部分,暂不需要。跳过。

这里一个选项,如果取消勾选的话,那么接下来的一个判断就会成立:

如果没有勾选,也就使用unity默认的一个值作为高光,这个到BRDF再讲。

如果勾选了:

终点就落在了高亮的那句。

他的参数,第一个采样。

然后关于他的第二个参数,回到我们在frag中准备的giInput:

这里不适用lightmap,所以可以直接删除这一块。保留高亮那一句即可。

这一部分可以直接注释掉。

上边说完了他的三个参数,进入函数内部。

这里执行了一个操作。粗糙度 = 粗糙度*(1.7 - 0.7 * 粗糙度)

先看上边,我们用粗糙度实现的结果就是,随着粗糙度的变化,然后物体表面对外界的反射越来越模糊,但是代码里面怎么实现这个,代码里面没办法一点点的去让这个图片变得模糊。

采用的方式,就是通过采样更高等级的mipmap图,让显示的结果变得更模糊。

就模拟出了粗糙度变化的效果。

可以在这里看出来。

这里就是先算出来那一层的mipmap,然后根据UVW采样即可。采样完成之后进行一个解码。

其中的perceptualRoughnessToMipmapLevel就是上边那一句,就是拿着参数乘以6,因为mipmap是0到5,六个层级。

这里转了一圈得到mip,为啥不是直接拿着roughness就作为mip?

是因为:mip和roughness之间的变化不是线性对应的。所以用了上边那个1.7那一坨进行了处理,然后又得到mip值的。

================================================================================================================================

当GI计算出上边的所有的颜色之后,我们就要进入

这一个了,这一个其实就是我们的核心,因为有了输入的颜色,把这些颜色加以作用就会得到输出颜色,也就是fragment的输出结果。

复制下来,

函数的内容可以看出,就是前面准备各种变量,然后执行BRDF计算,然后返回。

这里有上下三行和alpha有关的,都是和透明模式下相关的。

然后上边有一个函数调用,对BRDF的三个参数产生了影响。

 

注意这里函数名字是根据金属度来计算高光和diffuse,这个是计算传入BRDF计算的高光和diffuse,并不是最后颜色的。

金属性越强,最后高光接近本色。

后边他注释也说了一句,绝缘体高光反射率一般都是4%。

然后1-反射率,是1-高光的反射率,得到的是漫反射的反射率。这个怎么理解?

反射率就是反射出去的比上输入的,那么反射出去的要么是高光,要么是漫反射,所以这俩加上等于1.

dielectricSpec表示的就是高光反射率,而金属度越大的时候,反射率接近1.

漫反射率乘以漫反射颜色,就作为最后的albedo。

这里有这一坨东西,首先BRDF3_Unity_PBS是效果最差的,其次2,1是最好的。就是Unity内部实现了三种PBR。

第一个SHADER_TARGET < 30就是对机器性能判断的,性能太差,自动选用最差的pbr。

这里就说明了,其他的UNITY_PBS_USE_BRDF1/2/3,其实都是根据性能来的。

就是和这里对应的,根据standard shader quality来定义对应的宏,从而选择对应的PBR。

然后我们这里默认是high,所以就会执行

这个,我们复制它。

这里有一个#error用法,运行后:

可以用来调试shader。

=============================================================================================================

这一节开始,探索BRDF内部。

l和v就表示双向。view和light

之前说的四部分,这里直接把GI都混进去了。

第一行这个,首先diffuseColor就是传进去的albedo,就是贴图乘以漫反射的反射率的结果。

后面括号里面是gi的间接漫反射,和直接漫反射相加,直接漫反射这里收到光的颜色影响,所以他俩相乘了一下。

注意,其中的很多的值都是参数传递进来的,只有diffuseTerm是在这里新出现的,我们看一下他是如何实现的:

前两句,一个是光滑度求出粗糙度,直接1减去。

然后是获得归一化的半角向量。

上边的rsqrt是根号之后再倒数。

三维渲染中的裁剪总汇-腾讯游戏学堂 (qq.com)

突然想起来一个问题,就是,那些背面的像素是什么时候剔除的。

又想起来一个问题,可以学习DUO的玩法,把内容整理一下,然后把问题整理出来。以后可以对着自问自答。

这里有一个if判断,也主要是这里有一个ndotv可能因为一些情况出现负数,那么有一种方式可以修正,但是有一定的性能消耗,对于高性能的机器可以选择这种,就可以把上边的宏定义为1,这样就进入这种

修正处理的方式。还有一种简单的方式来处理,结果的精度会有所下降,但是性能会得到一些节省。所以需要机器的性能进行一个权衡。

剩下的就是各种点积运算。

再往下有一段注释,这里迪斯尼的漫反射公式里面有一个除以PI的操作,但是这里没有了,他在这里解释了一下的原因。

==================================================================================================================

再来看高光,我们的Fresnel就是F,那么这里高光,他会受到灯光颜色的影响,所以这里乘以一个灯光颜色无可厚非。

然后剩余的DG以及分母部分都在specularTerm中。

先看specular:

unity里面是按照上边分开计算的,把G和分母放在一起,V。

这里高光模型计算是有俩的。

其实GGX那个宏就是1,所以直接走上边的那一部分。这里就是获取VD两项。

这里的roughness是

之前是感性上的粗糙度,我们这里给他理性化,学术化。

这里取一个max是因为防止粗糙度为0,这样高光就没了。所以限制它有一个最小0.002。

这里就是看微平面,然后给他看作一个个的点,每一个点只有一个反射方向,当m=h的时候,才正好看到反射的光线,否则就看不到,看到了说明这个点对这里有贡献,否则就没贡献。

按照这种方式来进行一个计算,但是不可能一一去计算,所以这里就是一个概率估算。

a是粗糙度,  nh是一个整体。就是之前准备的向量。ndoth。

按照右边代码的写法,而不是直接按照等式直接套上,这是和性能优化有关的。

后面加了一个10的-7,是为了分母不为零。

关于V的计算,第一种计算方式,由于消耗比较大,直接给ban了。

下面的是简化操作,简化了平方根。不是完全精确的方式。

计算完VD之后,

这里首先考虑gamma空间,如果是会进行一下处理。

然后高光还要乘以nl,主要是考虑高光的入射方向和法线做一个系数。然后还有是否开启高光,不开启的话直接赋值为0.。

any函数他的输入如果是一维的,输入0返回0,输入非0返回1.

如果多维的,输入分量全是0则返回0,分量有非0的返回1.

specColor是参数中传递进来的高光颜色(就是通过金属度来推测出的那个高光颜色)。

这里主要处理一种情况,如上注释。

F0表示的是视线和法线夹角为0度反射的颜色。

F90表示视线和法线90度时反射的颜色。

注意代码中使用的是lh,因为h是l和v的角平分线,所以这俩结果是一样的,这样lh用的多,就不必多定义一个变量vh了。

F0的位置传进来的是specColor表示,夹角比较小,反射比较弱的时候的反射。

=============================================================================================

IBL是提升效果比较明显的一部分。最影响效果表现的一部分。

主要是拿着间接光的高光来操作,首先这个高光本身是物体所有地方都有的,需要surfaceReduction来处理一下,有些地方有,有些没有,有些强有些弱。

还有就是不同角度看上去结果不同,乘以后面那个FresnelLerp。

衰减的计算:

这俩根据空间进行一个分支。

粗糙度从0到1,衰减从1到0.5.

gamma空间需要进行一个矫正,他这里需要感性的粗糙度,所以有一个四次方(本来是二次方),1/2.2就是gamma矫正。

这个近似是基本重合的。

然后就是fresnelLerp那一项,高光随着视角而变,所以参数需要nv。

为啥说他影响大呢,把这里环境source设置为custom并且指定cubemap,如果没有ibl,不同的cubemap切换带来的效果变化并不是很大,但是一旦加上ibl这一项,效果变化就会比较大。

对于PBR,我们把环境给调节好,不需要更改额外的参数来获得效果,把四项参数按照实际情况弄进去,然后把环境调节好,就会得到相应环境下对应的效果。

 

这里的sign有点疑问??

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值