PBR(physics based rendering, 基于物理的渲染)已经是次世代游戏的一个标杆了,几乎所有图形人都在讨论pbr,不懂点pbr都不好意思说自己是搞渲染的了。正好最近有点空,准备好好地把pbr相关的知识体系整理一下。
一.光与物质的交互
既然是pbr,自然要从物理上说起。人眼能够看到图像,是由于光线打到物质表面反射到人眼的缘故,所以首先要讨论光线与物质表面的交互过程。大家在中学的时候都学过反射,知道反射遵循入射角=反射角的规律,如图所示。图片来自wiki反射词条。
按照反射规律,理论上只有当视线与反射光方向相同时,我们才能看到物体。但这显然与我们的生活经验不符,现实中大多数物体我们都可以从各个方向看到。是反射定律错了吗?显然不是的。事实上这是因为生活中绝大多数物体的表面都不是绝对平坦的,因此其反射方向就会散向四面八方,如图所示。
这种将物质表面视为由无数微小的面片组成的模型,称为微面元模型(microfacet model)。微面元模型是PBR中用来描述物质表面的主要模型。
除了表面凹凸不平以外,还有一种因素会导致反射光线面向四面八方。光在打到物质表面的时候,直接反射的光线只占一部分,另一部分光线会进入物质内部,经过多次折射反射后穿出表面,如下图所示。图片来自learnopengl.com
我们将这种四面八方的反射称为漫反射(diffuse reflection),而将具有明显方向性的反射称为镜面反射(specular reflection)。由于直接从表面反射的光线常常带有一定的方向性(除非表面极其粗糙),因此,下文中我们所谈到的漫反射特指经过多次折射反射后从物质内部射出的光线,而镜面反射特指直接从物质表面反射的光线。
1.1 金属与非金属
光与金属和非金属的交互过程是非常不一样的,有必要区别对待。对于非金属,光线的反射遵循我们上面描述的特点,存在从表面直接反射的分量和经过多次折射反射后从物质内部射出的分量。如果不考虑下文将要提到的菲涅尔效应,即如果入射光笔直地以平行于法线的方向照射到非金属表面的话,那么光线直接从非金属表面反射的量事实上极少(大约仅占4%左右),绝大多数出射光线是被物质吸收,折射反射后射出的。即,非金属主要以漫反射为主。
金属的表现则完全不同。金属是由金属阳离子和自由电子组成的,进入金属内部的光子会被自由电子完全吸收,无法再反射出来。因此金属只有镜面反射,没有漫反射。此外,直接从金属表面反射的光线比率也比非金属要大得多,远大于4%。
1.2 菲涅尔反射
上面介绍的非金属反射的特点与我们的日常经验似乎有一些不同。日常生活中我们时常能够在非金属上观察到强烈的镜面反射现象,比如大理石和水面等。问题出在哪里呢?事实上这和反射中的菲涅尔效应有关。
回想一下,当我们站在海边的时候,越远的海面越不容易看清水下的物体,而海平线处的海面几乎完全是镜面反射光,完全看不到水下了。而如果我们笔直地朝下看水面的话,那么水下物体将看得非常清楚,几乎看不到镜面反射光。法国物理学家菲涅尔最先系统性地研究了这一现象,并给出了一组描述反射率和折射率的方程,称为菲涅尔方程。完整的菲涅尔方程较为复杂,图形学里也较少使用,我们下文将会给出图形学较为常用的简化公式。现在我们只需记住,光线入射角越大,镜面反射的比例越大。
二. 辐射理论
光是一种电磁波,具备辐射能量。假设物体表面积为$A$,设光源在时间$t$内辐射到该表面的总能量为$Q$,那么该物体单位时间内受到的辐射功率称为通量(flux),记为$\phi$,单位为瓦特。显然我们有
$$
\phi = dQ / dt
$$
单位表面积的物体所受的辐射功率称为辐照度(irradiance),单位为瓦特每平方米,记为$E$。显然又有
$$
E = d\phi / dA
$$
照度代表了被照射的点所受到的光照。如果把相机想像成一张曝光底片的话,那么底片上每点的照度值正是图形渲染所需要计算的。然而,渲染中并不是将相机作为一张曝光底片来对待的。渲染中每个像素实际上代表的是不同方向照射到相机上的光照。因此,我们还需要寻找能够代表”某一方向上的光照”的物理量。为此首先需要給“方向”寻找良好的物理定义,我们引入立体角(solid angle)的概念。
立体角是平面上的角度在三维上的延申。它可以想象为圆锥的顶角张开的角度。类似于二维角度的弧度定义法,圆锥顶角的立体角大小定义为圆锥切割圆心在顶角的球面所得的表面积与该球半径的比值,如图。图片来自wiki。
立体角的单位为球面度(sr)。球的立体角为$4\pi$,半球为$2\pi$。
设光源入射到物体表面所张开的立体角大小为$\omega$,那么单位立体角内的辐射功率称为辐射强度(radiant intensity),记为$I$,单位为瓦特每球面度,显然
$$
I = d\phi / d\omega
$$
单位面积单位立体角所受到的辐射功率称为辐射度(radiance),记为$L$,单位为瓦特每平方米每立体角,有
$$
L = dI / dA_{proj} = dE / d\omega = \frac{d^2\phi}{dA_{proj}d\omega}
$$
式子中,$A_{proj}$为物体在光线方向上的投影面积。假设光线从光源出发,以立体角$\omega$打到表面$A$上,辐射功率为$\phi$。设入射光方向与表面法线的夹角为$\theta$。如图所示。图片来自《Real time rendeing, 3rd Edition》[1]
那么我们显然有$dA_{proj} = dAcos\theta$。
辐射度才是相机和人眼所真正测量的物理量,也是渲染所要计算的真正目标。
2.1 光学单位
辐射度理论聚焦于所有波长的波,但对于人眼来说,只有可见光波段的波是有意义的。因此,在光学里,常用另一套计量单位,其核心聚焦于人眼的感受。
光通量(Luminous flux)
与辐射度中的通量类似,光通量同样描述的是辐射功率,但它描述的是人眼所感受到的辐射功率。更准确地说,光通量是通过将光在各个波段的通量经过一个权重函数$V(\lambda)$加权得到,即
$$
\Phi = \int\phi(\lambda)V(\lambda)d\lambda
$$
权重函数$V(\lambda)$描述了人眼对各个波段的敏感程度。它由ISO和CIE标准所定义,是高度标准化的。
光通量的单位是流明(Lumen),简记为lm。流明的量纲与瓦特完全相同。在理论最大情况下,当光的能量完全集中于对人眼最敏感的波段时,有
$$
1lm = \frac{1}{683}W
$$
光强(Luminous intensity)
光强与辐射度中的辐照强度类似,它描述的是单位立体角内的光通量。它的单位是坎德拉(Candela),简写为cd。$1cd = 1lm/sr$。
照度(Illuminance)
照度与辐射度中的辐照度(irradiance)类似,描述的是单位面积内的光通量。它的单位是勒克斯(lux),简写为lx。$1lx = 1lm/m^2$。
亮度(Luminance)
亮度与辐射度理论中的辐射度(radiance)类似,描述的是单位面积、单位立体角内的光通量。它的单位是尼特(nit)。$1nit = 1lm/(m^2 * sr) = 1cd/m^2 = 1lx/sr$。
三. 渲染方程
以上述辐射理论为基础,1986年,Jim Kajiya和David Immel et al同时提出了著名的渲染方程[2][3],成为了渲染领域的核心。可以说,无论是实时渲染还是离线渲染,其本质就是求解这个方程。
完整的渲染方程的形式如下
$$
L_o(\mathbf{x}, \omega_o,\lambda,t) = \mathbf{L}_e(\mathbf{x}, \omega_o,\lambda,t) + \int_\Omega{f_r(\mathbf{x}, \omega_o, \omega_i, \lambda, t) L_i(\mathbf{x}, \omega_i, \lambda, t) cos\theta d\omega_i}
$$
式中
- $L_i$和$L_o$分别为入射光的辐射度和出射光的辐射度
- $\omega_i$和$\omega_o$分别为入射光和出射光的方向
- $L_e$为自发光
- $\lambda$为光的波长
- $t$为时间
- $\theta$为入射光与法线的夹角,$cos\theta$这一项的来源正是我们先前在辐射理论中提到的$dA_{proj} = dAcos\theta$
- $f_r$为一个描述物质材质属性的函数,类似于反射率。
渲染方程描述的概念很简单,即出射光等于自发光再加上各方向上的入射光乘以反射率。由于这个方程左右两边都有未知项($L_i$和$L_o$),因而其求解非常困难。离线渲染中的蒙特卡洛路径追踪法
(Monte Carlo Path Tracing)实际上就是以蒙特卡洛方法对该积分方程进行近似求解。
在实时渲染中,通常不考虑波长$\lambda$和时间$t$的影响。此时,渲染方程简化为
$$
L_o(\mathbf{x}, \omega_o) = \mathbf{L}_e(\mathbf{x}, \omega_o) + \int_\Omega{f_r(\mathbf{x}, \omega_o, \omega_i) L_i(\mathbf{x}, \omega_i) cos\theta d\omega_i}
$$
如果不考虑间接光照,只考虑直接光照,那么$L_i$项就是已知的,方程也就容易求解了。本文仅讨论如何利用该方程计算直接光照。间接光照我们将在后面的文章中讨论。
从方程中可以看到材质属性$f_r$是比较关键的一项。我们再对它多做一些说明。如果不考虑折射,只考虑反射,且假设反射和入射发生于同一点,则$f_r$称为双向反射分布函数(bidirectional reflectance distribution function, BRDF)。此时,积分空间为反射点表面朝向法向的半球空间。
如果不考虑反射,只考虑折射,则$f_r$称为双向透射分布函数(bidirectional transmittance distribution function, BTDF)。此时,积分空间为反射点表面背向法向的半球空间。
如果既考虑折射,又考虑反射,则$f_r$称为双向散射分布函数(bidirectional scattering distribution function, BSDF)。此时,积分空间为反射点表面的球形空间。BSDF = BRDF + BTDF。
根据第一节我们对漫反射的论述,事实上我们知道漫反射光的出射点和入射点可能不是同一个点。如果在BSDF的基础上再考虑到这种情况,那么$f_r$称为双向散射-表面反射分布函数(bidirectional scattering-surface reflectance distribution function , BSSRDF)。此时,积分空间变为整个受光照物体的表面上所有点周围的球形空间。BSSRDF描述的现象称为次表面散射(subsurface scattering),也是图形学研究的一个重要问题,像皮肤、雪、玉等材质都具备明显的次表面散射现象。
本文只讨论BRDF的情形,像次表面散射等话题将在后面的文章讨论。
四.迪士尼PBR准则
迪士尼于2012年提出了著名的迪士尼PBR准则[4],因其易用性很快在业界得到了广泛的应用。
迪士尼用以下几个主要参数来描述材质:
- 固有色(albedo), 即材质的固有颜色。
- 金属度(metallic),是一个0到1之间的标量。0表示非金属,1表示金属。
- 粗糙度(roughness),也是一个0到1之间的标量。0表示完全光滑表面,1表示完全粗糙表面(即镜面反射失去方向性,在各个方向上完全相同)。
这与传统的blinn_phong光照算法有着很大的差异。在传统的blinn_phong光照中,我们使用漫反射颜色、镜面反射颜色和光泽度三个值来描述材质。因此,pbr的美术制作流程与基于blinn_phong的美术制作流程有着很大的差异。不过,接下来我们将会看到,事实上这两套材质描述方法是可以互相转化的。
迪士尼的原始论文中还提到了许多描述材质的参数,诸如用来调节高光的specular和specular tint参数,描述次表面散射的subsurface参数,描述各向异性的anisotropic参数等。不过,本文不讨论各向异性和次表面散射,而高光调节参数更多用于控制美术效果,属于辅助参数。因此,经过简化之后,albedo、metallic和roughness三个参数是我们讨论的三个主要参数。
下面我们讨论怎样求解渲染方程。从方程中可以看到,对于直接光照来说,$L_e$为自发光颜色,$L_i$为光源颜色,$\omega_i$为入射光方向$\mathbf{l}$,$\omega_o$为视线方向$\mathbf{v}$,都很容易描述。唯一需要讨论的就是BRDF函数$f_r$ 。我们知道,BRDF可以拆为漫反射和镜面反射两部分,即
$$
f_r = f_{diff} + f_{spec}
$$
下面我们分别讨论漫反射和镜面反射。我们做出如下符号约定,以$c$代表固有色albedo,$m$代表金属度metallic,$r$代表粗糙度roughness。
4.1 漫反射
漫反射在绝大多数情况下都可以使用Lambert光照模型来描述,即
$$
f_{diff} = \frac{k_d c_d}{\pi}
$$
其中:
- $k_d$为漫反射率。根据能量守恒,有$k_d=1-k_s$,其中$k_s$为镜面反射率。我们将在镜面反射一节中介绍如何计算$k_s$。
- $c_d$为漫反射颜色。对于金属而言,$c_d=0$。对于非金属而言,$c_d$就是固有色albedo。即$c_d = c * (1 - m)$
迪士尼的论文中还提到了一种更精细的漫反射模型:
$$
f_{diff} = \frac{k_d c_d}{\pi}(1+(F_{D90}-1)(1-\cos\theta_l)^5)(1+(F_{D90}-1)(1-\cos\theta_v)^5) \
F_{D90} = 0.5 + 2 \cos \theta_{h}^2 roughness
$$
这种模型考虑到了菲涅尔效应,能够让边缘处的光线更明亮。不过,这种模型的实践价值并不是很大。感兴趣的读者可自行查阅论文[4]。
4.2 镜面反射
迪士尼提出,镜面反射的BRDF应采用Cook-Torrance模型来描述[5]:
$$
f_{spec} = \frac{DFG}{4\cos\theta_l \cos\theta_v}
$$
其中$D$、$F$、$G$为三个函数,$D$叫做法线分布函数(Normal Distribution Function, NDF),$F$为菲涅尔函数,$G$为几何遮蔽函数,或者叫自阴影函数。$\theta_l$和$\theta_v$分别为入射光方向$\mathbf{l}$和出射光方向$\mathbf{v}$与表面法线的夹角。
下面我们挨个剖析这三个函数。
4.2.1 法线分布函数$D$
法线分布函数描述的是什么呢?根据微面元模型我们知道,虽然微观上每一束镜面反射光都满足反射定律,即入射角等于反射角,但宏观上镜面反射却不会局限于单一的反射方向,而是有一个反射范围。而且根据生活经验我们知道,不同的反射方向,反射光的强度也不相同。
怎样解释这样的现象呢?我们知道,假设入射光方向为$\mathbf{l}$,我们的视线方向为$\mathbf{v}$,我们令$\mathbf{h} = \frac{\mathbf{l} + \mathbf{v}}{\left | \mathbf{l} + \mathbf{v} \right |}$,称$\mathbf{h}$为半角向量(half vector),这是PBR中一个很重要的概念。那么,显然只有那些法线与$\mathbf{h}$平行的微面元,它们的反射光才能射进我们的眼睛被我们看到,而其他的微面元我们是无法看到的。因此,镜面反射强度就与法线与$\mathbf{h}$平行的微面元所占的比例有关。而法线分布函数描述的正是这样的比例。
常用的法线分布函数大概有以下几种:
4.2.1.1 Blinn-phong[6]
$$
D = \frac{\alpha_p + 2}{2\pi}(\mathbf{n} \cdot \mathbf{h})^{\alpha_p}
$$
式中$\alpha_p$即为传统blinn-phong模型中的光泽度。由于该值对美术制作比较不友好,实践中常取变换$\alpha_p = \frac{2}{\alpha^2} - 2$。变换后的Blinn-phong NDF为
$$
D = \frac{1}{\pi\alpha^2}(\mathbf{n} \cdot \mathbf{h})^{\frac{2}{\alpha^2} - 2}
$$
4.2.1.2 Beckmann[7]
$$
D = \frac{1}{\pi\alpha^2 (\mathbf{n} \cdot \mathbf{h})^4}e^\frac{(\mathbf{n} \cdot \mathbf{h})^2-1}{\alpha^2 (\mathbf{n} \cdot \mathbf{h})^2}
$$
式中$\alpha$与blinn-phong中的光泽度$\alpha_p$的关系为$\alpha_p = \alpha^{-2} - 2$
4.2.1.3 GGX(Trowbridge-Reitz)[8][9]
$$
D = \frac{\alpha^2}{\pi((\mathbf{n} \cdot \mathbf{h})^2 (\alpha^2 - 1) + 1)^2}
$$
式中$\alpha$是一个衡量表面粗糙程度的参数。根据迪士尼的建议,取$\alpha=r^2$能够让粗糙程度随$r$值线性变化。记住$r$即为先前提到的粗糙度参数。
GGX和Blinn-phong还有Beckmann模型相比有着非常漂亮的“长尾”。也就是,当$\mathbf{n} \cdot \mathbf{h}$增长到比较大的值时,GGX衰减更慢。下图为三种模型的函数图像对比,图片来自[8]。红色为Beckmann,蓝色为Blinn-phong,绿色为GGX。可以看到Beckmann和Blinn-phong几乎没什么区别,而GGX的长尾效应非常明显。
下图为迪士尼文章中的高光对比图[4]。左侧为作为ground truth进行对比的铬样品,中间为GGX, 右侧为Beckmann。
由于这个漂亮的长尾,现在GGX已经是业界运用最为广泛的NDF。
最后我们贴上NDF镜面反射随粗糙度变化的对比图,来使读者对粗糙度的作用有个大致印象。下图来自learnopengl.com。
4.2.2 菲涅尔函数$F$
第1.2节中我们已经介绍过了菲涅尔反射的现象。物理学家菲涅尔给出过描述该现象的详细公式,但图形学中更常用的则是Schlick提出的近似公式[10],即
$$
F = F_0 + (1 - F_0)(1 - \mathbf{h} \cdot \mathbf{v})^5 \
$$
式中,$F_0$为入射光垂直于表面时的镜面反射率。如1.1节中我们提到过的那样,对于非金属而言,这个反射率对RGB分量都相等,约为4%。而对于金属而言,这个值的RGB分量就不相等了,且一般而言远大于4%。事实上,物理学家们已经通过实验测量出了多种物质的$F_0$值。如下图所示。图片来自浅墨大神的PBR白皮书:
$F_0$值也可以由物质的折射率计算得到[10]:
$$
F_0 = (\frac{n_1 - n_2}{n_1 + n_2})^2
$$
式中,$n_1$和$n_2$分别为入射介质和反射介质的折射率。通常入射介质为空气,即$n_1 = 1$,将反射介质的折射率记为$n$,则有
$$
F_0 = (\frac{n - 1}{n + 1})^2
$$
对于金属而言,通常将albedo值$c$看作$F_0$。
接下来我们需要指出的是,在Schlick的原始公式中[10], 包含的是$\mathbf{n} \cdot \mathbf{v}$ 而不是$\mathbf{h} \cdot \mathbf{v}$。但根据我们此前的分析,能够被我们的眼睛看到的微面元,其法线一定是半角向量$\mathbf{h}$而不是表面法线$\mathbf{n}$,因此我们需要使用$\mathbf{h} \cdot \mathbf{v}$。
最后我们指出,$F$描述的正是我们在4.1节即漫反射一节中提到的镜面反射率$k_s$。因此在实际计算中,可以先计算出$F$的值,然后再通过$k_d=1-F$得到漫反射率。
4.2.3 几何遮蔽函数G
是不是只要一个微面元的法线平行于半角向量$\mathbf{h}$,它的反射光就一定能被我们看到呢?答案是否定的,这是因为微面元的反射光可能会被其他微面元遮挡,如图所示。图片来自learnopengl.com
几何遮蔽函数$G$描述的正是没有被遮挡的微面元的比例。它与表面的粗糙度和法线分布有关。
历史上曾经提出过很多$G$的模型,如Implicit[11][17]、Newmann[12]、Cook-Torrance[5]、Kelemen[13]等。我们这里将着重介绍实际应用更广的Smith模型[14]。关于其他几个模型的公式,Epic Games的大神Brian Karis在他的博客中都有叙述。
原始的Smith模型只描述了出射光被遮蔽的概率。而Walter et.al对此作了改进,将G分为入射光遮蔽和出射光遮蔽两项,并认为这两项具有相同的形式[9],即
$$
G = G_1(\mathbf{l})G_1(\mathbf{v})
$$
式中,$G_1$具有如下形式[14]
$$
G_1(\mathbf{v}) = \frac{1}{1 + \Lambda(\mathbf{v})}
$$
将Smith模型与各种NDF结合后可以推导出各种$G$的公式。Walter et.al 于2007年给出了Blinn-phong、Beckmann和GGX下的公式[9], 这些公式都可以在Brian Karis的的博客上查阅到。下面我们列出一些常用公式:
4.2.3.1 Beckmann[9][15]
$$
\Lambda(\mathbf{v}) = \frac{erf(a)-1}{2} + \frac{1}{2a\sqrt{\pi}}\exp({-a^2}) \
a = \frac{1}{\alpha \tan\theta_v}
$$
4.2.3.2 Schlick-Beckmann[10]
上一小节给出的公式比较复杂,Schlick在对$G$作了一些边界条件假设后,推导出了一个近似公式[10]:
$$
G_1(\mathbf{v}) = \frac{\mathbf{n} \cdot \mathbf{v}}{\mathbf{n} \cdot \mathbf{v}(1-k) + k} \
k = \alpha \sqrt{\frac{2}{\pi}}
$$
4.2.3.3 GGX [9][15]
$$
\Lambda(\mathbf{v}) = \frac{-1 + \sqrt{1+\frac{1}{a^2}}}{2} \
a = \frac{1}{\alpha \tan\theta_v}
$$
代入可得
$$
G_1(\mathbf{v}) = \frac{2}{1 + \sqrt{1+\alpha^2 \tan\theta_v^2}} \
$$
将$\tan\theta_v^2 = \frac{1}{\cos\theta_v^2 - 1}$代入后推导如下
$$
G_1(\mathbf{v}) = \frac{2\cos\theta_v}{\cos\theta_v + \sqrt{\cos\theta_v(\cos\theta_v - \alpha^2\cos\theta_v) + \alpha^2}}
$$
UE4中相关代码如下
// Smith term for GGX
// [Smith 1967, "Geometrical shadowing of a random rough surface"]
float Vis_Smith( float Roughness, float NoV, float NoL )
{
float a = Square( Roughness );
float a2 = a*a;
float Vis_SmithV = NoV + sqrt( NoV * (NoV - NoV * a2) + a2 );
float Vis_SmithL = NoL + sqrt( NoL * (NoL - NoL * a2) + a2 );
return rcp( Vis_SmithV * Vis_SmithL );
}
可以发现该代码计算的公式与上式相比差了分子项$2\cos\theta_v$,乘起来共相差$4\cos\theta_v\cos\theta_l$。在UE4中,相差的这些是可以在其他地方计算的时候补偿回来的,并不是bug。
4.2.3.4 Schlick-GGX
该模型与Schlick-Beckmann模型几乎一致,除了$k$值不同:
$$
G_1(\mathbf{v}) = \frac{\mathbf{n} \cdot \mathbf{v}}{\mathbf{n} \cdot \mathbf{v}(1-k) + k} \
k = \frac{\alpha}{2}
$$
UE4中相关代码如下:
// Tuned to match behavior of Vis_Smith
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
float Vis_Schlick( float Roughness, float NoV, float NoL )
{
float k = Square( Roughness ) * 0.5;
float Vis_SchlickV = NoV * (1 - k) + k;
float Vis_SchlickL = NoL * (1 - k) + k;
return 0.25 / ( Vis_SchlickV * Vis_SchlickL );
}
同样差了分子项$\mathbf{n} \cdot \mathbf{v}$或者叫$\cos\theta_v$,乘起来仍然相差$4\cos\theta_v\cos\theta_l$。
4.2.3.5 Smith-Joint[15]
Heitz在2014年提出,入射光遮蔽和出射光遮蔽实际上是有关联的(correlated)而不是相互独立的。如果考虑到二者之间的关联,那么几何遮蔽函数$G$更精确的表达式应该是
$$
G = G_2(\mathbf{l},\mathbf{v}) = \frac{1}{1 + \Lambda(\mathbf{l}) + \Lambda(\mathbf{v})}
$$
该模型称为Smith-Joint模型。将GGX代入该模型可以得到一个$G$的计算公式,但非常复杂。UE4提出了一个简化公式,代码如下
// Appoximation of joint Smith term for GGX
// [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
float Vis_SmithJointApprox( float Roughness, float NoV, float NoL )
{
float a = Square( Roughness );
float Vis_SmithV = NoL * ( NoV * ( 1 - a ) + a );
float Vis_SmithL = NoV * ( NoL * ( 1 - a ) + a );
// Note: will generate NaNs with Roughness = 0. MinRoughness is used to prevent this
return 0.5 * rcp( Vis_SmithV + Vis_SmithL );
}
4.2.3.6 其他非物理模型
除了以上这些基于物理的模型以外,还存在一些非物理的模型,如Implicit[11][17]、Newmann[12]、Kelemen[13]等。本文这里不再逐一罗列,读者可查阅Brian Karis的的博客或论文[15]。
五. Unity引擎PBR代码分析
到目前为止我们已经介绍了PBR的基本理论。光说不练假把式,下面我们以Unity引擎为例,来看看实际的PBR代码是什么样的。文中选用的Unity版本为2018.2.4f1。
Unity引擎的shader代码均可在引擎目录/Editor/Data/CGIncludes下找到。我们首先关注LightingStandard函数,该函数是Unity默认使用的光照模型,位于UnityPBSLighting.cginc下:
inline half4 LightingStandard (SurfaceOutputStandard s, float3 viewDir, UnityGI gi)
{
s.Normal = normalize(s.Normal);
half oneMinusReflectivity;
half3 specColor;
s.Albedo = DiffuseAndSpecularFromMetallic (s.Albedo, s.Metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
// shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
// this is necessary to handle transparency in physically correct way - only diffuse component gets affected by alpha
half outputAlpha;
s.Albedo = PreMultiplyAlpha (s.Albedo, s.Alpha, oneMinusReflectivity, /*out*/ outputAlpha);
half4 c = UNITY_BRDF_PBS (s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
c.a = outputAlpha;
return c;
}
我们可以看到,该函数首先通过DiffuseAndSpecularFromMetallic函数,将albedo和metallic变换为diffuse颜色和specular颜色。DiffuseAndSpecularFromMetallic函数位于UnityStandardUtils.cginc下:
inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
{
specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
return albedo * oneMinusReflectivity;
}
inline half OneMinusReflectivityFromMetallic(half metallic)
{
half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}
其中,unity_ColorSpaceDielectricSpec是一个全局的宏定义,位于UnityCG.cginc下
#define unity_ColorSpaceDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04)
可见,Unity首先计算$F_0$项,非金属取常数0.04,金属取albedo:
specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
然后计算漫反射率$k_d$。这里与我们先前所论述的稍有不同。按我们先前所论述的,应当先计算菲涅尔项F,然后以$k_d=1-F$得到$k_d$。而Unity这里作了一些简化,取了如下公式:
$$
k_d = (1 - m)(1 - F_{0dieletric}) \
F_{0dieletric} = 0.04
$$
这里的$1-m$实际上是从漫反射颜色的公式$c_d=(1-m)c$中给搬了过来,因此和我们提到的公式的区别就是以常数0.04取代了$F$。对于金属而言,反正漫反射为0,因此无所谓。而对于非金属而言,$F_0=0.04$,而$F>F_0$,因此Unity计算的漫反射实际上会更强一些,造成的结果就是削弱了非金属的菲涅尔反射效果。
接下来我们看UNITY_BRDF_PBS。这个宏定义于UnityPBSLighting.cginc:
// Default BRDF to use:
#if !defined (UNITY_BRDF_PBS) // allow to explicitly override BRDF in custom shader
// still add safe net for low shader models, otherwise we might end up with shaders failing to compile
#if SHADER_TARGET < 30 || defined(SHADER_TARGET_SURFACE_ANALYSIS) // only need "something" for surface shader analysis pass; pick the cheap one
#define UNITY_BRDF_PBS BRDF3_Unity_PBS
#elif defined(UNITY_PBS_USE_BRDF3)
#define UNITY_BRDF_PBS BRDF3_Unity_PBS
#elif defined(UNITY_PBS_USE_BRDF2)
#define UNITY_BRDF_PBS BRDF2_Unity_PBS
#elif defined(UNITY_PBS_USE_BRDF1)
#define UNITY_BRDF_PBS BRDF1_Unity_PBS
#else
#error something broke in auto-choosing BRDF
#endif
#endif
可见,根据不同的设置,Unity共有三种不同的BRDF。它们定义于UnityStandardBRDF.cginc内。BRDF1为标准的基于Disney准则和微面元模型的BRDF, 性能消耗也最大,而BRDF2和BRDF3都有一定的简化。我们选取最标准的BRDF1进行分析。对代码进行一定的简化,删去部分宏以及全局光照之后如下:
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
float3 normal, float3 viewDir,
UnityLight light, UnityIndirect gi)
{
float perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness);
float3 halfDir = Unity_SafeNormalize (float3(light.dir) + viewDir);
half nv = saturate(dot(normal, viewDir));
half nl = saturate(dot(normal, light.dir));
float nh = saturate(dot(normal, halfDir));
half lv = saturate(dot(light.dir, viewDir));
half lh = saturate(dot(light.dir, halfDir));
// Diffuse term
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;
// Specular term
// HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH
float roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
// GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping.
roughness = max(roughness, 0.002);
half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
float D = GGXTerm (nh, roughness);
half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
// specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value
specularTerm = max(0, specularTerm * nl);
// To provide true Lambert lighting, we need to be able to kill specular completely.
specularTerm *= any(specColor) ? 1.0 : 0.0;
half3 color = diffColor * light.color
+ specularTerm * light.color * FresnelTerm (specColor, lh);
return half4(color, 1);
}
首先看漫反射部分:
float perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness);
float3 halfDir = Unity_SafeNormalize (float3(light.dir) + viewDir);
half nv = saturate(dot(normal, viewDir));
half nl = saturate(dot(normal, light.dir));
float nh = saturate(dot(normal, halfDir));
half lv = saturate(dot(light.dir, viewDir));
half lh = saturate(dot(light.dir, halfDir));
// Diffuse term
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;
可见Unity使用的是Disney在原始论文中提出的更复杂的漫反射模型[4]:
half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
{
half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
// Two schlick fresnel term
half lightScatter = (1 + (fd90 - 1) * Pow5(1 - NdotL));
half viewScatter = (1 + (fd90 - 1) * Pow5(1 - NdotV));
return lightScatter * viewScatter;
}
perceptualRoughnessh即为Disney漫反射公式中的roughness项:
float SmoothnessToPerceptualRoughness(float smoothness)
{
return (1 - smoothness);
}
可见整个漫反射计算过程基本上完全是按照Disney漫反射模型来的,非常标准,除了没有除以$\pi$。Unity也在注释中提到这是为了和老的光照模型保持兼容性,因为如果除以$\pi$的话就会明显比老光照模型暗。这也是不得不付出的代价,老的光照系统不符合物理,新的光照系统也只能去兼容它而不是去推倒它,毕竟引擎最终还是要对美术效果负责而不是对物理正确性负责。
下面再来看specular的部分:
float roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
// GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping.
roughness = max(roughness, 0.002);
half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
float D = GGXTerm (nh, roughness);
half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
// specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value
specularTerm = max(0, specularTerm * nl);
// To provide true Lambert lighting, we need to be able to kill specular completely.
specularTerm *= any(specColor) ? 1.0 : 0.0;
half3 color = diffColor * light.color
+ specularTerm * light.color * FresnelTerm (specColor, lh);
从函数名字上看,这里采用了GGX作为NDF,以及Smith Joint GGX作为几何遮蔽函数G(代码中叫V)。我们来看几何遮蔽函数的实现:
inline half SmithJointGGXVisibilityTerm (half NdotL, half NdotV, half roughness)
{
half a = roughness;
half lambdaV = NdotL * (NdotV * (1 - a) + a);
half lambdaL = NdotV * (NdotL * (1 - a) + a);
return 0.5f / (lambdaV + lambdaL + 1e-5f);
}
可以看到使用的是UE4中用到的Smith-Joint模型在GGX下的近似公式。而GGX NDF和菲涅尔项的实现就比较标准了:
inline float GGXTerm (float NdotH, float roughness)
{
float a2 = roughness * roughness;
float d = (NdotH * a2 - NdotH) * NdotH + 1.0f; // 2 mad
return UNITY_INV_PI * a2 / (d * d + 1e-7f); // This function is not intended to be running on Mobile,
// therefore epsilon is smaller than what can be represented by half
}
inline half3 FresnelTerm (half3 F0, half cosA)
{
half t = Pow5 (1 - cosA); // ala Schlick interpoliation
return F0 + (1-F0) * t;
}
另外一个和通常做法不太一样的是,在specularTerm中,Unity并没有采用Cook-Torrance BRDF(除以$4\cos\theta_l \cos\theta_v$),而是采用了Torrance-Sparrow BRDF[16]:
half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
specularTerm = max(0, specularTerm * nl);
这里的$\mathbf{n} \cdot \mathbf{l}$是渲染方程中的固有项。
至此我们把Unity的PBR视线基本过了一遍。可以看到有的地方和标准的PBR做法还是稍有不同,不知道人们总说Unity画质不如UE4,这会不会是其中的原因之一呢?
总结
本文中,我们简要介绍了PBR的基本理论框架,并通过对Unity引擎的代码分析来学习实践中的PBR是怎么写的。我们在本文中介绍的理论和公式都很简要,远远称不上详细,感兴趣的读者可进一步查阅相关资料进行学习。不过,我们必须指出的是,作为游戏工程师,渲染技术最终还是要为美术效果服务的,切不可拘泥于物理公式。当物理公式妨碍到美术效果的实现时,应当具有对公式做出不符合物理的hack的勇气。
参考文献
[1]Akenine-Moller, Tomas, Eric Haines, and Naty Hoffman. Real-time rendering. AK Peters/CRC Press, 2018.
[2]Kajiya, James T. "The rendering equation." ACM SIGGRAPH computer graphics. Vol. 20. No. 4. ACM, 1986.
[3]Immel, David S., Michael F. Cohen, and Donald P. Greenberg. "A radiosity method for non-diffuse environments." ACM Siggraph Computer Graphics. Vol. 20. No. 4. ACM, 1986.
[4]Burley, Brent, and Walt Disney Animation Studios. "Physically-based shading at disney." ACM SIGGRAPH. Vol. 2012. 2012.
[5]Cook, Robert L., and Kenneth E. Torrance. "A reflectance model for computer graphics." ACM Transactions on Graphics (TOG) 1.1 (1982): 7-24.
[6]Blinn, James F. "Models of light reflection for computer synthesized pictures." ACM SIGGRAPH computer graphics. Vol. 11. No. 2. ACM, 1977.
[7]Beckmann, Petr, and Andre Spizzichino. "The scattering of electromagnetic waves from rough surfaces." Norwood, MA, Artech House, Inc., 1987, 511 p. (1987).
[8]Trowbridge, T. S., and Karl P. Reitz. "Average irregularity representation of a rough surface for ray reflection." JOSA 65.5 (1975): 531-536.
[9]Walter, Bruce, et al. "Microfacet models for refraction through rough surfaces." Proceedings of the 18th Eurographics conference on Rendering Techniques. Eurographics Association, 2007.
[10]Schlick, Christophe. "An inexpensive BRDF model for physically‐based rendering." Computer graphics forum. Vol. 13. No. 3. Edinburgh, UK: Blackwell Science Ltd, 1994.
[11]Hoffman, Naty. "Background: physics and math of shading." Physically Based Shading in Theory and Practice 24.3 (2013): 211-223.
[12]Neumann, László, Attila Neumannn, and László Szirmay‐Kalos. "Compact metallic reflectance models." Computer Graphics Forum. Vol. 18. No. 3. Oxford, UK and Boston, USA: Blackwell Publishers Ltd, 1999.
[13]Kelemen, Csaba, and Laszlo Szirmay-Kalos. "A microfacet based coupled specular-matte BRDF model with importance sampling." Eurographics Short Presentations. Vol. 25. 2001.
[14]Smith, Bruce. "Geometrical shadowing of a random rough surface." IEEE transactions on antennas and propagation 15.5 (1967): 668-671.
[15]Heitz, Eric. "Understanding the masking-shadowing function in microfacet-based BRDFs." (2014).
[16]Torrance, Kenneth E., and Ephraim M. Sparrow. "Theory for off-specular reflection from roughened surfaces." Josa 57.9 (1967): 1105-1114.
[17]McAuley, Stephen, et al. "Physically based shading in theory and practice." ACM SIGGRAPH 2013 Courses. ACM, 2013.

评论(0)
暂无评论