Web编程技术营地
研究、演示、创新

微表面 PBR 模型

撰写时间:2026-07-21

修订时间:2026-07-23

概述

在上一章中,尽管我们使用Phong模型渲染出现环境光漫反射光高光,但这些公式只侧重于为光源建立起数学模型。例如,高光公式中只有高光强度specStrength光泽系数shininess参与了运算。

可见,这种模型忽略了材质属性的差异性。在真实物理世界中,所有光滑的表面,若仔细看,它们都由坑坑洼洼的细小表面所构成。只不过光滑的材质(如镜面),这些微表面都朝同一个方向整齐排列;而粗糙的材质(如橡胶),这些微表面各朝不规律的方向排列。这些特性,必将影响最终的光照朝向。

此外,材质对光照的吸收、反射能力也能影响漫反射高光的计算。例如,非金属将产生漫反射及较弱的镜面反射;而金属将主要产生镜面反射漫反射很少或几乎没有。

微表面PBR模型 (Microfacet physically-based rendering,微表面基于物理的渲染模型) 将这些特性纳入至其数学模型中,通过引入粗糙度 (roughness) 及金属度 (metallic) 这两个最关键的基本因子,完美覆盖了从塑料、皮肤、金属、清漆等各个材质的差异,从而使得渲染效果更为逼真、绚丽。

微表面 PBR 模型的特征

微表面PBR模型有以下4个物理特性:

  1. 基于微表面假设:表面由无数微小、完美的镜面组成,光线在这些微表面上发生完美的镜面反射
  2. 能量守恒:小范围内的高光刺眼,大范围内的高光柔和。也即反射光折射光漫反射光的能量总和,不能超过入射光的能量。
    这是其与Phong模型最大的不同点。
  3. 基于BRDFBRDF,全称为 Bidirectional Reflectance Distribution Function,中文通常译为双向反射分布函数二向性反射分布函数。它规范了光线照到物体表面后,如何向不同方向反射的规律。从视觉上,它决定了我们从一个特定角度观察物体时,能看到多亮的反光
  4. 精准模拟菲涅尔效应菲涅尔效应,英文为 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 命名由来

TrowbridgeReitz最早于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\)为微表面法线mZ轴(也即宏观法线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属性值:

AbstractMeshrenderInViewport方法中,予以处理:

如果网格物体存在material属性,则从中解构出roughnessmetallic的值;否则,取默认值。然后传递给着色器

场景偏暗的原因及措施

场景偏暗,正是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.20.6之间的变化,对视觉真实感的提升最为明显。

运行应用

整个场景立即非常明亮,高光与暗部之间的对比十分鲜明,各种材质对高光的反射非常真实,渲染效果比较惊艳。可见,PBR的光照效果不仅真实,且极易调节出亮丽的电影级质感。

参考资源

Specifications

  1. WebGL 1.0 Specification
  2. WebGL 2.0 Specification
  3. OpenGL ES 3.0 Reference Pages

JS Libraries

  1. glMatrix.net

Scientific Papers

  1. Sampling the GGX Distribution of Visible Normals
  2. HAL: Open Science