Blinn-Phong是目前很多游戏引擎使用的一种光照模型,是James F. Blinn在1977年在Phong光照模型的基础上,加以拓展得到的,但是这种光照模型得到的效果并不真实,特别是对于一些写实游戏而言,而想要得到更真实的效果,就不得不使用一种基于物理学规律的方式来模拟光照,PBR由此诞生。
PBR的实现方案有很多,这里我只提最先由迪士尼提出,并被Epic Games用于实时渲染的基于金属质地工作流(Metallic Workflow)的方案。
实现PBR必须满足以下三个条件: 1. 基于微平面(Microfacet)的表面模型 2. 能量守恒 3. 应用基于物理的BRDF。
1. 基于微平面(Microfacet)的表面模型
所有的PBR技术都基于微平面理论, 简单来说任何物体的表面在达到一定的微观尺度后,可以用一种微平面(Microfacets)的细小镜面来进行描述, 理论上来说, 粗糙度越高这些微平面排列就越混乱这就会导致光线会从不同的方向发散开来, 而对于粗糙度低的微平面,排列就比较平整,光线也基本上会沿着一个方向反射,我们可以定义一个粗糙度参数,然后用统计学的方法来估计微平面的粗糙程度,(注意!任何物体在微观尺度下都不是绝对光滑的,所以粗糙度的参数绝对不为0) 我们可以根据这个粗糙度来计算朝向方向与中间向量的比例,朝向方向越是与中间向量一致,镜面反射的效果就越是强烈越是锐利。

能量守恒
简单来说: 出射光线的能量永远不能超过入射光线的能量(发光面除外),光线到达物体的表面时会分成两部分,一个是折射部分,另一个是反射部分,折射部分是会进入物体表面并被吸收的光线,也就是漫反射光照, 反射的部分就是直接反射开而不进入物体表面的光线,也就是镜面光照。
根据能量守恒的结论: 反射光与折射光它们二者之间是互斥的关系, 而被物体表面所反射的光线不会在被物体表面所吸收,也就是光线到达物体表面的时候,它只会吸收折射部分的光线而反射部分的光线将不再被吸收,所以只要我们计算出反射光线能量的百分比,再用去1 减去, 结果就是折射光线能量的百分比,这样我们就能在遵守能量守恒定律的前提下知道入射光线的反射部分与折射部分所占的总量了。
反射率方程

这个方程是用来模拟光的视觉效果最好的模型, PBR正是基于这个模型的,这个方程可能会有点吓人,不过不用担心我们一步步来解剖它。
首先我们需要了解一点辐射度量学的内容,我们常说的“光”其实只是电磁辐射大家族中的一小部分——可见光(波长大约在380nm到780nm之间)

辐射度量学就是对电磁辐射的测量。
辐射通量(Radiant Flux): 用Φ表示, 它指的是单位时间内发射、传输或接收的辐射能。想象水龙头,它表示水流(辐射能量流)的速率有多快。
立体角(Solid Angle): 用ω表示, 它可以为我们描述投射到单位球体上的一个截面的大小或者面积

辐射强度(Radiant Intensity): 用I表示,它指的是在单位球面上,一个光源向每单位立体角所投送的辐射通量,
所以它的计算公式是辐射通量Φ除以立体角ω,不过在这里我们用微分的形式,所以它的完整公式是I = \frac{dΦ}{dω}
辐射率(Radiance): 用L表示, 它描述的是:从一个特定位置、朝一个特定方向、穿过一个特定面积(垂直于该方向)的辐射功率。想象你拿着一个望远镜看月亮,望远镜镜头接收到的那束来自月亮上某一点、朝你眼睛方向发出的光,它的“强度”就可以用辐射率来描述。它的计算公式如下: L=\frac{d^2Φ}{dAdωcosθ}。
它受到入射光线与平面法线间的夹角θ的余弦值cosθ的影响:当直接辐射到平面上的程度越低时,光线就越弱,而当光线完全垂直于平面时强度最高。这和漫反射光照的概念相似,其中cosθ就直接对应于光线的方向向量和平面法向量的点积

如果我们把立体角ω和面积A看作是无穷小的,那么我们就能用辐射率来表示单束光线穿过空间中的一个点的通量。这就使我们可以计算得出作用于单个点上的单束光线的辐射率,我们实际上把立体角ω转变为光线的入射方向向量然后把面A转换为点p。这样我们就能直接使用辐射率来计算单束光线对单个物体表面的作用了。
辐照度(Irradiance): 指的是所有投射到点p上的光线的总和,专业一点的说法是描述照射到一个表面上的辐射功率密度。
现在我们回去看方程式,L_i指的是某个无限小的立体角ω_i在p点上的辐射率,而立体角可以视作是光线的入射方向向量,我们利用光线和平面间的入射角的余弦值cosθ来计算能量,也就是方程式中的(n \cdot w_i),用ωo
表示观察的方向,反射率公式计算了点p在ω_o方向上被反射出来的辐射率L_o(p,ω_o)的总和。或者换句话说:Lo表示了从ω_o方向上观察,光线投射到点p反射出来的辐照度。
但是,反射率公式是围绕所有入射辐射率的总和,也就是辐照度来计算的,所以我们需要计算的就不只是单一的一个方向上的入射光,而是一个以点p为球心的半球领域Ω内所有方向上的入射光。一个半球领域(Hemisphere)可以描述为以平面法线n为轴所环绕的半个球体。为了计算半球领域里所有入射方向上的dw_i总和,我们要用到一个经典的数学工具,积分(Integral)∫, 所以这个反射率方程概括了在半球领域Ω内,碰撞到了点p上的所有入射方向ω_i
上的光线的辐射率,并受到f_r的约束,然后返回观察方向上反射光的L_o。
BRDF
BRDF,或者说双向反射分布函数(Bidirectional Reflective Distribution Function),也就是方程式中的f_r。
BRDF可以近似的求出每束光线对一个给定了材质属性的平面上最终反射出来的光线所作出的贡献程度。举例来说,如果一个平面拥有完全光滑的表面(比如镜面),那么对于所有的入射光线ω_i(除了一束以外)而言BRDF函数都会返回0.0 ,只有一束与出射光线ω_o拥有相同(被反射)角度的光线会得到1.0这个返回值。
实际上,采用ω_i和ω_o作为输入参数的 Blinn-Phong光照模型也被认为是一个BRDF,但是,BRDF基于我们之前所探讨过的微平面理论来近似的求得材质的反射与折射属性。对于一个BRDF,它必须遵守能量守恒定律,也就是说反射光线的总和永远不能超过入射光线的总量,然而由于Blinn-Phong模型并没有遵循能量守恒定律,因此它不被认为是基于物理的渲染。
目前有许多BRDF使用的实时渲染管线都是一种被称为Cook-Torrance BRDF模型,Cook-Torrance BRDF兼有漫反射和镜面反射两个部分:f_r = k_df_{lambert} + k_sf_{cook−torrance}。其中k_d和k_s就是之前提到过的折射光线能量的百分比和反射光线能量的百分比。
这个函数左侧是漫反射部分,用的模型是Lambertian漫反射模型:f_{lambert} = \frac{c}{π},c表示表面颜色。除以π是为了对漫反射光进行标准化。目前存在着许多不同类型的模型来实现BRDF的漫反射部分,大多看上去都相当真实,但是相应的运算开销也非常的昂贵。不过按照Epic公司给出的结论,Lambertian漫反射模型已经足够应付大多数实时渲染的用途了。
函数右侧的镜面反射部分要稍微更高级一些,f_{cook−torrance} = \frac{DGF}{4(w_{o}\cdot n)(w_{i}\cdot n)}),镜面反射部分包含三个函数,此外分母部分还有一个标准化因子 。字母D,F与G分别代表着一种类型的函数,各个函数分别用来近似的计算出表面反射特性的一个特定部分。三个函数分别为法线分布函数(Normal Distribution Function),菲涅尔方程(Fresnel Rquation)和几何函数(Geometry Function):
法线分布函数:估算在受到表面粗糙度的影响下,朝向方向与半程向量一致的微平面的数量。这是用来估算微平面的主要函数。
几何函数:描述了微平面自成阴影的属性。当一个平面相对比较粗糙的时候,平面表面上的微平面有可能挡住其他的微平面从而减少表面所反射的光线。几何函数采用一个材料的粗糙度参数作为输入参数,粗糙度较高的表面其微平面间相互遮蔽的概率就越高。

菲涅尔方程:菲涅尔方程描述的是在不同角度下反射的光线对比光线被折射的部分所占的比率。当光线碰撞到一个表面的时候,菲涅尔方程会根据观察角度告诉我们被反射的光线所占的百分比。利用这个反射比率和能量守恒原则,我们可以直接得出光线被折射的部分以及光线剩余的能量。
以上的每一种函数都是用来估算相应的物理参数的,而且你会发现用来实现相应物理机制的每种函数都有不止一种形式。它们有的非常真实,有的则性能高效。你可以按照自己的需求任意选择自己想要的函数的实现方法。这里我将用Unreal Engine 4中所使用的函数,其中D使用Trowbridge-Reitz GGX,F使用Fresnel-Schlick近似(Fresnel-Schlick Approximation),而G使用的是GGX与Schlick-Beckmann近似的结合体(Schlick-GGX),为了有效的估算几何部分,需要将观察方向(几何遮蔽(Geometry Obstruction))和光线方向向量(几何阴影(Geometry Shadowing))都考虑进去。我们可以使用史密斯法(Smith’s method)来把两者都纳入其中,所以是Smith’s Schlick-GGX。
Trowbridge-Reitz GGX: NDF_{GGXTR}(n,h,α) = \frac{a^2}{\pi((n\cdot h)^2 (a^2 -1)^2 + 1 )^2},其中h
表示用来与平面上微平面做比较用的半程向量,而a表示表面粗糙度,n则为法向量。
Schlick-GGX: G_{SchlickGGX}(n,v,k) = \frac{n \cdot v}{(n \cdot v) (1 - k) + k},其中n为法向量,v是我们观察的方向,这里的k是α的重映射(Remapping),分两种情况对于直接光照k = \frac{(a + 1)^2}{8},对于IBLk = \frac{a^2}{2}。
Smith’s Schlick-GGX: G(n,v,l,k) = G_{SchlickGGX}(n,v,k)G_{SchlickGGX}(n,l,k),其中n 代表法向量,v是我们观察的方向,L是光线的方向,k和上面一样.
Fresnel-Schlick: F_{Schlick}(h,v,F_0) = F_0 + (1 - F_0)(1 - (h \cdot v))^5,其中F_0称为“基础反射率”(Base Reflectivity)。它是菲涅耳反射现象在光线垂直入射表面(入射角为0°)时的反射率。F_0由材质的折射率(IOR, Index of Refraction) 决定。对于非金属(绝缘体),可通过折射率计算。所以你需要一个金属度参数,然后进行插值即可,对于非金属我可以使用0.04作为近似值结合颜色和金属度进行插值运算,例子:
vec3 F0 = vec3(0.04);
F0 = mix(F0, surfaceColor.rgb, metalness);
现在准备就绪,我们把Cook-Torrance BRDF带入进去,便可以得到Cook-Torrance反射率方程:

最后我们编写一些PBR所需的材质:
反照率:反照率(Albedo)纹理为每一个金属的纹素(Texel)(纹理像素)指定表面颜色或者基础反射率。这和我们之前使用过的漫反射纹理相当类似,不同的是所有光照信息都是由一个纹理中提取的。漫反射纹理的图像当中常常包含一些细小的阴影或者深色的裂纹,而反照率纹理中是不会有这些东西的。它应该只包含表面的颜色(或者折射吸收系数)。
法线:法线贴图纹理和我们之前在法线贴图教程中所使用的贴图是完全一样的。法线贴图使我们可以逐片段的指定独特的法线,来为表面制造出起伏不平的假象。
金属度:金属(Metallic)贴图逐个纹素的指定该纹素是不是金属质地的。根据PBR引擎设置的不同,美术师们既可以将金属度编写为灰度值又可以编写为1或0这样的二元值。
粗糙度:粗糙度(Roughness)贴图可以以纹素为单位指定某个表面有多粗糙。采样得来的粗糙度数值会影响一个表面的微平面统计学上的取向度。一个比较粗糙的表面会得到更宽阔更模糊的镜面反射(高光),而一个比较光滑的表面则会得到集中而清晰的镜面反射。某些PBR引擎预设采用的是对某些美术师来说更加直观的光滑度(Smoothness)贴图而非粗糙度贴图,不过这些数值在采样之时就马上用(1.0 – 光滑度)转换成了粗糙度。
AO:环境光遮蔽(Ambient Occlusion)贴图或者说AO贴图为表面和周围潜在的几何图形指定了一个额外的阴影因子。比如如果我们有一个砖块表面,反照率纹理上的砖块裂缝部分应该没有任何阴影信息。然而AO贴图则会把那些光线较难逃逸出来的暗色边缘指定出来。在光照的结尾阶段引入环境遮蔽可以明显的提升你场景的视觉效果。网格/表面的环境遮蔽贴图要么通过手动生成,要么由3D建模软件自动生成。
有了这些材质之后,便可以开启你的代码之旅,赶快为你的游戏加入更真实的光照吧!
友情提示!由于PBR可能需要你将光源的颜色RGB值大于1.0f,所以你还需要HDR!