PBR单位
电磁辐射的测量是辐射度学。有很多的辐射量单位来描述物体表面接受的光照,但是图形渲染经常用RGB三色谱来作为单位, 也就是float3.
BRDF基本属性
普通认为, 光照shading是在局部进行的, 在上面PBR物理现象的图17可以看出来。这种情况下,我们用 V 代表作为从物体表面观察点到人眼睛的单位方向向量,用 L 代表物体表面观察点的入射光的反方向单位向量。物体对光的交互关系用双向反射分布函数(BRDF)来描述,也就是有关于:“一个物体表面点在给定的入射光照方向L和给定的观察方向V,会表现如何的函数。如下图所示:
当然,入射光照方向L和观察方向V通常都是认定为表面之上的, 也就是dot(n, L)和dot(n, V)都是非负数的。
BRDF现象可以表达为两种方式,下面两种都是正确有效的
(1)多束不同方向的光在一个表面点的反射方向都为V。
(2)一束特定方向的入射光反射出多个方向的光。
(这里在IBL的specular重要性采样中计算可以用上面的理论来解释)
BRDF计算采用的是光谱单位,但是上面我们提过了图形渲染通常采用的RGB三色谱单位,所以BRDF里面代入的各种光照值也是RGB三色谱值。
BRDF在光照方程式中使用,方程式:
这个光照方程式告诉我们一个点的表现等于沿着该点各种方向的入射光照的微积分计算.
BRDF函数在光照计算中,遵循两大定律:
(1)光照的交互率,即把入射光照方向和观察方向V的函数位置调换后,计算的值还是一样的。数学上,表达为下面:
(2)能量守恒定律, 也就是反射出去能量不能超过入射的能量, 能量不是凭空产生.数学上,表达为下面:
BRDF描述了上篇博客提到两大光照交互现象, 光的反射现象和光的次表面散现象,但是由于我们本篇PBR形容的都是普通材质,次表面效果都近视为漫反射效果。所以这里的BRDF两大现象为:
(1)镜面反射(specular)
(2)漫反射(diffuse)
BRDF函数通常也是根据光交互两大物理现象分化为 镜面反射项(specular term)和漫反射项(diffuse).如下所示:
BRDF函数模型
上面提出了BRDF函数的各种属性,图形学界这么多年来很多研究者提出了各种满足BRDF函数的具体模型。
但是目前渲染业界最常用计算BRDF的specular项是Cook-Torrance函数模型,而计算diffuse的是Lambert-diffuse,如下所示
Cook-Torrance的函数模型------specular
Cook-Torrance用来计算BRDF的specular项, 由三大项组成,即
(1)F(l, h), 微表面的法线分布函数, 描述了各个微表面对齐半向量(h)的情况(microfacet normal distribution function, NDF)
(2)G(l, v, h), 几何函数,描述了微表面下光照入射和光照出射在微表面的遮挡下剩下比例。(Geomtetry attenuation function)
当然在图形渲染界, G(l, v, h)经常是和此条公式的(n.l)和(n.v)组成可视项(Visibility Item),如下
(3)D(h), 菲涅尔函数, 描述了光在两种介质接触边界反射量和折射量之间的比例关系(Fresnel function)
Cook-Torrance模型用来描述BRDF的spceluar是存在两种优点的
(1)能描述了光的物理现象
(2)鲁棒性很强,把光的物理现象分解成上述的三大函数原型,这三大函数原型代表了光和物质表面点交互的物理表现,实际上很多论文采用Cook-Torrance模型是为了研究不同的F,G,H函数模型效果罢了。
法线分布函数(NDF)
上篇文章说过, PBR理论是建立在微表面理论的基础上的, 微表面理论假设了表面由很多微小的表面构成的,小到人眼都无法看清楚。h向量是半向量, 为L和V方向的中间向量.
不同的粗糙度法线分布是不一样的
对于法线分布的函数,本章用的是GGX-NDF,如下所示:
当然还存在其他的NDF模型,如Blinn-Phong,只不过GGX相对于Blinn-Phong消耗基本大不了多少,但高光尾巴效果却好了不少,显得更狭长更自然
《使命召唤-黑色行动2》使用了Blinn-Phong:
GGX-NDF在不同的粗糙度下的效果如下:
代码如下:
//NormalDistribution:Trowbridge-Reitz GGX
float DistributionGGX(float3 n, float3 h, float roughness)
{
float a2 = roughness * roughness;
float nDoth = max(dot(n, h), 0.0);
float nDoth2 = nDoth * nDoth;
float denom = (nDoth2 * (a2 - 1.0) + 1.0);
denom = max(PI * denom * denom, 0.001);
return a2 / denom;
}
几何衰减函数(Geomtetry attenuation function)
几何遮挡是对光线的遮挡,越粗糙的表面光线遮挡越强。所以几何衰减函数也把GGX的法线分布考虑进来,GGX和Schlick-Beckmann结合得到了几何遮挡函数,如下所示:
当然如果你用不同法线分布函数(比如phong_),几何遮挡函数自然是不同的,本文的几何衰减函数是根据GGX推导而来,下面的大名鼎鼎的GGX来源论文就论述了相关概念。
其中的k系数对于不同光源是不同的,点光源和平行光这些光源如下:
IBL光源如下:
微表面对光线的遮挡考虑为 反射光出射的遮挡和微表面对视线的遮挡的结合,因此使用 Smith's method 把两个因素考虑起来
Schlick-GGX遮挡函数在不同的粗糙度效果如下:
代码如下:
//GeometryDistribution: Schlick-GGX
float GeometrySchlickGGX(float nDotv, float roughness)
{
float r = roughness + 1.0;
float k = (r * r) / 8.0;
float denom = nDotv * (1.0 - k) + k;
return nDotv / denom;
}
//GeometrySmith = geometry obstruction(view_direction_obs) + geometry shadowing(light_dir_obs)
float GeometrySmith(float3 normal, float3 vieDir, float3 lightDir, float roughness)
{
float nDotv = max(dot(normal, vieDir), 0.0);
float nDotl = max(dot(normal, lightDir), 0.0);
float ggx1 = GeometrySchlickGGX(nDotv, roughness);
float ggx2 = GeometrySchlickGGX(nDotl, roughness);
return ggx1 * ggx2;
}
菲涅尔函数(Fresnel function)
菲涅尔函数描述了一束光从两种介质界面发生折射时,多少比例的光发生了折射,多少比例的光发生了反射。大部分菲涅尔函数都用了Schlick模型, 如下:
当然有部分团队的PBR模型改进了Schlick模型或者采用了其他菲尼尔函数模型,比如UE4引擎的菲尼尔函数改进了Schlick模型,采用了 Spherical Gaussian approximation来代替 “5”的幂指数,变得更节省
在球面上观察菲涅尔效果,观察角度越接近水平的地方就越亮(dot(h, v)的值越小),如下:
这里的 Fo 指的是在垂直于两种介质边界的反射值,被称为材质的基本反射率。不同的材质基本反射率不一样,其中金属的基本反射率较高, 电解质或者绝缘体的基本反射率较低。如下图所示:
所以Fo的值应该根据金属值进行一定的计算,这里我们这里做个hack设定Fo最小值为float3(0.04, 0.04, 0.04),而最大值为Albedo, 利用metal金属值在两种中进行插值。(这里0金属值代表完全的绝缘体,1.0代表完全的金属).代码如下:
float3 GetFresnelF0(float3 albedo, float metallic)
{
float3 f0 = float3(0.04, 0.04, 0.04);
f0 = lerp(f0, albedo, metallic);
return f0;
}
float3 FresnelSchlick(float cosTheta, float3 f0)
{
return f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);
}
漫反射函数(Diffuse Function)
上篇我们已经说过了漫反射现象的由来, 即次表面散射(折射进入第二种介质中才发射出来,伴随菲尼尔现象)的理想近视得到的。我们也们说过BRDF遵循能量守恒,所以在发生菲涅尔折射现象的时候,折射进入第二种材质的所有能量以漫反射的方式再次转换为了漫反射能量, 这里设定specular item存在一个能量系数ks, diffuse item存在一个能量系数 kd,则
ks + kd = 1.0
ks = FresnelSchlick
kd = 1.0 - FresnelSchlick
而我们最常用的Diffuse模型为Lambert Diffuse, 也就是
代码如下:
float3 F = FresnelSchlick(cosTheta, fo);
float3 ks = F;
float3 kd = float3(1.0, 1.0, 1.0) - ks;
kd *= 1.0 - metal;
diffuse = kd * albedo / PI
PBR材质的表现
金属值(metal)从下往上是0.0到1.0. 粗糙度(roughness)从左往右是0.0到1.0.
项目链接
https://github.com/2047241149/SDEngine
资料参考
【2】https://learnopengl.com/PBR/Lighting
【3】https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf (GGX NDF的来源)
【4】https://inst.eecs.berkeley.edu/~cs294-13/fa09/lectures/cookpaper.pdf (Cook torrance的来源)
【5】https://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf