微表面 PBR 模型
撰写时间:2026-07-21
修订时间:2026-07-23
概述
在上一章中,尽管我们使用Phong模型渲染出现环境光、漫反射光及高光,但这些公式只侧重于为光源建立起数学模型。例如,高光公式中只有高光强度specStrength及光泽系数shininess参与了运算。
可见,这种模型忽略了材质属性的差异性。在真实物理世界中,所有光滑的表面,若仔细看,它们都由坑坑洼洼的细小表面所构成。只不过光滑的材质(如镜面),这些微表面都朝同一个方向整齐排列;而粗糙的材质(如橡胶),这些微表面各朝不规律的方向排列。这些特性,必将影响最终的光照朝向。
此外,材质对光照的吸收、反射能力也能影响漫反射及高光的计算。例如,非金属将产生漫反射及较弱的镜面反射;而金属将主要产生镜面反射,漫反射很少或几乎没有。
微表面PBR模型 (Microfacet physically-based rendering,微表面基于物理的渲染模型) 将这些特性纳入至其数学模型中,通过引入粗糙度 (roughness) 及金属度 (metallic) 这两个最关键的基本因子,完美覆盖了从塑料、皮肤、金属、清漆等各个材质的差异,从而使得渲染效果更为逼真、绚丽。
微表面 PBR 模型的特征
微表面PBR模型有以下4个物理特性:
- 基于微表面假设:表面由无数微小、完美的镜面组成,光线在这些微表面上发生完美的镜面反射。
- 能量守恒:小范围内的高光刺眼,大范围内的高光柔和。也即反射光、折射光、漫反射光的能量总和,不能超过入射光的能量。
这是其与Phong模型最大的不同点。 - 基于BRDF:BRDF,全称为 Bidirectional Reflectance Distribution Function,中文通常译为双向反射分布函数或二向性反射分布函数。它规范了光线照到物体表面后,如何向不同方向反射的规律。从视觉上,它决定了我们从一个特定角度观察物体时,能看到多亮的反光。
- 精准模拟菲涅尔效应:菲涅尔效应,英文为 Fresnel reflection,即反射强度随观察角度不同而变化。较为典型的场景有:1、近处湖水透明能看到水中的小鱼来回游弋(反射差)、远处湖水变成镜子而倒映出高山及云朵(反射变强);2、靠近窗户玻璃能看清外面,远离窗户玻璃却能看到房间内物体的镜像;3、光滑圆球中心反光弱而边缘反光强。
三大核心分量公式
GGX法线分布函数
公式及代码实现
GGX, 或称GGX Distribution,是一个椭圆球体的法线分布函数 (normal distribution function, NDF),用以计算椭圆球体的特定朝向法线的分布密度。各向同性 (isotropic, 也即X轴与Y轴方向的粗糙度相等) 时的公式如下:
\[D_{GGX} = \frac {α^2} {𝛑 \cdot ((N \cdot H)^2 \cdot (α^2 - 1) + 1)^2}\]
其中因子α表示粗糙度,N为法线向量,H为半角向量。
具体应用代码:
GGX 命名由来
Trowbridge及Reitz最早于1975年提出使用椭圆来表示法线分布的概念。后面,Neyret使用相同函数来计算带有体积的物体 (volumetric materials)。之后,Walter等人在研究磨砂玻璃 (ground class) 的法线时将此模型命名为GGX,即ground glass unkown。命名中的unkown
应是强调其通用性,即,即使是未知的材质,也能很好地处理。当前,GGX已是渲染业界中被广泛使用的微表面法线分布计算方式。
各向同性公式推导
Eri Heitz在其 Sampling the GGX Distribution of Visible Normals 论文中所给出的最原始的公式如下:
\[D(N) = \frac {1} {𝛑\;α_x\;α_y\;\left(\frac {x_n^2} {α_x^2} + \frac {y_n^2} {α_y^2} + z_n^2\right)^2}\]
该公式建立起一个局部坐标系,而微表面中各点在该坐标系中的坐标值为\((x_n,\;y_n,\;z_n)\)。而在WebGL渲染中,为方便调用该公式,我们可将其转换为使用诸如N, H向量的形式。下面为具体的推导过程。
在大多数实际应用中,我们将材质视为各向同性,则:
\[α_x = α_y = α\]
将\(α\)代入:
\[D(N) = \frac {1} {𝛑\;α^2\;\left(\frac {x_n^2 + y_n^2} {α^2} + z_n^2\right)^2}\]
根据单位向量的性质:\(x_n^2 + y_n^2 + z_n^2 = 1\),得到\(x_n^2 + y_n^2 = 1 - z_n^2\),代入上式:
\[D(N) = \frac {1} {𝛑\;α^2\;\left(\frac {1 - z_n^2} {α^2} + z_n^2\right)^2}\]
通分、化简而得:
\[D(N) = \frac {α^2} {𝛑\;\left(z_n^2(α^2 - 1) + 1\right)^2}\]
在GGX中,各个微表面都有它们自己的微表面法线,设这些微表面法线为m。为计算这些微表面法线,建立起了一个Z轴朝向天空的局部坐标系,并且Z轴代表了宏观法线N(即所渲染像素的法线)的方向。由此,\(z_n\)为微表面法线m在Z轴(也即宏观法线N)上的投影分量。故而在数学上,
\[z_n = N \cdot m\]
再根据Cook-Torrance BRDF模型,能够成功将方向为L的光线反射到视线方向V的那些微表面,要求它们的微表面法线m必须恰好指向半角向量H (H = normalize(L + V))。因此,将H代入m,得:
\[z_n = N \cdot H\]
最后得出公式:
\[D(N) = \frac {α^2} {𝛑 \cdot ((N \cdot H)^2 \cdot (α^2 - 1) + 1)^2}\]
公式因子只剩表示粗糙度的α,法线向量N,以及半角向量H。渲染引擎可依此而设计具体函数了。
Smith 几何遮挡函数
Smith 几何遮挡模型的思想是,从入射方向L看过去的遮挡 (shadowing) 与从出射方向V看过去的遮蔽 (masking) 是两个独立的因子,总的几何衰减量等于两者相乘。故此总公式为:
\[G(n, v, l) = G_1(n, v) \times G_1(n, l)\]
对于每个方向的因子,Schlick提出了针对GGX分布的近似解析解:
\[G_1(n, v) = \frac {n \cdot v} {(n \cdot v) \times (1 - k) + k}\]
\[G_1(n, l) = \frac {n \cdot l} {(n \cdot l) \times (1 - k) + k}\]
在Schlick的原始论文中,
\[k = \frac {α} {2}\]
其中\(α\)表示粗糙度。但当\(α\)值在0.5附近时最终效果在视觉上变化较大,业界调节该因子,发现当
\[k = \frac {(α + 1)^2} {8}\]
时最终效果比较平滑。故对\(k\)的取值一般采用后者公式。
具体应用代码:
菲涅尔效应
模拟不同入射角下的反射率变化。
\[F = F_0 + (1 - F_0) \cdot (1 - cosθ)^5\]
其中,\(cosθ = h \cdot v\),\(F_0\)为材质垂直入射的基础反射率,在GLSL中,其公式为:
\[F_0 = mix(vec3(0.04), albedo, uMetallic)\]
其中的\(albedo\)为通用的反射率,glTF 2.0中使用baseColor来代替。则\(F_0\)的值介于灰色(0.04, 0.04, 0.04)与baseColor颜色值之间,可模拟表面既有金属属性又有非金属属性的混合质感。
具体应用代码:
对于所求出的\(F\),仍需下面代码联动地保障其能量守恒:
kS代表高光反射项,kD代表漫反射项。当uMetallic值为1(金属)时,漫反射项kD值为0,意味着此时金属物体的漫反射完全消失,所有颜色均靠高光kS来呈现,完美契合金属没有漫反射的物理现实。
而当uMetallic值为0(非金属)时,漫反射项kD取除去高光之外的值,也即baseColor颜色值。而高光只反射4%的白光。
着色器代码
顶点着色器:
与上一章相比,其代码无变动。
片断着色器代码变化较大:
运行应用。
材质属性:粗糙度为0.3,金属度为1.0,渲染出一个磨砂金属的效果,在球体上效果更为明显。旋转场景,观察因视线方向变化而引起的光影变化效果。各个网格物体的颜色随机,多刷几次查看不同的颜色组合。
改善材质的渲染
从上节的渲染结果中,我们发现当场景中只有环境光及一盏方向灯时,场景偏暗,且只有当观察位置在水平方向上面对灯光时,才有较亮的效果。不容易调出令人满意的灯光效果。本节予以解决这些问题。
为不同的网格物体赋予不同材质
在客户端,为每个网格物体单独设置其material属性值:
在AbstractMesh的renderInViewport方法中,予以处理:
如果网格物体存在material属性,则从中解构出roughness及metallic的值;否则,取默认值。然后传递给着色器。
场景偏暗的原因及措施
场景偏暗,正是PBR基于物理的渲染在能量守恒的物理规律下的必然表现。我们探其原因。
原因一:漫反射的余弦定律是铁律。
在片断着色器的代码中,漫反射的强度取决于:
此值决定了:
- 当表面正对灯光时,NdotL = 1,最亮。
- 当表面旋转45o时,NdotL = 0.7,亮度值降为70%。
- 当表面旋转60o时,NdotL = 0.5,亮度值降为50%。
- 当表面背向灯光时,NdotL = 0,全黑,只剩环境光。
在场景中只有一盏方向灯的情况下,物体表面90%以上的区域都处于非正对光源的状态,自然看起来整体偏暗。而这恰好是客观世界真实的物理光照。只有当光源能反射进我们的眼睛,我们才能看得到光照。
原因二:PBR的漫反射项除以PI导致光强陡降。
这个式子是能量守恒的必须要求。但这也意味着当lightIntensity值为1时,光强将变为\(\frac {1.0} {3.14} = 0.318\)。再加上表面旋转后,其角度余弦值再次衰减,亮度更低。
找到了原因,下面予以补救:
一是稍微提高环境光亮度。例如,将其值设置为(0.2, 0.2, 0.2)或(0.15, 0.15, 0.2)左右。但需注意,环境光不能设置太高,若成分值超过0.3,则面光与背光的对比度将大幅下降甚至消失,画面将呈现扁平的卡通风格。
二是适当提升lightIntensity的值。上面公式显示diffuse的值被PI除过,因此,在PBR中,lightIntensity取值自默认值1.0是错误的。我们需补偿回来。例如,若将其值设为3.14,则回到原来默认值的状态。而对于特别暗的场景,可直接设置为[4.0 ~ 5.0]。
三是加入简单的Reinhard色调映射。
片断着色器中的代码:
是导致场景光照难以调亮的元凶。当光强较高时,高光区域直接被截断为值1.0,变成一片刺眼的纯白,导致过曝,而暗部依旧很暗。也即,我们无法通过线性调节来兼顾暗部与亮部。
解决方案:使用柔和的色调映射代替粗暴的截断。将上面代码改为:
先通过Reinhard色调映射方法,将色调映射至更亮的区域,然后根据人眼对亮度的感知与物理功率是幂函数的关系的特点,对显示器所显示的颜色值取用幂指数为2.2的值进行Gamma校正。
最终效果为,即使我们将lightIntensity的值提为较高,如10.0,则高光区域会自动软化成亮灰色,不会出现刺眼的纯白色,而暗部也会被提亮,画面瞬间变得通透、明亮、高级。
渲染
调节光照系数:
对于光源颜色,将其设置为一种代表夕阳之光的暖金色,可很好地烘托整个场景。光源颜色应尽量保留饱和度,不要因为怕颜色太艳而调成灰白色,否则高光将失去生命力。
调节粗糙度的材质参数:
- 地板:roughness = 0.3,塑料材质。
- 小球:roughness = 0.05,近乎镜面的金属材质。
- 立方体:roughness = 0.6,磨砂材质,可呈现颗粒状效果。
- 挡板:roughness = 0.8,磨砂橡胶,高光弥散而柔和。
在PBR中,粗糙度从0.2到0.6之间的变化,对视觉真实感的提升最为明显。
运行应用。
整个场景立即非常明亮,高光与暗部之间的对比十分鲜明,各种材质对高光的反射非常真实,渲染效果比较惊艳。可见,PBR的光照效果不仅真实,且极易调节出亮丽的电影级质感。
