URP | PBR材质(三)-自定义PBR
内容偏多

注意 因为太复杂,本人才疏学浅有很多错误的地方,
目的,以Unity默认URP Shader效果为参考效果。
基础公式
PBR基础公式

光照的公式我们可以总结为:
最终结果 = 直接光的漫反射 + 直接光的高光反射 + 间接光的高光反射 + 间接光的漫反射 + 其他光照效果(自发光什么什么等)
D 法线分布函数:描述微观法线N和半角向量H的趋同性比重。粗糙度越低,物体光滑度越高,
G 高光
F 菲尼尔
PBR 流程
Metallic粗糙度工作流
贴图 (纹理,法线,环境遮蔽,金属度,粗糙度,自发光)
Specular工作流
纹理,法线,环境遮蔽,高光,自发光
我们这次以及金属粗糙度光照流程。
准备基础Shader
准备Shader ,默认Shader 有基础属性。
我们前输入需要准备的贴图 变量
这里定义 颜色贴图,法线贴图,Mask贴图,
Mask 贴图
金属度贴图储存在 = R通道
粗糙度贴图储存到 = G通道
AO贴图储存到 = B通道
自发光贴图储存到 = Alpha
目前我们需要数值,后面替换成贴图。
声明定义变量,贴图
片元着色器阶段输出
输出贴图颜色
输出颜色贴图

计算法线
因为我们使用的是法线贴图,我们在顶点着色器阶段求出的是模型的法线。
我们需要把这两融合起来。
在顶点着色器简单求出切线,副切线等。
对应的片元结构体中输出
在片元着色器阶段计算出法线
效果

这样N就计算完成

测试 NormalScale 控制法线强度是否起作用。

扩展 UnpackNormalScale 函数
UnpackNormalScale 法线纹理采样
首先呢,我们都知道法线是一个三维向量,其每个分量的范围都是(-1,1),但在我们存储的法线图中每个通道的范围是(0,1),这其中存在一个转换关系。以R通道分量为例:

所以逆过程就是先乘以2再加上1
再者,Unity对法线贴图的压缩算法采用的格式被称为DXT5nm,这种格式的突出特点是利用A通道存储x分量,利用G通道存储y分量,因为这两个通道的bit位数最多(RGBA分别为5,6,5,8)。所以需要我们反映射,至于Z分量,由于法线向量是单位向量,所以我们可以利用几何关系求得Z分量。

环境准备
准备10个球体,10个材质球

指定基础贴图,颜色贴图,法线贴图,粗糙度,金属度使用数值调整。


直接光高光反射部分
镜面反射部分

镜面反射部分包含三个函数 D F G ,分母部分还有一个标准化因子. $4(w_o ⋅ n)(w_i ⋅ n)$

高光反射 | D
法线分布函数GGX(Normal Distribution Function)

它是描述微观法线N和半角向量H的趋同性比重。

实现粗糙度高光过程
在Shader里定义一个函数,计算D
我们这里使用上面的12shader
这个函数是在计算D 需要输入2个变量,一个是NdotH , 一个是粗糙度变量。
需要的基础数据
我们需要计算出 NdotH
在片元着色器阶段获取灯光信息,归一化传过来的的N V,使用V + L计算出H
在定义一个变量扩展粗糙度
目前需要对粗糙度进行映射处理
这里 1 - _Roughness 反向粗糙度 。
单独输出D
输出看一下结果,
现在调整粗糙度就可以看到高光的变化了。

注意: 这里是高光范围,所以需要限制最小值也是有高光点的。


几何遮蔽 | G
几何函数G,(Geometry function)
即核心方程的G项,它是描述入射射线(即光照方向)和出射射线(视线方向)被自己的微观几何形状遮挡的比重。

使用粗糙度作为几何函数的输入参数
这里的K是做a基于几何函数是针对直接光照还是针对IBL光照的重映射
扩展 这里几何函数是因为需要考虑观察方向(V)和光线入射方向(L)的遮蔽情况

几何函数

几何函数具有两种主要形式:G1和G2
我们前求出K的值,在计算G1 G2相乘。
这里 K的值 使用拟合曲线:float k = pow(1 + roughness, 2) * 0.5;
这里我们需要哪些属性,,
a 粗糙度
NdotL
NdotV
再去定义真正的G项,把NL和NV代入子项,相乘即可
注意 :K系数,在直射光和间接光的计算方式略有不同,代码里是直接光的,而间接光的K=pow(roughness,2)/2,这里要注意。
在片元着色器阶段计算 NdotL NdotV
效果
粗糙度 0.1 - 1.0


菲涅尔函数 | F
float hl = max(saturate(dot(halfDir, lightDir)), 0.0001);
菲涅尔反射(Fresnel Reflectance)。光线以不同角度入射会有不同的反射率。相同的入射角度,不同的物质也会有不同的反射率。万物皆有菲涅尔反射。F0是即 0 度角入射的菲涅尔反射值。大多数非金属的F0范围是 0.02 - 0.04,大多数金属的F0范围是 0.7 - 1.0。

Schlick 的模型的公式

但是由于我们需要的法线方向并不是模型本身的宏观法线n,而是经过D项筛选通过的微观法线H,故需把N改成H。
UNITY 对这个计算进行了优化,视线方向V换成了L,如下所示,这是我们所使用的函数。

其中的5次方计算量比较大,把它变成自然对数函数进行计算可以节省计算量,后续文章里所有的5次方计算都可以换算成对数计算。

F 函数
这里输入 HdotL在输入一个F0 计算
那在片元着色器阶段计算HdotL和F0
效果
无贴图 金属度 0 -1

有颜色贴图

注意:菲涅尔效果是由金属度影响的。
这里金属度给到0的时候是没有颜色贴图,


合并输出
直接光高光部分我们都计算完成,把D,G,F代入公式,计算出高光部分。

这里计算出的高光点数值是大于1的,如果没有打开后处理的泛光效果的话,也看不出来,但是如果打开之后,就会发现效果不对。我这里强行把它限制到了0到1的区间了。
效果
公式结果 金属度 0-1 粗糙度 0-1

注意:这里的黄色是贴图的颜色
增加光照颜色
注意:这里经过半球积分后会乘PI,不要丢了;注意这里并未再次乘KS,因为KS=F,已经计算过了一次不需要重复计算。
金属度=1 粗糙度 0-1

金属度 = 0 粗糙度 0-1

金属度 0-1 粗糙度 0-1


直接光漫反射部分
上面准备好数据,计算一下直接光漫反射部分。
漫反射部分
获取计算漫反射需要的数据
漫反射计算

k_d = 漫反射部分占比率
k_s = 反射部分占比率
注意: 由于分母带了PI,而半球积分后会乘PI,两者就约掉了这就没有写。
入射光线和出射光线最大是180度,计算的时候是在一个半球空间计算。

这里的C = Albedo
扩展 这里为什么不叫Diffuse了,反而叫Albedo? 这是因为,Albedo 都是颜色信息,原来比如需要做一些阴影到贴图上,PBR完全不需要处理阴影,只需要颜色,所以起名叫Albedo
而除以 π 的原因是为归一化BRDF,从而使得BRDF符合能量守恒的条件,不会导致反射出去的光线比入射的光线还要多的情况。
所以上面这个公式可以这样表示
这里我们现在没有计算出kd ,前计算 NdaoL * 光照颜色
效果

这里处理一下漫反射,我们上面只是简单的制作的漫反射效果,我们还需要和金属度颜色关联到一起。
效果
金属度 = 0-1 粗糙度 1


合并直接光漫反射和直接光高光反射
效果
金属度 0- 1 粗糙度 0-1


金属度 1 粗糙度 0- 1


粗糙度 1 金属度 0-1


代码

间接光漫反射部分
球体型光照
在场景中创建球型光照,使用函数获取球型光照的颜色。
输出结果看一下效果
效果

IndirF_Function
定义一个函数 计算间接光的F
分别计算出间接光的 ks kd效果
输出间接光漫反射
金属 0 粗糙度 0-1

注意:金属度是1 都是黑色。

间接光镜面反射部分
间接光照的高光反射本质是对于反射探针(360全景相机)拍的一张图进行采样,把采样到的颜色当成光照去进行计算,这种光照称为基于图像的光照IBL(Image-Based Lighting),前面的漫反射也是IBL,关于IBL有非常复杂的理论,
URP | Reflection Probes 反射探针 - 哔哩哔哩 (bilibili.com)
前定义一个函数,对Cube采样,根据不同的粗糙度进行采样。
注意:这里也考虑AO的效果
单独输出看结果
效果
粗糙度

还需要间接光高光影响因子,这里就使用Unity默认的方法, unity使用的曲线拟合去得到结果.
smoothness 这里就单独粗糙度,因为前面的粗糙度我们重新映射过了。

输出查看高光影响因子
效果

在计算间高光反射接光
效果
金属度 1 粗糙度 0-1

金属度 0 粗糙度 0-1

合并输出,
效果
金属度 1 粗糙度 0-1

金属度 0 粗糙度 0-1

效果对比
这是自定义和 URP 自带的Lit Shader对比。

全代码
总结
PBR其实和其他光照模型一样,都是根据入射光和出射光计算,只是使用了更物理的算法BRDF算法。
漫反射部分,是在半球空间计算,C = 颜色贴图,
Fresnel 需要考虑金属和非金属对光的反射区别,Fresnel的公式。
UE4和unity的粗糙度反射是不一样的,
UE4

Unity

内容太多分开,下面会进行代码整理完善。