目前业界广泛采用的Microfacet Cook-Torrance BRDF形式如下:
基于此公式,本系列之前我们也实现过一版PBR基础:Vulkan_PBR—基于物理的渲染基础。
其中:
- D(h): 法线分布函数 (Normal Distribution Function) ,描述微面元法线分布的概率,即正确朝向的法线的浓度。即具有正确朝向,能够将来自l的光反射到v的表面点的相对于表面面积的浓度。
- F(l,h): 菲涅尔方程(Fresnel Equation) ,描述不同的表面角下表面所反射的光线所占的比率。
- G(l,v,h): 几何函数(Geometry Function) ,描述微平面自成阴影的属性,即m = h的未被遮蔽的表面点的百分比。
- 4(n·l)(n·v):校正因子(correctionfactor) ,作为微观几何的局部空间和整个宏观表面的局部空间之间变换的微平面量的校正。
一、法线分布函数(Normal Distribution Function)
1.1 法线分布从宏观表现到微观细节
关于基于物理的渲染理念,其实跟图形学中对几何体的建模尺度有一定关联。图形学中,对几何体外观的建模,总会假设一定的建模尺度和观察尺度:
- 宏观尺度(Macroscale), 几何体通过三角形网格进行建模, 由顶点法线(Vertex Normal)提供每顶点法线信息
- 中尺度(Mesoscale), 几何体通过纹理进行建模,由法线贴图(Normal Map)提供每像素法线信息
- 微观尺度(Microscale), 几何体通过BRDF进行建模,由粗糙度贴图(Roughness Map)配合法线分布函数,提供每亚像素(subpixel)法线信息
传统光照模型中,一般只将几何体建模到中尺度的法线贴图(Normal Map)层面。虽说Blinn-Phong等分布也是基于微平面理论推导而来,但并没有配套粗糙度贴图(Roughness Map)为其提供亚像素级精度的细节,而且传统的NDF一般都没有经过归一化,不满足能量守恒,容易出现失真。
而在基于物理的渲染工作流中,通过将粗糙度贴图(Roughness Map)与微平面归一化的法线分布函数结合使用,将需渲染的几何体的建模尺度细化到了微观尺度(Microscale)的亚像素层面,对材质的微观表现更加定量,所以能够带来更加接近真实的渲染质量和更全面的材质外观质感把控。
1.2 法线分布函数与微平面理论
- **微平面理论(microfacet theory)**作为一种研究微观几何(microgeometry)对反射率影响的数学分析方法,基于将微观几何(microgeometry)建模为微平面(microfacets)的集合的思想。其最初由光学物理领域开发,用于研究统计表面上的散射[Beckmann Spizzichino 1963]。在图形社区中,我们使用它来推导基于物理的BRDF。
- 微平面模型的一个重要特性是微平面法线m的统计分布(statistical distribution)。 此分布由曲面的法线分布函数(Normal Distribution Function,NDF)定义。
- 从直觉上来说,NDF就像是微平面法线分布的直方图(histogram)。 它在微平面法线更可能指向的方向上具有更高的值。大多数表面都具有在宏观表面法线n处显示出很强的法线分布峰值。
- 若以函数输入和输出的角度来看NDF,则其输入为微表面粗糙度(微表面法线集中程度)和宏观法线与视线的中间矢量(微表面法线方向),输出为此方向上的微表面法线强度。
- 一般我们用宏观表面的半矢量h来表示微观表面法线m,因为仅m = h的表面点的朝向才会将光线l反射到视线v的方向,其他朝向的表面点对BRDF没有贡献(正负相互抵消)。
- 目前业界广泛采用的Microfacet Cook-Torrance BRDF形式如下:
- 其中 D(h) 即法线分布函数 (Normal Distribution Function),描述了平面法线分布的概率,即具有正确朝向的微表面法线浓度。即具有正确朝向,能够将来自l的光反射到v的表面点的相对于表面面积的浓度。D(h)常被直接写作D(m)。
- 可以将法线分布函数 D(m) 理解为微观几何表面区域上的微平面表面法线的统计分布。 对 D(m) 在整个微平面法线上积分,会得到微表面的面积。
1.3 各项同性NDF相关总结
最常见的法线分布函数是各向同性(isotropic)的,它们围绕由宏观表面法线n定义的轴旋转对称(rotationally symmetrical)。本文将对其中在历史上使用和讨论较为广泛的几种法线分布函数,可以总结如下:
Beckmann [1963]
Blinn-Phong [1977]
GGX [2007] / Trowbridge-Reitz [1975]
Trowbridge-Reitz(GTR)[2012]
1.3.1 Beckmann分布
Beckmann分布是光学业界开发的第一批微平面模型中使用的法线分布。[Beckmann 1963],也是Cook-Torrance BRDF在提出时选择的NDF [Cook 1981] [Cook 1982]。
Beckmann NDF具备形状不变性(shape-invariant)
正确归一化后,Beckmann分布具有以下形式:
UE4中对Beckmann分布的实现代码如下:
// [Beckmann 1963, "The scattering of electromagnetic waves from rough surfaces"]
float D_Beckmann(float NoH, float a )
{
float NoH2 = NoH * NoH;
return exp( (NoH2 - 1) / (a * a * NoH2) ) / ( PI * a * a * NoH2 * NoH2 );
}
运行效果见下图:
1.3.2 Blinn-Phong分布
Blinn-Phong分布的前身Phong分布是计算机图形学文献中提出的最早的着色方程之一。
Blinn-Phong法线分布函数由Blinn推导出,作为(非基于物理的)Phong着色模型的改进,以更好地拟合微平面BRDF的结构。
Blinn-Phong 分布不具备形状不变性(shape-invariant)。
下式是较为主流的归一化的Blinn-Phong(Normalized Blinn-Phong)的形式:
其中,幂αp是Blinn-Phong NDF的“粗糙度参数”;高值表示光滑表面,低值表示粗糙表面。对于非常光滑的曲面,值可以任意高(一个完美的镜面αp=∞),并且通过将αp设置为0可以实现最大随机曲面(均匀NDF)。
αp参数不便于艺术家操纵或直接绘制,因为它带来的视觉变化非常不均匀。出于这个原因,经常让美术师们操纵“界面值”,即通过非线性函数从中导出αp。UE4中,则采用映射 [公式] , 那么得到的Blinn-Phong的形式为:
UE4中对Blinn-Phong的实现代码如下:
// [Blinn 1977, "Models of light reflection for computer synthesized pictures"]
float D_Blinn( float NoH, float a2)
{
float n = 2 / (a2*a2) - 2;
return (n+2) / (2*PI) * pow( NoH, n ); // 1 mad, 1 exp, 1 mul, 1 log
}
运行效果见下图:
1.3.3 GGX(Trowbridge-Reitz)分布
GGX即Trowbridge-Reitz分布,最初由Trowbridge和Reitz [Trowbridge 1975]推导出,在Blinn 1977年的论文 [Blinn 1977]中也有推荐此分布函数,但一直没有受到图形学界的太多关注。30多年后,Trowbridge-Reitz分布被Walter等人独立重新发现[Walter 2007],并将其命名为GGX分布。
在[Walter 2007]重新发现并提出GGX分布之后,GGX分布采用风潮开始在电影 [Burley 2012]和游戏 [Karis 2013][Lagarde 2014]行业中广泛传播,成为了如今游戏行业和电影行业中最常用的法线分布函数。
GGX分布的公式为:
在流行的模型中,GGX拥有最长的尾部。这是GGX能够日益普及的主要原因:
GGX分布具备形状不变性(shape-invariant),而与其对标的GTR等分布不具备形状不变性,这是GGX能普及的深层次原因。
在迪士尼原理着色模型(Disney principled shading model)中,Burley推荐将粗糙度控制以α= r2暴露给用户,其中r是0到1之间的用户界面粗糙度参数值,以让分布以更线性的方式变化。这种方式实用性较好,不少使用GGX分布的引擎与游戏都采用了这种映射,如UE4和Unity。
// GGX / Trowbridge-Reitz
// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"]
float D_GGXT(float NoH , float a)
{
float d = (NoH * NoH) * (a * a -1) + 1;
return (a*a) / ( PI*d*d );
}
运行效果见下图:
1.3.4 Generalized-Trowbridge-Reitz(GTR)分布
Burley [ Burley 2012]根据对Berry,GGX等分布的观察,提出了广义的Trowbridge-Reitz(Generalized-Trowbridge-Reitz,GTR)法线分布函数,其目标是允许更多地控制NDF的形状,特别是分布的尾部:
其中,γ参数用于控制尾部形状。 当γ= 2时,GTR等同于GGX。 随着γ的值减小,分布的尾部变得更长。而随着γ值的增加,分布的尾部变得更短。上式中:
γ=1时,GTR即Berry分布
γ=2时,GTR即GGX(Trowbridge-Reitz)分布
以下为各种γ值的GTR分布曲线与θh的关系图示:
GTR分布不具备形状不变性(shape-invariant),导致其发布以来,无法被广泛使用。
以下是γ= 1和γ= 2时GTR分布的Shader实现代码:
// Generalized-Trowbridge-Reitz distribution
float D_GTR1(float alpha, float dotNH)
{
float a2 = alpha * alpha;
float cos2th = dotNH * dotNH;
float den = (1.0 + (a2 - 1.0) * cos2th);
return (a2 - 1.0) / (PI * log(a2) * den);
}
float D_GTR2(float alpha, float dotNH)
{
float a2 = alpha * alpha;
float cos2th = dotNH * dotNH;
float den = (1.0 + (a2 - 1.0) * cos2th);
return a2 / (PI * den * den);
}
GTR1效果:
GTR2效果:
1.4 各向异性NDF相关总结
现实世界中,大多数材质具有各向同性的表面外观,但有些特殊材质的微观结构具有显著的各向异性(Anisotropy),从而显著影响其外观。
创建各向异性NDF的常用方法是基于现有各向同性NDF进行推导。而推导所使用的方法是通用的,可以应用于任何形状不变的(shape-invariant)各向同性NDF,这便是GGX等形状不变的NDF能更加普及的另一个原因。
1.4.1 Anisotropic Beckmann Distribution
各项异性的Beckmann分布形式如下:
其中,m为微表面法线(可以理解为half半矢量),n为宏观表面法线,t为切线方向,b为副法线方向。
以下为UE4中Anisotropic Beckmann分布的Shader实现代码:
// Anisotropic Beckmann
float D_Beckmann_aniso( float ax, float ay, float NoH, float3 H, float3 X, float3 Y )
{
float XoH = dot( X, H );
float YoH = dot( Y, H );
float d = - (XoH*XoH / (ax*ax) + YoH*YoH / (ay*ay)) / NoH*NoH;
return exp(d) / ( PI * ax*ay * NoH * NoH * NoH * NoH );
}
1.4.2 Anisotropic GGX Distribution
各项异性的GGX分布形式如下:
以下为UE4中Anisotropic GGX分布的Shader实现代码:
// Anisotropic GGX
// [Burley 2012, "Physically-Based Shading at Disney"]
float D_GGXaniso( float ax, float ay, float NoH, float3 H, float3 X, float3 Y )
{
float XoH = dot( X, H );
float YoH = dot( Y, H );
float d = XoH*XoH / (ax*ax) + YoH*YoH / (ay*ay) + NoH*NoH;
return 1 / ( PI * ax*ay * d*d );
}
其中,X为tangent,t切线方向,Y为binormal,b,副法线方向
需要注意的是,将法线贴图与各向异性BRDF组合时,重要的是要确保法线贴图扰动(perturbs)切线和副切线矢量以及法线。
二、菲涅尔方程(Fresnel Equation)
对于菲涅尔(Fresnel)项,业界方案一般都采用Schlick的Fresnel近似,因为计算成本低廉,而且精度足够:
vec3 F_Schlick(float cosTheta, float metallic)
{
vec3 F0 = mix(vec3(0.04), materialcolor(), metallic); // * material.specular
vec3 F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
return F;
}
运行效果见下图:
而虚幻4中使用的涅菲尔模型为:
vec3 Fresnel_UE4(float cosTheta, float metallic)
{
vec3 F0 = mix(vec3(0.04), materialcolor(), metallic); // * material.specular
vec3 F = F0 + (1.0 - F0) * pow(2, ((-5.55473) * cosTheta - 6.98316) * cosTheta);
return F;
}
运行效果见下图:
菲涅尔项的常见模型可以总结如下:
Cook-Torrance [1982]
Schlick [1994]
Gotanta [2014]
三、几何函数(Geometry Function)
在Microfacet Specular BRDF的D,G,F三项中,如果说法线分布函数是最核心的一项,那么几何函数则是核心的辅助项,而且是三项中最复杂的一项。
历史上主流的几何函数建模,按提出或归纳的时间进行排序,可以总结为:
- Smith [1967]
- V-cavity(Cook-Torrance)[1982]
- Schlick-Smith [1994]
- Neumann [1999]
- Kelemen [2001]
- Implicit [2010]
其中,Smith遮蔽函数(Smith masking function)是现在业界所采用的主流遮蔽函数,Eric Heitz在2014年[Heitz 2014]将其拓展为Smith联合遮蔽阴影函数(Smith Joint Masking-Shadowing Function),该函数具有四种形式:
- 分离遮蔽阴影型(Separable Masking and Shadowing)
- 高度相关遮蔽阴影型(Height-Correlated Masking and Shadowing)
- 方向相关遮蔽阴影型(Direction-Correlated Masking and Shadowing)
- 高度-方向相关遮蔽阴影型(Height-Direction-Correlated Masking and Shadowing)
其中,高度相关遮蔽阴影型(Height-Correlated Masking and Shadowing),以及其近似,是目前业界采用的主流遮蔽阴影函数。
3.1 几何函数的定义与主要属性
- 在基于物理的渲染中,几何函数(Geometry
Function)是一个0到1之间的标量,描述了微平面自阴影的属性,表示了具有半矢量法线的微平面(microfacet)中,同时被入射方向和反射方向可见(没有被遮挡的)的比例,即未被遮挡的m=
h微表面的百分比。几何函数(Geometry Function)即是对能顺利完成对光线的入射和出射交互的微平面概率进行建模的函数。 - 在microfacet
BRDF中,单纯的法线分布函数得到数值不是有效的微表面的法线强度,需结合几何函数,才能得到有效入射和出射法线,得到能对microfacet
BRDF产生贡献的强度。 - 在各种文献中,几何函数(Geometry Function)还有大量不同的别名。一些主要的常见叫法有: 几何项(Geometry
Term)、Specular G、几何衰减因子(Geometric Attenuation)、阴影因子(Shadowing
Factor)、遮蔽函数(Masking Function)、阴影函数(Shadowing
Function)、遮蔽阴影函数(Masking-Shadowing Function)、双向遮蔽阴影函数(Bidirectional
Shadowing-Masking Function) - 其中,在部分游戏引擎和文献中,几何函数G(l,v,h)和分母中的校正因子4(n·l)(n·v)会合并为可见性项(The
Visibility Term),Vis项,简称V项。其也经常作为几何函数的代指:
- 通常,除了近掠射角或非常粗糙的表面,几何函数对BRDF的形状影响相对较小,但对于BRDF保持能量守恒而言,几何函数至关重要。
- 几何函数取决于微表面的细节,并且很少有精确的表达式。很多情况下,各类文献会使用各种统计模型和简化假设推导出近似值。
3.2 几何函数的两种主要形式:G1和G2
几何函数具有两种主要形式:G1和G2,其中:
- G1为微平面在单个方向(光照方向L或观察方向V)上可见比例,一般代表遮蔽函数(masking
function)或阴影函数(shadowing function) - G2为微平面在光照方向L和观察方向V两个方向上可见比例,一般代表联合遮蔽阴影函数(joint masking-shadowing
function) 在实践中,G2由G1推导而来
默认情况下,microfacet BRDF中使用的几何函数代指G2
图中几何函数的两种主要形式:G1和G2。G1为微平面在单个方向(光照方向L或观察方向V)上可见比例。G2为微平面在光照方向L和观察方向V两个方向上可见比例(图片来自GDC 2017, PBR Diffuse Lighting for GGX+SmithMicrosurfaces, Earl Hammon )
3.3 几何函数与法线分布函数的联系
几何函数与法线分布函数作为Microfacet Specular BRDF中的重要两项,两者之间具有紧密的联系:
- 几何函数的解析形式的确认依赖于法线分布函数。
在微平面理论中,通过可见微平面的投影面积之和等于宏观表面的投影面积的恒等式,选定法线分布函数,并选定几何函数的模型,就可以唯一确认几何函数的准确形式。在选定几何函数的模型后(一般为Smith),几何函数的解析形式的确认则由对应的法线分布函数决定。 - 法线分布函数需要结合几何函数,得到有效的法线分布强度。
单纯的法线分布函数的输出值并不是能产生有效反射的法线强度,因为光线的入射和出射会被微平面部分遮挡,即并不是所有朝向m=h的微表面,能在给定光照方向L和观察方向V时可以顺利完成有效的反射。几何函数即是对能顺利完成入射和出射的微平面概率进行建模的函数。法线分布函数需要结合几何函数,得到最终对microfacet BRDF能产生贡献的有效法线分布强度。
3.4 Smith GGX的演变和发展
游戏和电影工业对GGX-Smith遮蔽函数的选用方面,可以总结为两个主要阶段:
SIGGRAPH 2014之前,Smith分离的遮蔽阴影函数
SIGGRAPH 2014之后,Smith相关的遮蔽阴影函数
而这两个阶段的演变,主要在于2014年Eric Heitz于JCGT 2014上发表了著名的paper 《Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs》,以及其后续在SIGGPRAPH 2014上进行的同名的talk。
3.4.1 SIGGRAPH 2012:Disney
Disney参考了 [Walter 2007]的近似方法,使用Smith GGX导出的G项,并将粗糙度参数进行重映射以减少光泽表面的极端增益,即将α 从[0,1]重映射到[0.5, 1],α的值为(0.5 + roughness/2)^2。从而使几何项的粗糙度变化更加平滑,更便于美术人员的使用。
以下为Disney 实现的Smith GGX的几何项的表达式:
3.4.2 SIGGRAPH 2013:UE4
其中UE4在SIGGRAPH 2013上公布的方案为基于Schlick近似,将k映射为k=a/2,去匹配GGX Smith方程,并采用了Disney对粗糙度的重映射:
3.4.3 SIGGRAPH 2014至今:业界转向Smith Joint Masking-Shadowing Function
在2014年,Heitz在JCGT 2014发表了著名的paper 《Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs》,以及后续在SIGGPRAPH 2014上进行了同名的talk,将游戏和电影业界对遮蔽阴影函数(The Smith Joint Masking-Shadowing Function)的理解上升到了一个新的层次。
UE4 ,Frostbite 和Unity等引擎都受到Heitz的启发,为了得到更精确的几何遮挡关系,开始考虑入射阴影和出射遮蔽之间的相关性,并在后续更新中各自转向了Smith联合遮蔽阴影函数(The Smith Joint Masking-Shadowing Function)的高度相关遮蔽阴影形式(Height-Correlated Masking and Shadowing),并相应地都做了一些近似与优化。
下面将分别对其进行总结。
3.4.4 UE4的GGX-Smith Correlated Joint 近似方案
UE4采用的 GGX-Smith Correlated Joint Approximate为:
实现代码如下:
// Appoximation of joint Smith term for GGX
// [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
float Vis_SmithJointApprox( float NoL , float NoV,float a)
{
float a2 = a*a;
float Vis_SmithV = NoL * ( NoV * ( 1 - a2 ) + a2 );
float Vis_SmithL = NoV * ( NoL * ( 1 - a2 ) + a2 );
return 0.5 /( Vis_SmithV + Vis_SmithL );
}
运行效果见下图:
3.4.5 Unity HDRP 的GGX-Smith Correlated Joint近似方案
Unity HDRP采用的GGX-Smith Correlated Joint Approximate为:
实现代码如下:
float V_SmithJointGGX(float NdotL, float NdotV, float roughness)
{
float a2 = roughness*roughness;
float lambdaV = NdotL * (NdotV * (1 - roughness) + roughness);
float lambdaL = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);
return 0.5 / (lambdaV + lambdaL);
}
运行效果见下图:
3.4.6 Google Filament渲染器 的GGX-Smith Joint近似方案
Google Filament渲染器采用的 GGX-Smith Correlated Joint Approximate为:
实现代码如下:
float V_SmithGGXCorrelated(float NoL, float NoV, float a)
{
float a2 = a * a;
float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);
return 0.5 / (GGXV + GGXL);
}
运行效果见下图:
3.4.7 Respawn Entertainment的 GGX-Smith Joint近似方案
Hammon[Hammon 2017]在GDC 2017上提出,UE4在2013年提出的近似G1可以导出由高度相关的Vis项的高效近似:
其中,lerp表示线性插值算子, lerp(x, y, s) = x * (1 − s) + y * s
mix(x,y,a) a控制混合结果 return x(1-a) +y*a 返回 线性混合的值
float G_RespawnEntertainment(float NoL, float NoV, float a)
{
float lerp = mix(2*abs(NoL)*abs(NoV),abs(NoL)+abs(NoV),a);
return 0.5 / lerp;
}
运行效果见下图:
四、 推荐方程
使用了上述多种DFG组合,最终个人感觉以下公式效果最佳:
// Normal Distribution function
float D_GGX(float dotNH, float roughness)
{
float alpha = roughness * roughness;
float alpha2 = alpha * alpha;
float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
return (alpha2)/(PI * denom*denom);
}
// Fresnel function
vec3 F_Schlick(float cosTheta, float metallic)
{
vec3 F0 = mix(vec3(0.04), materialcolor(), metallic); // * material.specular
vec3 F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
return F;
}
// Geometric Shadowing function
float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float GL = dotNL / (dotNL * (1.0 - k) + k);
float GV = dotNV / (dotNV * (1.0 - k) + k);
return GL * GV;
}
运行效果见下图: