基于物理的渲染(Physically Based Rendering,PBR)是指使用基于物理原理和微平面理论建模的着色/光照模型,以及使用从现实中测量的表面参数来准确表示真实世界材质的渲染理念。
虽然如此,基于物理的渲染仍然只是对基于物理原理的现实世界的一种近似,这也就是为什么它被称为基于物理的着色(Physically based Shading) 而非物理着色(Physical Shading)的原因。
所有的PBR技术都基于微平面理论。这项理论认为,达到微观尺度之后任何平面都可以用被称为微平面(Microfacets)的细小镜面来进行描绘。根据平面粗糙程度的不同,这些细小镜面的取向排列可以相当不一致:
产生的效果就是:一个平面越是粗糙,这个平面上的微平面的排列就越混乱。这些微小镜面这样无序取向排列的影响就是,当我们特指镜面光/镜面反射时,入射光线更趋向于向完全不同的方向发散(Scatter)开来,进而产生出分布范围更广泛的镜面反射。而与之相反的是,对于一个光滑的平面,光线大体上会更趋向于向同一个方向反射,造成更小更锐利的反射:
微表面理论(Microfacet Theory)认为我们看到的表面上的一点是由很多朝向各异且光学平的微小表面组成。当光线从 l \bm l l方向照射到这点,而我们在 v \bm v v方向观察时,由于光学平面只会将光线 l \bm l l反射到关于法线对称的 v \bm v v方向,而 l \bm l l和 v \bm v v已经确定,所以只有法线朝向正好是 l \bm l l和 v \bm v v的半角向量 h \bm h h的微表面才会将光线发射到 v \bm v v方向(如下图),从而被我们看见。
光线实际上可以被认为是一束没有耗尽就不停向前运动的能量,而光束是通过碰撞的方式来消耗能量。每一种材料都是由无数微小的粒子所组成,这些粒子都能如下图所示一样与光线发生碰撞。这些粒子在每次的碰撞中都可以吸收光线所携带的一部分或者是全部的能量而后转变成为热量。
一般来说,并非所有能量都会被全部吸收,而光线也会继续沿着(基本上)随机的方向发散,然后再和其他的粒子碰撞直至能量完全耗尽或者再次离开这个表面。而光线脱离物体表面后将会协同构成该表面的(漫反射)颜色。不过在基于物理的渲染之中我们进行了简化,假设对平面上的每一点所有的折射光都会被完全吸收而不会散开。而有一些被称为次表面散射(Subsurface Scattering) 技术的着色器技术将这个问题考虑了进去,它们显著的提升了一些诸如皮肤,大理石或者蜡质这样材质的视觉效果,不过伴随而来的则是性能下降代价。
对于金属(Metallic)表面,当讨论到反射与折射的时候还有一个细节需要注意。金属表面对光的反应与非金属材料还有电介质(Dielectrics)材料表面相比是不同的。 它们遵从的反射与折射原理是相同的,但是所有的折射光都会被直接吸收而不会散开,只留下反射光或者说镜面反射光。亦即是说, 金属表面不会显示出漫反射颜色。由于金属与电介质之间存在这样明显的区别,因此它们两者在PBR渲染管线中被区别处理
折射能量 + 反射能量 <= 入射光能量
我们按照能量守恒的关系,首先计算镜面反射部分,它的值等于入射光线被反射的能量所占的百分比。然后折射光部分就可以直接由镜面反射部分计算得出:
float kS = calculateSpecularComponent(...); // 反射/镜面 部分
float kD = 1.0 - ks; // 折射/漫反射 部分
立体角是度量三维角度的量,用符号w
表示,单位为立体弧度(也叫球面度,Steradian,简写为sr),等于立体角在单位球上对应的区域的面积,单位球的表面积是 4 π 4\pi 4π,所以整个球面的立体角也是 4 π 4\pi 4π。
Light generally arrives at or leaves a surface point from a range of directions that is denoted by solid angles. solid angles represents a 3D generalization of angle formed by a region on a sphere.
d w = d s r 2 d A = ( r d θ ) ( r s i n θ d φ ) = r 2 s i n θ d θ d φ d w = d A r 2 = s i n θ d θ d φ \mathbf{dw=\frac{ds}{r^2}}\\ \ \\ \mathbf{dA=(rd\theta)(rsin\theta d\varphi)=r^2sin\theta d\theta d\varphi}\\ \ \\ \mathbf{dw = \frac{dA}{r^2} = sin\theta d\theta d\varphi}\\ dw=r2ds dA=(rdθ)(rsinθdφ)=r2sinθdθdφ dw=r2dA=sinθdθdφ
The apparent area of a surface patch according to the angle at which it is viewed
a r e a = A c o s θ \mathbf{area=Acos\theta} area=Acosθ
能量(Energy),用符号 Q Q Q表示,单位焦耳( J J J),每个光子都具有一定量的能量,和频率相关,频率越高,能量也越高。
辐射功率(Power),单位瓦特(Watts),或者焦耳/秒( J / s J/s J/s)。辐射度学中,辐射功率也被称为辐射通量(Radiant Flux)或者通量(Flux),指单位时间内通过表面或者空间区域的能量的总量,用符号$\Phi $表示,定义:
Φ = d Q d t \Phi = \frac{dQ}{dt} Φ=dtdQ
辐照度(Irradiance),指单位时间内到达单位面积的辐射能量,或到达单位面积的辐射通量,也就是通量对于面积的密度。用符号 E E E表示,单位 W / m 2 W/m^2 W/m2。定义:
E = d Φ d A E = \frac{d\Phi}{dA} E=dAdΦ
辐出度(Radiant Existance),也称为辐射出射度、辐射度(Radiosity),用符号M表示。辐出度与辐照度类似,唯一的区别在辐出度衡量的是离开表面的通量密度,辐照度衡量的是到达表面的通量密度。辐照度和辐出度都可以称为辐射通量密度(Radiant Flux Density)。
处理通量密度时,我们需要注意表面朝向和光线方向的角度。如上图所示,当光线垂直表面照射时,照射到表面上时的间距为d;而当光线倾斜照射表面时(光照向量l和表面法线n的夹角为 θ i \theta_i θi),间距为 d / c o s θ i d/cos\theta_i d/cosθi,光线间距相对垂直照射时变大了,也就是说倾斜照射时通量密度降低了 。光照角度影响通量密度在我们日常生活中有很多实际例子,地球的季节变化就是因为光照角度变化,导致通量密度发生了变化。
假定不垂直于光线传输方向的表面面积为 A A A,将它投影到垂直于光线方向得到一个虚拟表面,这个虚拟表面的面积为 A c o s θ i Acos\theta_i Acosθi,通过这两个面积的通量是相同的,均为 Φ \Phi Φ,地表面接收到的辐照度 E = Φ A E=\frac{\Phi}{A} E=AΦ,虚拟表面上的辐照度 E L = Φ A ⊥ = Φ A c o s θ i E_L=\frac{\Phi}{A^\perp}=\frac{\Phi}{Acos\theta_i} EL=A⊥Φ=AcosθiΦ,于是 E = E L c o s θ i E=E_Lcos\theta_i E=ELcosθi
辐射强度(Radiant Intensity),指通过单位立体角的辐射通量。用符号 I I I表示,单位 W / s r W/sr W/sr,定义:
I = d Φ d w I = \frac{d\Phi}{dw} I=dwdΦ
之所以引入辐射强度,是因为有时候要度量通过一个点的通量的密度,但因为点的面积是0,无法使用辐照度,所以引入辐射强度。辐射强度不会随距离变化而变化,不像点光源的辐照度会随距离增大而衰减,这是因为立体角不会随距离变化而变化。
辐射率(Radiance),指每单位面积每单位立体角的辐射通量密度。用符号 L L L表示,单位 W / m 2 s r W/m^2sr W/m2sr,定义:
L = d Φ d w d A ⊥ L = \frac{d\Phi}{dwdA^\perp} L=dwdA⊥dΦ
辐射率实际上可以看成是我们眼睛看到(或相机拍到)的物体上一点的颜色。在基于物理着色时,计算表面一点的颜色就是计算它的辐射率。
辐射率不会随距离变化而衰减,这和我们日常感受一致,在没有雾霾的干扰时,我们看到的物体表面上一点的颜色并不会随距离变化而变化。为什么辐照度会随距离增大而衰减,但是我们看到的颜色却不会衰减呢?这是因为随着距离变大,我们看到的物体上的一块区域到达视网膜的通量密度会变小,同时这块区域在视网膜表面上的立体角也会变小,正好抵消了通量密度的变化。
我们看到一个表面,实际上是周围环境的光照射到表面上,然后表面将一部分光反射到我们眼睛里。双向反射分布函数 BRDF(Bidirectional Reflectance Distribution Function)就是描述表面入射光和反射光关系的。
对于一个方向的入射光,表面会将光反射到表面上半球的各个方向,不同方向反射的比例是不同的,我们用BRDF来表示指定方向的反射光和入射光的比例关系,BRDF定义为:
f ( l , v ) = d L o ( v ) d E ( l ) \mathbf{f(l, v) = \frac{dL_o(v)}{dE(l)}} f(l,v)=dE(l)dLo(v)
我们考虑来自方向 l l l的入射光辐射率(LightIn) L i ( l ) L_i(l) Li(l)(辐射进来的单位面积单位立体角的能量),由辐射率和辐照度的定义:
L i ( l ) = d Φ d w i d A c o s θ i = d E ( l ) d w i c o s θ i \mathbf{L_i(l) = \frac{d\Phi}{dw_idAcos\theta_i} = \frac{dE(l)}{dw_icos\theta_i} } Li(l)=dwidAcosθidΦ=dwicosθidE(l)
则照射到表面来自于方向 l l l的入射光贡献的微分辐照度 (照射进来的单位时间单位面积的能量):
d E ( l ) = L i ( l ) d w i c o s θ i \mathbf{dE(l) = L_i(l)dw_icos\theta_i} dE(l)=Li(l)dwicosθi
表面反射到 v v v方向的由来自于方向 l l l的入射光贡献的微分辐射率:
d L o ( v ) = f ( l , v ) ⊗ d E ( l ) = f ( l , v ) L i ( l ) d w i c o s θ i \mathbf{dL_o(v) = f(l,v)\otimes dE(l) = f(l,v)L_i(l)dw_icos\theta_i} dLo(v)=f(l,v)⊗dE(l)=f(l,v)Li(l)dwicosθi
符号 ⊗ \otimes ⊗表示按向量的分量相乘,因为 f f f和 L i L_i Li都包含RGB三个分量。
要计算表面反射到 v v v方向的来自上半球所有方向入射光线贡献的辐射率,可以将上式对半球所有方向的光线积分:
L o ( v ) = ∫ Ω f ( l , v ) ⊗ L i ( l ) c o s θ i d w i \mathbf{L_o(v) = \int_{\Omega}f(l,v)\otimes L_i(l)cos\theta_idw_i} Lo(v)=∫Ωf(l,v)⊗Li(l)cosθidwi
上式称为反射方程 (Reflectance Equation),用来计算表面反射辐射率。
对于点光源、方向光等理想化的精准光源(Punctual Light),可以认为整个场景的辐照度是一个常数,对于点光源,辐照度随距离的平方衰减,用公式 E L = Φ 4 π r 2 E_L=\frac{\Phi}{4\pi r^2} EL=4πr2Φ计算过程可以大大简化。我们考察单个精准光源照射表面,此时表面上的一点只会被来自一个方向的一条光线照射到(而面积光源照射表面时,表面上一点会被来自多个方向的多条光线照射到),则辐射率:
L o ( v ) = ∫ Ω f ( l , v ) ⊗ d E ( l ) L o ( v ) = f ( l , v ) ⊗ E L c o s θ i \mathbf{L_o(v) = \int_{\Omega}f(l,v)\otimes dE(l)}\\ \mathbf{L_o(v) = f(l,v)\otimes E_Lcos\theta_i}\\ Lo(v)=∫Ωf(l,v)⊗dE(l)Lo(v)=f(l,v)⊗ELcosθi
其中乘以 c o s θ i cos\theta_i cosθi是因为地表面的面积比虚拟表面大,辐照度就小
对于多个精准光源,只需简单累加就可以了:
L o ( v ) = ∑ k = 1 n f ( l k , v ) ⊗ E L k c o s θ i k \mathbf{L_o(v) = \sum_{k=1}^{n}f(l_k,v)\otimes E_{Lk}cos\theta_{ik}} Lo(v)=k=1∑nf(lk,v)⊗ELkcosθik
回头看看反射方程,是对表面上半球所有方向的入射光线积分,这里面包含了来自精准光源的光线,也包括周围环境反射的光线。处理来自周围环境的光线可以大幅提高光照的真实程度,在实时图形学中,这部分光照可以用基于图像的光照 IBL(Image Based Lighting) 来模拟。
Cook-Torrance BRDF兼有漫反射和镜面反射两个部分:
f ( l , v ) = k d f l a m b e r t + k s f c o o k t o r r a n c e f(l,v) = k_d f_{lambert} + k_s f_{cook_torrance} f(l,v)=kdflambert+ksfcooktorrance
这里的 k d \bm{k_d} kd是早先提到过的入射光线中被折射部分的能量所占的比率,而 k s \bm{k_s} ks是被反射部分的比率
BRDF的左侧表示的是漫反射部分。它被称为Lambertian漫反射,这和我们之前在漫反射着色中使用的常数因子类似,用如下的公式来表示:
f l a m b e r t = c π f_{lambert} = \frac{c}{\pi} flambert=πc
c表示表面颜色(回想一下漫反射表面纹理)。除以π是为了对漫反射光进行标准化,因为前面含有BRDF的积分方程是受π影响的
Torrance-Sparrow基于微表面理论,建立了高光BRDF模型:
f ( l , v ) = F ( l , h ) G ( l , v ) D ( h ) 4 c o s θ i c o s θ o = F ( l , h ) G ( l , v ) D ( h ) 4 ( n ⋅ l ) ( n ⋅ v ) \mathbf{f(l,v) = \frac{F(l,h)G(l,v)D(h)}{4cos\theta_icos\theta_o}=\frac{F(l,h)G(l,v)D(h)}{4(n\cdot l)(n\cdot v)}} f(l,v)=4cosθicosθoF(l,h)G(l,v)D(h)=4(n⋅l)(n⋅v)F(l,h)G(l,v)D(h)
其中 n \bm n n是宏观表面法线, h \bm h h是微表面法线
下面我们介绍下Epic Games在Unreal Engine 4中所使用的函数
我们用法线分布函数 D ( h ) \mathbf{ D(h)} D(h)来描述组成表面一点的所有微表面的法线分布概率,现在可以这样理解:向NDF输入一个朝向 h \bm h h,NDF会返回朝向是 h \bm h h的微表面数占微表面总数的比例(虽然实际并不是这样,这点我们在讲推导过程的时候再讲),比如有1%的微表面朝向是 h \bm h h,那么就有1%的微表面可能将光线反射到[公式] v \bm v v方向。
N D F G G X T R ( n , h , a ) = a 2 π ( ( n ⋅ h ) 2 ( a 2 − 1 ) + 1 ) 2 \mathbf{NDF_{GGXTR}(n, h, a) = \frac{a^2}{\pi((n\cdot h)^2(a^2 - 1) + 1)^2}} NDFGGXTR(n,h,a)=π((n⋅h)2(a2−1)+1)2a2
如果我们把h当成是不同粗糙度参数下,平面法向量和光线方向向量之间的中间向量的话,我们可以得到如下图示的效果:
float D_GGX_TR(vec3 N, vec3 H, float a)
{
float a2 = a*a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH*NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / denom;
}
但实际上并不是所有微表面都能收到接受到光线,如下面左边的图有一部分入射光线被遮挡住,这种现象称为Shadowing。也不是所有反射光线都能到达眼睛,下面中间的图,一部分反射光线被遮挡住了,这种现象称为Masking。光线在微表面之间还会互相反射,如下面右边的图,这可能也是一部分漫射光的来源,在建模高光时忽略掉这部分光线。
Shadowing和Masking用几何衰减因子 G ( l , v ) \mathbf{ G(l,v)} G(l,v)来建模,输入入射和出射光线方向,输出值表示光线未被遮蔽而能从 l \bm l l反射到 v \bm v v方向的比例。
几何函数从统计学上近似的求得了微平面间相互遮蔽的比率,这种相互遮蔽会损耗光线的能量。
G S c h l i c k G G X ( n , v , k ) = n ⋅ v ( n ⋅ v ) ( 1 − k ) + k \mathbf{G_{SchlickGGX}(n, v, k) = \frac{n\cdot v}{(n\cdot v)(1-k) + k}} GSchlickGGX(n,v,k)=(n⋅v)(1−k)+kn⋅v
这里的k是α基于几何函数是针对直接光照还是针对IBL光照的重映射(Remapping) :
k d i r e c t = ( α + 1 ) 2 8 k I B L = α 2 2 \mathbf{k_{direct}=\frac{(\alpha + 1)^2}{8}}\\ \ \\ \mathbf{k_{IBL}=\frac{\alpha^2}{2}}\\ kdirect=8(α+1)2 kIBL=2α2
为了有效的估算几何部分,需要将观察方向(几何遮蔽(Geometry Obstruction))和光线方向向量(几何阴影(Geometry Shadowing))都考虑进去。我们可以使用史密斯法(Smith’s method)来把两者都纳入其中:
G ( n , v , l , k ) = G s u b ( n , v , k ) G s u b ( n , l , k ) \mathbf{G(n, v, l, k) = G_{sub}(n, v, k)G_{sub}(n, l, k)} G(n,v,l,k)=Gsub(n,v,k)Gsub(n,l,k)
使用史密斯法与Schlick-GGX作为 G s u b G_{sub} Gsub可以得到如下所示不同粗糙度的视觉效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LMtBObLT-1582885138714)(https://learnopengl-cn.github.io/img/07/01/geometry.png)]
几何函数是一个值域为[0.0, 1.0]的乘数,其中白色或者说1.0表示没有微平面阴影,而黑色或者说0.0则表示微平面彻底被遮蔽。
float GeometrySchlickGGX(float NdotV, float k)
{
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(vec3 N, vec3 V, vec3 L, float k)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float ggx1 = GeometrySchlickGGX(NdotV, k);
float ggx2 = GeometrySchlickGGX(NdotL, k);
return ggx1 * ggx2;
}
光学平面并不会将所有光线都反射掉,而是一部分被反射,一部分被折射,反射比例符合菲涅尔方程 F ( l , h ) \mathbf{ F(l,h)} F(l,h)。
如果你站在湖边,低头看脚下的水,你会发现水是透明的,反射不是特别强烈,如果你看远处的湖面,你会发现水并不透明,这说明反射非常强烈。这就是“菲涅尔效应”。
当视线垂直于表面时,反射较弱,折射越强,而当视线非垂直表面时,夹角越小(越平行于表面),反射越明显,折射越弱。
菲涅尔方程是一个相当复杂的方程式,不过幸运的是菲涅尔方程可以用Fresnel-Schlick近似法求得近似解:
F S c h l i c k ( h , v , F 0 ) = F 0 + ( 1 − F 0 ) ( 1 − ( h ⋅ v ) ) 5 \mathbf{F_{Schlick}(h, v, F_0) = F_0 + (1 - F_0)(1 - (h\cdot v))^5} FSchlick(h,v,F0)=F0+(1−F0)(1−(h⋅v))5
F 0 F_0 F0表示平面的基础反射率,它是利用所谓 折射指数IOR(Indices of Refraction) 计算得出的。然后正如你可以从球体表面看到的那样,我们越是朝球面掠角的方向上看(此时视线和表面法线的夹角接近90度)菲涅尔现象就越明显,反光就越强:
菲涅尔方程还存在一些细微的问题。其中一个问题是Fresnel-Schlick近似仅仅对电介质或者说非金属表面有定义。对于导体(Conductor)表面(金属),使用它们的折射指数计算基础折射率并不能得出正确的结果,这样我们就需要使用一种不同的菲涅尔方程来对导体表面进行计算。由于这样很不方便,所以我们预先计算出平面对于法向入射( F 0 F_0 F0)的反应(处于0度角,好像直接看向表面一样)然后基于相应观察角的Fresnel-Schlick近似对这个值进行插值,用这种方法来进行进一步的估算。这样我们就能对金属和非金属材质使用同一个公式了。
通过预先计算电介质与导体的F0值,我们可以对两种类型的表面使用相同的Fresnel-Schlick近似,但是如果是金属表面的话就需要对基础反射率添加色彩。我们一般是按下面这个样子来实现的:
vec3 F0 = vec3(0.04);
F0 = mix(F0, surfaceColor.rgb, metalness);
我们为大多数电介质表面定义了一个近似的基础反射率。F0取最常见的电解质表面的平均值,这又是一个近似值。不过对于大多数电介质表面而言使用0.04作为基础反射率已经足够好了,而且可以在不需要输入额外表面参数的情况下得到物理可信的结果。然后,基于金属表面特性,我们要么使用电介质的基础反射率要么就使用 F 0 F_0 F0来作为表面颜色。因为金属表面会吸收所有折射光线而没有漫反射,所以我们可以直接使用表面颜色纹理来作为它们的基础反射率。
Fresnel Schlick近似可以用代码表示为:
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
其中cosTheta是表面法向量n与观察方向v的点乘的结果。
随着Cook-Torrance BRDF中所有元素都介绍完毕,我们现在可以将基于物理的BRDF纳入到最终的反射率方程当中去了:
L o ( v ) = ∫ Ω ( k d c π + k s D F G 4 ( n ⋅ v ) ( n ⋅ l ) ) L i ( l ) ( n ⋅ l ) d w i \mathbf{L_o( v) = \int_{\Omega}^{}(k_d\frac{c}{\pi} + k_s\frac{DFG}{4(n\cdot v)(n\cdot l)})L_i(l)(n\cdot l) \mathrm{d}w_i} Lo(v)=∫Ω(kdπc+ks4(n⋅v)(n⋅l)DFG)Li(l)(n⋅l)dwi
这个方程现在完整的描述了一个基于物理的渲染模型,它现在可以认为就是我们一般意义上理解的基于物理的渲染也就是PBR。
在了解了PBR后面的数学模型之后,最后我们将通过说明美术师一般是如何编写一个我们可以直接输入PBR的平面物理属性的来结束这部分的讨论。PBR渲染管线所需要的每一个表面参数都可以用纹理来定义或者建模。使用纹理可以让我们逐个片段的来控制每个表面上特定的点对于光线是如何响应的:不论那个点是金属的,粗糙或者平滑,也不论表面对于不同波长的光会有如何的反应。
在下面你可以看到在一个PBR渲染管线当中经常会碰到的纹理列表,还有将它们输入PBR渲染器所能得到的相应的视觉输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olw5P2iL-1582885138715)(https://learnopengl-cn.github.io/img/07/01/textures.png)]
美术师们可以在纹素级别设置或调整这些基于物理的输入值,还可以以现实世界材料的表面物理性质来建立他们的材质数据。这是PBR渲染管线最大的优势之一,因为不论环境或者光照的设置如何改变这些表面的性质是不会改变的,这使得美术师们可以更便捷的获取物理可信的结果。在PBR渲染管线中编写的表面可以非常方便的在不同的PBR渲染引擎间共享使用,不论处于何种环境中它们看上去都会是正确的,因此看上去也会更自然。