发布于: 2023-4-3最后更新: 2023-4-3字数 00 分钟

type
status
date
slug
summary
tags
category
icon
password

1 PBR基础理论和推导

本节的理论和推导尽量简化和精简,更深入的原理和理论将在下一章阐述。
满足以下条件的光照模型才能称之为PBR光照模型:
  • 基于微平面模型(Be based on the microfacet surface model)。
  • 能量守恒(Be energy conserving)。
  • 使用基于物理的BRDF(Use a physically based BRDF)。

1.1 微平面(Microfacet)

大多数PBR技术都是基于微平面理论。在此理论下,认为在微观上所有材质表面都是由很多朝向不一的微小平面组成,有的材质表面光滑一些,有的粗糙一些。
真实世界的物体表面不一定是很多微小平面组成,也可能是带有弧度或者坑坑洼洼。但对于我们肉眼能观察到的维度,PBR的微观近似模拟方法产生的结果跟实际差别甚微。
notion image
所有材质表面由粗糙度不同的微小平面组成。左边材质更粗糙,右边的平滑一些。
当光线射入这些微平面后,通常会产生镜面反射。对于越粗糙的表面,由于其朝向更无序,反射的光线更杂乱,反之,平滑的微平面,反射的光线更平齐。
notion image
上图左边材质表面更粗糙,反射的光线更杂乱;图右的平滑许多,反射的光线更有规律。
从微观角度来说,没有任何表面是完全光滑的。由于这些微平面已经微小到无法逐像素地继续对其进行细分,因此我们只有假设一个粗糙度(Roughness,即上节4.1中提到的粗糙度)参数,然后用统计学的方法来概略的估算微平面的粗糙程度。
我们可以基于一个平面的粗糙度来计算出某个向量的方向与微平面平均取向方向一致的概率。这个向量便是位于光线向量 和视线向量之间的中间向量,被称为半角向量(Halfway Vector)
notion image
半角向量是视线和入射光的中间单位向量。
半角向量计算公式如下:
半角向量计算GLSL实现:
越多的微平面取向与其半角向量一致,材质镜面反射越强越锐利。加上引入取值0~1的粗糙度,可以大致模拟微平面的整体取向。
notion image
粗糙度从0.1~1.0的变化图。粗糙度越小,镜面反射越亮范围越小;粗糙度越大,镜面反射越弱。

1.2 能量守恒(Energy Conservation)

在微平面理论中,采用近似的能量守恒:出射光的总能量不超过入射光的总能量(自发光材质除外)。上面的粗糙度变化图可以看出,材质粗糙度越大,反射的范围越大,但整体亮度变暗。
那么PBR是如何实现近似的能量守恒呢?
为了回答这个问题,先弄清楚镜面反射(specular)和漫反射(diffuse)的区别。
一束光照到材质表面上,通常会分成反射(reflection)部分和折射(refraction)部分。反射部分直接从表面反射出去,而不进入物体内部,由此产生了镜面反射光。折射部分会进入物体内部,被吸收或者散射产生漫反射。
折射进物体内部的光如果没有被立即吸收,将会持续前进,与物体内部的微粒产生碰撞,每次碰撞有一部分能量损耗转化成热能,直至光线能量全部消耗。有些折射光线在跟微粒发生若干次碰撞之后,从物体表面射出,便会形成漫反射光。
notion image
照射在平面的光被分成镜面反射和折射光,折射光在跟物体微粒发生若干次碰撞之后,有可能发射出表面,成为漫反射。
通常情况下,PBR会简化折射光,将平面上所有折射光都视为被完全吸收而不会散开。而有一些被称为次表面散射(Subsurface Scattering)技术的着色器技术会计算折射光散开后的模拟,它们可以显著提升一些材质(如皮肤、大理石或蜡质)的视觉效果,不过性能也会随着下降。
金属(Metallic)材质会立即吸收所有折射光,故而金属只有镜面反射,而没有折射光引起的漫反射。
回到能量守恒话题。反射光与折射光它们二者之间是互斥的,被表面反射出去的光无法再被材质吸收。故而,进入材质内部的折射光就是入射光减去反射光后余下的能量。
根据上面的能量守恒关系,可以先计算镜面反射部分,此部分等于入射光线被反射的能量所占的百分比。而折射部分可以由镜面反射部分计算得出。
通过以上代码可以看出,镜面反射部分与漫反射部分的和肯定不会超过1.0,从而近似达到能量守恒的目的。

1.3 反射方程(Reflectance Equation)

渲染方程(Render Equation)是用来模拟光的视觉效果最好的模型。而PBR的渲染方程是用以抽象地描述PBR光照计算过程的特化版本的渲染方程,被称为反射方程
PBR的反射方程可抽象成下面的形式:
反射方程看似很复杂,但如果拆分各个部分加以解析,就可以揭开其神秘的面纱。
为了更好地理解反射方程,先了解辐射度量学(Radiometry)。辐射度量学是一种用来度量电磁场辐射(包括可见光)的手段。有很多种辐射度量(radiometric quantities)可以用来测量曲面或者某个方向上的光,此处只讨论和反射方程有关的一种量,它就是辐射率(Radiance),用来表示。
先用一个表展示辐射度量学涉及的概念、名词、公式等信息,后面会更加详细地介绍。
名称
符号
单位
公式
解析
辐射能量(Radiant energy)
焦耳()
-
电磁辐射能量
辐射通量(Radiant Flux)
瓦()
单位时间辐射的能量,也叫辐射功率(Radiant Power)或通量(Flux)
辐照度(Irradiance)
瓦/平方米()
到达单位面积的辐射通量
辐射度(Radiosity)
瓦/平方米()
离开单位面积的辐射通量,也叫辐出度、辐射出射度(Radiant Existance)
辐射强度(Radiant Intensity)
瓦/立体弧度()
通过单位立体角的辐射通量
辐射率(Radiance)
瓦/平方米立体弧度()
通过单位面积单位立体角的辐射通量
立体角(Solid Angle)
立体弧度,球面度(
是二维弧度在三维的扩展,1球面度等于单位球体的表面面积
辐射率被用来量化单一方向上发射来的光线的大小或者强度。辐射率是由多个物理变量集合而成的,它涉及的物理变量有以下几种:
  • 辐射通量(Radiant Flux):辐射通量用符号表示,表示一个光源输出的能量,以瓦特为单位。光是由多种不同波长的能量集合而成,每种波长与一种特定的(可见的)颜色相关。因此一个光源所放射出来的能量可以被视作这个光源包含的所有各种波长的一个函数。波长介于390nm(纳米)到700nm的光被认为是处于可见光光谱中,也就是说它们是人眼可见的波长。
notion image
上图展示了太阳光中不同波长的光所具有的能量。
传统物理学上的辐射通量将会计算这个由不同波长构成的函数的总面积,这种计算很复杂,耗费大量性能。在PBR技术中,不直接使用波长的强度,而是使用三原色编码(RGB)来简化辐射通量的计算。虽然这种简化会带来一些信息上的损失,但是这对于视觉效果上的影响基本可以忽略。
  • 立体角(Solid Angle):用符号表示,它描述投射到单位球体上的一个截面的大小或者面积。可以把立体角想象成为一个带有体积的方向:
notion image
更加形象地描述:观察者站在单位球面的中心,向着投影的方向看,在单位球体面上的投影轮廓的大小就是立体角。
  • 辐射强度(Radiant Intensity):用符号表示,它描述的是在单位球面上,一个光源向每单位立体角所投送的辐射通量。举个例子,假设一个点光源向所有方向均匀地辐射能量,辐射强度就能计算出它在一个单位面积(立体角)内的能量大小:
notion image
计算辐射强度的公式:
其中表示辐射通量除以立体角的辐射强度。
理解以上物理变量后,可以继续讨论辐射率方程了。下面方程代表的意义是:一个辐射强度为的光通过立体角辐射在区域的可被观察到的总能量。
注:原文的公式是,经推导之后,并没有平方。
notion image
辐射率是一个区域内光照量的辐射学度量,按照光的入射(或者来源)角与平面法线的夹角计算。越是斜着照射在平面上光越弱,反之越是垂直照射在表面上的光越强,类似基础光照中的漫反射颜色计算,直接等于光的方向和表面法线的点积。
上面的物理符号似乎和PBR的反射方程没有直接的关系。但是,如果将立体角跟区域都看作无限小,就可以使用辐射率来分析一束光线打在空间上一个点的通量,也就是说能够计算单束光线对单个(片元)点的辐射率影响。进一步地,将立体角转化为方向向量,将区域转化成点,因此在shader中直接使用辐射率来计算单束光线对每个片元的贡献。
实际上,当谈及光的辐射率时,通常只关注的是所有射入点的光线,这些光的辐射度总和称为辐照度(Irradiance)。理解了辐射率和辐照度,回到反射方程:
渲染方程式中代表某个点的辐射率,而无限小的入射光的立体角可以看作入射光方向向量,将用来衡量入射光与平面法线夹角对能量的影响的分量移出辐射率方程,作为反射方程的单独项
反射方程计算了点在所有视线方向上被反射出来的辐射率的总和。换言之:计算的是在方向的眼睛观察到的点的总辐照度。
反射方程里面使用的辐照度,必须要包含所有以点为中心的半球内的入射光,而不单单只是某一个方向的入射光。这个半球指的是围绕面法线的那一个半球:
notion image
为什么只计算半球而不计算整个球体呢?
因为另外一边的半球因与视线方向相反,不能被观察,也就是辐射通量贡献量为0,所以被忽略。
为了计算这个区域(半球)内的所有值,在反射方程中使用了一个称作为积分的数学符号 ,来计算半球内所有的入射向量
积分计算面积的方法,有解析(analytically)渐近(numerically)两种方法。目前尚没有可以满足渲染计算的解析法,所以只能选择离散渐近法来解决这个积分问题。
具体做法是在半球按一定的步长将反射方程离散地求解,然后再按照步长大小将所得到的结果平均化,这种方法被称为黎曼和(Riemann sum)。下面是实现的伪代码:
dW的值越小结果越接近正确的积分函数的面积或者说体积,衡量离散步长的dW可以看作反射方程中的。积分计算中我们用到的是线性连续的符号,跟代码中的dW并没有直接关系,但是这种方式有助于我们理解,而且这种离散渐近的计算方法总是可以得到一个很接近正确结果的值。值得一提的是,通过增加步骤数steps可以提高黎曼和的准确性,但计算量也会增大。
反射方程加了所有的,以各个方向射入半球并打中点的入射光,经过反射函数进入观察者眼睛的所有反射光的辐射率之和。入射光辐射度可以由光源处获得,此外还可以利用一个环境贴图来测算所有入射方向上的辐射度。
至此,反射方程中,只剩下项未描述。就是双向反射分布函数(Bidirectional Reflectance Distribution Function, BRDF),它的作用是基于表面材质属性来对入射辐射度进行缩放或者加权。

1.4 双向反射分布函数(BRDF)

双向反射分布函数(Bidirectional Reflectance Distribution Function,BRDF)是一个使用入射光方向作为输入参数的函数,输出参数为出射光,表面法线为,参数表示的是微平面的粗糙度。
BRDF函数是近似的计算在一个给定了属性的不透明表面上每个单独的光线对最终的反射光的贡献量。假如表面是绝对光滑的(比如镜子),对于所有入射光的BRDF函数都将会返回0.0,除非出射光线方向的角度跟入射光线方向的角度以面法线为中轴线完全对称,则返回1.0。
BRDF对于材质的反射和折射属性的模拟基于之前讨论过的微平面理论,想要BRDF在物理上是合理的,就必须遵守能量守恒定律。比如反射光能量总和永远不应该超过入射光。技术上来说,Blinn-Phong光照模型跟BRDF一样使用了作为输入参数,但是没有像基于物理的渲染这样严格地遵守能量守恒定律。
BRDF有好几种模拟表面光照的算法,然而,基本上所有的实时渲染管线使用的都是Cook-Torrance BRDF
Cook-Torrance BRDF分为漫反射和镜面反射两个部分:
其中是入射光中被折射的比例,是另外一部分被镜面反射的入射光。BRDF等式左边的表示的是漫反射部分,这部分叫做伦勃朗漫反射(Lambertian Diffuse)。它类似于我们之前的漫反射着色,是一个恒定的算式:
其中代表的是Albedo或表面颜色,类似漫反射表面纹理。除以是为了规格化漫反射光,为后期的BRDF积分做准备。
此处的伦勃朗漫反射跟以前用的漫反射之间的关系:以前的漫反射是用表面的漫反射颜色乘以法线与面法线的点积,这个点积依然存在,只不过是被移到了BRDF外面,写作,放在反射方程靠后的位置。
BRDF的高光(镜面反射)部分更复杂:
Cook-Torrance镜面反射BRDF由3个函数(D,F,G)和一个标准化因子构成。D,F,G符号各自近似模拟了特定部分的表面反射属性:
  • D(Normal Distribution Function,NDF):法线分布函数,估算在受到表面粗糙度的影响下,取向方向与中间向量一致的微平面的数量。这是用来估算微平面的主要函数。
  • F(Fresnel equation):菲涅尔方程,描述的是在不同的表面角下表面反射的光线所占的比率。
  • G(Geometry function):几何函数,描述了微平面自成阴影的属性。当一个平面相对比较粗糙的时候,平面表面上的微平面有可能挡住其他的微平面从而减少表面所反射的光线。
以上的每一种函数都是用来估算相应的物理参数的,而且你会发现用来实现相应物理机制的每种函数都有不止一种形式。它们有的非常真实,有的则性能高效。你可以按照自己的需求任意选择自己想要的函数的实现方法。
Epic Games公司的Brian Karis对于这些函数的多种近似实现方式进行了大量的研究。这里将采用Epic Games在Unreal Engine 4中所使用的函数,其中D使用Trowbridge-Reitz GGX,F使用Fresnel-Schlick近似法(Approximation),而G使用Smith's Schlick-GGX。

1.4.1 (Normal Distribution Function,NDF)

法线分布函数,从统计学上近似的表示了与某些(如中间)向量取向一致的微平面的比率。
目前有很多种NDF都可以从统计学上来估算微平面的总体取向度,只要给定一些粗糙度的参数以及一个我们马上将会要用到的参数Trowbridge-Reitz GGX(GGXTR):
这里的是用来测量微平面的半角向量,是表面的粗糙度,是表面法线。如果将放到表面法线和光线方向之间,并使用不同的粗糙度作为参数,可以得到下面的效果:
notion image
当粗糙度很低(表面很光滑)时,与中间向量取向一致的微平面会高度集中在一个很小的半径范围内。由于这种集中性,NDF最终会生成一个非常明亮的斑点。但是当表面比较粗糙的时候,微平面的取向方向会更加的随机,与向量取向一致的微平面分布在一个大得多的半径范围内,但是较低的集中性也会让最终效果显得更加灰暗。
Trowbridge-Reitz GGX的NDF实现代码:

1.4.2 (Fresnel equation)

菲涅尔方程定义的是在不同观察方向上,表面上被反射的光除以被折射的光的比例。在一束光击中了表面的一瞬间,菲涅尔根据表面与观察方向之间的夹角,计算得到光被反射的百分比。根据这个比例和能量守恒定律我们可以直接知道剩余的能量就是会被折射的能量。
当我们垂直观察每个表面或者材质时都有一个基础反射率,当我们以任意一个角度观察表面时所有的反射现象都会变得更明显(反射率高于基础反射率)。你可以从你身边的任意一件物体上观察到这个现象,当你以90度角观察你的桌子你会法线反射现象将会变得更加的明显,理论上以完美的90度观察任意材质的表面都应该会出现全反射现象(所有物体、材质都有菲涅尔现象)。
菲涅尔方程同样是个复杂的方程,但是幸运的是菲涅尔方程可以使用Fresnel-Schlick来近似:
表示的是表面基础反射率,这个我们可以使用一种叫做Indices of refraction(IOR)的方法计算得到。运用在球面上的效果就是你看到的那样,观察方向越是接近掠射角(grazing angle,又叫切线角,与正视角相差90度),菲涅尔现象导致的反射就越强:
notion image
菲涅尔方程中有几个微妙的地方,一个是Fresnel-Schlick算法仅仅是为电介质(绝缘体)表面定义的算法。对于金属表面,使用电介质的折射率来计算基础反射率是不合适的,我们需要用别的菲涅尔方程来计算。对于这个问题,我们需要预先计算表面在正视角(即以0度角正视表面)下的反应(),然后就可以跟之前的Fresnel-Schlick算法一样,根据观察角度来进行插值。这样我们就可以用一个方程同时计算金属和电介质了。
表面在正视角下的反映或者说基础反射率可以在这个数据库中找到,下面是Naty Hoffman的在SIGGRAPH公开课中列举的一些常见材质的值:
notion image
这里可以观察到的一个有趣的现象,所有电介质材质表面的基础反射率都不会高于0.17,这其实是例外而非普遍情况。导体材质表面的基础反射率起点更高一些并且(大多)在0.5和1.0之间变化。此外,对于导体或者金属表面而言基础反射率一般是带有色彩的,这也是为什么要用RGB三原色来表示的原因(法向入射的反射率可随波长不同而不同)。这种现象我们只能在金属表面观察的到。
金属表面这些和电介质表面相比所独有的特性引出了所谓的金属工作流的概念。也就是我们需要额外使用一个被称为金属度(Metalness)的参数来参与编写表面材质。金属度用来描述一个材质表面是金属还是非金属的。
通过预先计算电介质与导体的值,我们可以对两种类型的表面使用相同的Fresnel-Schlick近似,但是如果是金属表面的话就需要对基础反射率添加色彩。我们一般是按下面这个样子来实现的:
我们为大多数电介质表面定义了一个近似的基础反射率。取最常见的电解质表面的平均值,这又是一个近似值。不过对于大多数电介质表面而言使用0.04作为基础反射率已经足够好了,而且可以在不需要输入额外表面参数的情况下得到物理可信的结果。然后,基于金属表面特性,我们要么使用电介质的基础反射率要么就使用作来为表面颜色。因为金属表面会吸收所有折射光线而没有漫反射,所以我们可以直接使用表面颜色纹理来作为它们的基础反射率。
Fresnel Schlick近似可以用GLSL代码实现:
其中cosTheta是表面法向量与观察方向的点乘的结果。

1.4.3 (Geometry function)

几何函数模拟微平面相互遮挡导致光线的能量减少或丢失的现象。
notion image
类似NDF,几何函数也使用粗糙度作为输入参数,更粗糙意味着微平面产生自阴影的概率更高。几何函数使用由GGX和Schlick-Beckmann组合而成的模拟函数Schlick-GGX:
这里的是使用粗糙度计算而来的,用于直接光照和IBL光照的几何函数的参数:
需要注意的是这里的值取决于你的引擎怎么将粗糙度转化成,在接下来的教程中我们将会进一步讨论如何和在什么地方进行这个转换。
为了有效地模拟几何体,我们需要同时考虑两个视角,视线方向(几何遮挡)跟光线方向(几何阴影),我们可以用Smith函数将两部分放到一起:
其中v表示视线向量,表示视线方向的几何遮挡;表示光线向量,表示光线方向的几何阴影。使用Smith函数与Schlick-GGX作为可以得到如下所示不同粗糙度R的视觉效果:
notion image
几何函数是一个值域为[0.0, 1.0]的乘数,其中白色(1.0)表示没有微平面阴影,而黑色(0.0)则表示微平面彻底被遮蔽。
使用GLSL编写的几何函数代码如下:

1.4.4 Cook-Torrance反射方程(Cook-Torrance reflectance equation)

Cook-Torrance反射方程中的每一个部分我们我们都用基于物理的BRDF替换,可以得到最终的反射方程:
上面的方程并非完全数学意义上的正确。前面提到菲涅尔项代表光在表面的反射比率,它直接影响因子,意味着反射方程的镜面反射部分已经隐含了因子。因此,最终的Cook-Torrance反射方程如下(去掉了):
这个方程完整地定义了一个基于物理的渲染模型,也就是我们一般所说的基于物理的渲染(PBR)。

1.5 制作PBR材质

对PBR数学模型有了基本了解之后,我们最后要讨论的是美工应该生成怎样的材质属性,让我们可以直接用在PBR渲染管线里。PBR管线中需要的所有材质参数都可以使用纹理来定义或者模拟,使用纹理我们可以逐像素控制制定的面如何跟光线交互:这个点是否是金属,粗糙度如何又或者表面对不同波长的光有什么反映。
下面是在PBR渲染管线中经常用到的纹理:
notion image
下面的参数跟PBR在游戏引擎的应用描述的很多参数基本一致。
  • 反射率(Albedo):反射率纹理指定了材质表面每个像素的颜色,如果材质是金属那纹理包含的就是基础反射率。这个跟我们之前用过的漫反射纹理非常的类似,但是不包含任何光照信息。漫反射纹理通常会有轻微的阴影和较暗的裂缝,这些在Albedo贴图里面都不应该出现,仅仅只包含材质的颜色(金属材质是基础反射率)。
  • 法线(Normal):法线纹理跟我们之前使用的是完全一样的。法线贴图可以逐像素指定表面法线,让平坦的表面也能渲染出凹凸不平的视觉效果。
  • 金属度(Metallic):金属度贴图逐像素的指定表面是金属还是电介质。根据PBR引擎各自的设定,金属程度即可以是[0.0,1.0]区间的浮点值也可以是非0即1的布尔值。
  • 粗糙度(Roughness):粗糙度贴图逐像素的指定了表面有多粗糙,粗糙度的值影响了材质表面的微平面的平均朝向,粗糙的表面上反射效果更大更模糊,光滑的表面更亮更清晰。有些PBR引擎用光滑度贴图替代粗糙度贴图,因为他们觉得光滑度贴图更直观,将采样出来的光滑度使用(1-光滑度)= 粗糙度 就能转换成粗糙度了。
  • 环境光遮挡(Ambient Occlusion,AO):AO贴图为材质表面和几何体周边可能的位置,提供了额外的阴影效果。比如有一面砖墙,在两块砖之间的缝隙里Albedo贴图包含的应该是没有阴影的颜色信息,而让AO贴图来指定这一块需要更暗一些,这个地方光线更难照射到。AO贴图在光照计算的最后一步使用可以显著的提高渲染效果,模型或者材质的AO贴图一般是在建模阶段手动生成的。
美术可以直接根据物体在真实世界里的物理属性,来设置和调整用于渲染的基于物理的材质。
基于物理的渲染管线最大的优势在于,材质的物理属性是不变的,无论环境光怎么样设置都能得到一个接近真实的渲染结果,这让美术的人生都变得美好了。
基于物理管线的材质可以很简单的移植到不同的渲染引擎,不管光照环境如何都能正确的渲染出一个自然的结果。

2 PBR的光照实现

前面章节阐述了Cook-Torrance反射方程的理论和公式意义。这节将探讨如何将前面讲到的理论转化成一个基于直接光照的渲染器:比如点光源,方向光和聚光灯。

2.1 辐照度计算

前面章节解释了Cook-Torrance反射方程的大部分含义,但有一点未提及:具体要怎么处理场景中的辐照度(Irradiance,也就是辐射的总能量)?在计算机领域,场景的辐射率度量的是来自光源光线的辐射通量穿过指定的立体角,在这里我们假设立体角无限小,小到辐射度衡量的是光源射出的一束经过指定方向向量的光线的通量。
有了这个假设,我们又要怎么将之融合到之前教程讲的光照计算里去呢?想象我们有一个辐射通量以RGB表示为(23.47, 21.31, 20.79)的点光源,这个光源的辐射强度等于辐射通量除以所有出射方向。当为平面上某个特定的点着色的时候,所有可能的入射光方向都会经过半球,但只有一个入射方向是直接来自点光源的,又因为我们的场景中只包含有一个光源,且这个光源只是一个点,所以点所有其它的入射光方向的辐射率都应该是0.
notion image
如果我们暂时不考虑点光源的距离衰减问题,且无论光源放在什么地方入射光线的辐射率都一样大(忽略入射光角度对辐射度的影响),又因为点光源朝各个方向的辐射强度都是一样的,那么有效的辐射强度就跟辐射通量完全一样:恒定值(23.47, 21.31, 20.79)。
然而,辐射率需要使用位置作为输入参数,因为现实中的灯光根据点和光源之间距离的不同,辐射强度多少都会有一定的衰减。另外,从原始的辐射方程中我们可以发现,面法线于入射光方向向量的点积也会影响结果。
用更精炼的话来描述:在点光源直接光照的情况里,辐射率函数计算的是灯光颜色,经过到点距离的衰减之后,再经过缩放。能击中点的光线方向就是从点看向光源的方向。把这些写成代码:
你应该非常非常熟悉这段代码:这就是以前我们计算漫反射光的算法!在只有单光源直接光照的情况下,辐射率的计算方法跟我们以前的光照算法是类似的。
要注意我们这里假设点光源无限小,只是空间中的一个点。如果我们使用有体积的光源模型,那么就有很多的入射光方向的辐射率是非0的。
对那些基于点的其他类型光源我们可以用类似的方法计算辐射率,比如平行光源的入射角的恒定的且没有衰减因子,聚光灯没有一个固定的辐射强度,而是围绕一个正前方向量来进行缩放的。
这也将我们带回了在表面半球的积分。我们知道,多个单一位置的光源对同一个表面的同一个点进行光照着色并不需要用到积分,我们可以直接拿出这些数目已知的光源来,分别计算这些光源的辐照度后再加到一起,毕竟每个光源只有一束方向光能影响物体表面的辐射率。这样只需要通过相对简单的循环计算每个光源的贡献就能完成整个PBR光照计算。当我们需要使用IBL将环境光加入计算的时候我们才会需要用到积分,因为环境光可能来自任何方向。

2.2 PBR表面模型( PBR surface model)

我们先从写一个能满足前面讲到的PBR模型的片源着色器开始。首先,我们需要将表面的PBR相关属性输入着色器:
我们能从顶点着色器拿到常见的输入,另外一些是物体表面的材质属性。
在片源着色器开始的时候,我们先要做一些所有光照算法都需要做的计算:

2.2.1 直接光照(Direct lighting)

在这个教程的示例中,我们将会有4个点光源作为场景辐照度来源。为了满足反射方程我们循环处理每一个光源,计算它独自的辐射率,然后加总经过BRDF跟入射角缩放的结果。我们可以把这个循环当作是积分运算的一种实现方案。首先,计算每个光源各自相关参数:
由于我们是在线性空间进行的计算(在最后阶段处理Gamma校正),所以光源的衰减会更符合物理上的反平方律(inverse-square law)。
反平方律虽然物理学正确,但我们可能还会使用常量、线性、二次方程式来更好地控制光照衰减,即便这些衰减不是物理学正确的。
然后,我们对每个光源计算所有的Cook-Torrance BRDF分量:
我们要做的第一件事是计算高光跟漫反射之间的比例,有多少光被反射出去了又有多少产生了折射。前面的教程我们讲到过这个菲涅尔方程:
Fresnel-Schlick算法需要的F0参数就是我们之前说的基础反射率,即以0度角照射在表面上的光被反射的比例。不同材质的F0的值都不一样,可以根据材质到那张非常大的材质表里去找。在PBR金属度流水线中我们做了一个简单的假设,我们认为大部分的电介质表面的F0用0.04效果看起来很不错。而金属表面我们将F0放到albedo纹理内,这些可以写成代码如下:
如上述代码所见,非金属的F0永远是0.04,除非我们通过金属度属性在F0albedo之间进行线性插值,才能得到一个不同的非金属F0
有了F,还剩下法线分布函数跟几何函数需要计算。
在直接光照的PBR光照着色器中它们等价于如下代码:
这里值得注意的是,相较于前面理论篇教程,我们直接传入了粗糙度参数进函数。这样我们就可以对原始粗糙度做一些特殊操作。根据迪斯尼的原则和Epic Games的用法,在法线分布函数跟几何函数中使用粗糙度的平方替代原始粗糙度进行计算光照效果会更正确一些。
当这些都定义好了之后,在计算NDF和G分量就是很简单的事情了:
然后就可以计算Cook-Torrance BRDF了:
denominator项里的0.001是为了防止除0情况而特意加上的。
到这里,我们终于可以计算每个光源对反射方程的贡献了。因为菲涅尔值相当于,可用F代表任意光击中表面后被反射的部分,根据能量守恒定律我们可以用直接计算得到
表示的是光能有多少被反射了,剩下的被折射的光能我们用来表示。此外,由于金属表面不折射光,因此没有漫反射颜色,我们通过归零来实现这个规则。
有了这些数据,我们终于可以算出每个光源的出射光了:
最终结果Lo,或者说出射辐射度(Radiosity),实际上是反射方程在半球的积分的结果。这里要特别注意的是,我们将移除方程式,是因为我们已经在BRDF中乘过菲涅尔参数F了,此处不需要再乘一次。
我们没有真正的对所有可能的入射光方向进行积分,因为我们已经清楚的知道只有4个入射方向可以影响这个片元,所以我们只需要直接用循环处理这些入射光就行了。
剩下的就是要将AO运用到光照结果Lo上,我们就可以得到这个片元的最终颜色了:

2.2.2 线性和HDR渲染( Linear and HDR rendering)

以上我们假设所有计算都在线性空间,为了使用这个结果我们还需要在着色器的最后进行伽马校正(Gamma Correct),在线性空间计算光照对于PBR是非常非常重要的,所有输入参数同样要求是线性的,不考虑这一点将会得到错误的光照结果。
另外,我们希望输入的灯光参数更贴近实际的物理参数,比如他们的辐射度或者颜色值可以是一个非常宽广的值域。这样作为结果输出的Lo也将变得很大,如果我们不做处理默认会直接Clamp到0.0至1.0之间以适配低动态范围(LDR)输出方式。
为了有效解决Lo的值域问题,我们可以使用色调映射(Tone Map)和曝光控制(Exposure Map),用它们将Lo的高动态范围(HDR)映射到LDR之后再做伽马校正:
这里我们使用的是莱因哈特算法(Reinhard operator)对HDR进行Tone Map操作,尽量在伽马矫正之后还保持高动态范围。我们并没有分开帧缓冲或者使用后处理,所以我们可以直接将Tone Mapping和伽马矫正放在前向片元着色器(forward fragment shader)。
notion image
对于PBR渲染管线来说,线性空间跟高动态范围有着超乎寻常的重要性,没有这些就不可能绘制出不同灯光强度下的高光低光细节,错误的计算结果会产生难看的渲染效果。

2.2.3 完整的PBR直接光照着色器

现在唯一剩下的就是将最终的色调映射和伽玛校正的颜色传递给片元着色器的输出通道,我们就拥有了一个PBR直接光照着色器。基于完整性考虑,下面列出完整的main函数:
希望在学习了前面教程的反射方程的理论知识之后,这个shader不再会让大家苦恼。使用这个shader,4个点光源照射在金属度和粗糙度不同的球上的效果大概类似这样:
notion image
从下往上金属度的值从0.0到1.0,粗糙度从左往右从0.0增加到1.0。可以通过观察小球之间的区别理解金属度和粗糙度参数的作用。
示例的源码可以从LearnOpenGL的网站找到。

2.3 使用纹理的PBR(Textured PBR)

2.2.3小节的PBR实现中,部分重要的表面材质属性是float类型:
实际上,可以将它们用纹理代替,使用纹理的PBR可以更加精确地控制表面材质的细节,使得渲染效果更佳。Unity支持这种方法。
为了实现逐像素的控制材质表面的属性我们必须使用纹理替代单个的材质参数:
要注意美术制作的albedo纹理一般都是sRGB空间的,因此我们要先转换到线性空间再进行后面的计算。根据美术资源的不同,AO纹理也许同样需要从sRGB转换到线性空间。
将前面那些小球的材质属性替换成纹理之后,对比以前用的光照算法,PBR有了一个质的提升:
notion image
可以在这里找到带纹理的Demo源码,所有用到的纹理在这里(用了白色的AO贴图)。记住金属表面在直接光照环境中更暗是因为他们没有漫反射。在环境使用环境高光进行光照计算的情况下看起来也是正常的,这个我们在下一个教程里再说。
这里没有其他PBR渲染示例中那样令人惊艳的效果,因为我们还没有加入基于图片的光照(Image Based Lighting)技术。尽管如此,这个shader任然算是一个基于物理的渲染,即使没有IBL你也可以法线光照看起来真实了很多。

3 基于图像的光照(Image Based Lighting,IBL)

基于图像的光照(IBL)是对光源物体的技巧集合,与直接光照不同,它将周围环境当成一个大光源。IBL通常结合cubemap环境贴图,cubemap通常采集自真实的照片或从3D场景生成,这样可以将其用于光照方程:将cubemap的每个像素当成一个光源。这样可以更有效地捕获全局光照和常规感观,使得被渲染的物体更好地融入所处的环境中。
当基于图像的光照算法获得一些(全局的)环境光照时,它的输入被当成更加精密形式的环境光照,甚至是一种粗糙的全局光照的模拟。这使得IBL有助于PBR的渲染,使得物体渲染效果更真实。
在介绍IBL结合PBR之前,先回顾一下反射方程:
如之前所述,我们的主目标是解决所有入射光通过半球的积分。与直接光照不同的是,在IBL中,每一个来自周围环境的入射光都可能存在辐射,这些辐射对解决积分有着重要的作用。为解决积分有两个要求:
  • 需要用某种方法获得给定任意方向向量的场景辐射。
  • 解决积分需尽可能快并实时。
对第一个要求,相对简单,采用环境cubemap。给定一个cubemap,可以假设它的每个像素是一个单独的发光光源。通过任意方向向量采样cubemap,可以获得场景在这个方向的辐射。
获取任意方向向量的场景辐射很简单,如下:
对要求二,解决积分能只考虑一个方向的辐射,要考虑环境贴图的半球的所有可能的方向,但常规积分方法在片元着色器中开销非常大。为了有效解决积分问题,可采用预计算或预处理的方法。因此,需要深究一下反射方程:
可将上述的项拆分:
拆分后,可分开处理漫反射和镜面反射的积分。先从漫反射积分开始。

3.1 漫反射辐照度(Diffuse irradiance)

仔细分析上面方程的漫反射积分部分,发现Lambert漫反射是个常量项(颜色,折射因子)并且不依赖积分变量。因此,可见常量部分移出漫反射积分:
因此,积分只依赖(假设在环境贴图的中心)。据此,可以计算或预计算出一个新的cubemap,这个cubemap存储了用卷积(convolution)计算出的每个采样方向(或像素)的漫反射积分结果。
卷积(convolution)是对数据集的每个入口应用一些计算,假设其它所有的入口都在这个数据集里。此处的数据集就是场景辐射或环境图。因此,对cubemap的每个采样方向,我们可以顾及在半球的其它所有的采样方向。
为了卷积环境图,我们要解决每个输出采样方向的积分,通过离散地采样大量的在半球的方向并取它们辐射的平均值。采样方向的半球是以点为中心以为法平面的。
notion image
这个预计算的为每个采样方向存储了积分结果的cubemap,可被当成是预计算的在场景中所有的击中平行于表面的非直接漫反射的光照之和。这种cubemap被称为辐照度图(Irradiance map)
辐射方程依赖于位置,假设它在辐照度图的中心。这意味着所有非直接漫反射光需来自于同一个环境图,它可能打破真实的幻觉(特别是室内)。渲染引擎用放置遍布场景的反射探头(reflection probe)来解决,每个反射探头计算其所处环境的独自的辐照度图。这样,点p的辐射率(和辐射)是与其最近的反射探头的辐照度插值。这里我们假设总是在环境图的中心采样。反射探头将在其它章节探讨。
下面是cubemap环境图(下图左)和对应的辐照度图(下图右):
notion image
通过存储每个cubemap像素卷积的结果,辐照度图有点像环境的平均颜色或光照显示。从这个环境图采样任意方向,可获得这个方向的场景辐照度。

3.1.1 球体图(Equirectangular map)

球体图(Equirectangular map)有些文献翻译成全景图,它与cubemap不一样的是:cubemap需要6张图,而球体图只需要一张,并且存储的贴图有一定形变:
notion image
cubemap是可以通过一定算法转成球体图的,详见这里

3.1.2 从球体图到立方体图

直接从球体图采样出环境光照信息是可能的,但它的开销远大于直接采样立方体图(cubemap)。因此,需要将球体图先转成立方体图,以便更好地实现后面的逻辑。当然,这里也会阐述如何从作为3D环境图的球体图采样,以便大家有更多的选择权。
为了将球体图映射到立方体图,首先需要构建一个立方体模型,渲染这个立方体模型的顶点着色器如下:
在像素着色器中,将会对变形的球体图的每个部位映射到立方体的每一边,具体实现如下:
渲染出来的立方体效果如下:
notion image
对于立方体图的采样,顶点着色器如下:
对于立方体图的采样,像素着色器如下:
上述代码中,要注意在输出最终的颜色之前,做了HDR到LDR的转换和Gamma校正。
渲染的效果如下图:
notion image

3.1.3 PBR和非直接辐射度光照(indirect irradiance lighting)

辐射度图提供了漫反射部分的积分,该积分表示来自非直接的所有方向的环境光辐射之和。由于辐射度图被当成是无方向性的光源,所以可以将漫反射镜面反射合成环境光。
首先,得声明预计算出的辐射度图的sample:
通过表面的法线,获得环境光可以简化成下面的代码:
尽管如此,在之前所述的反射方程中,非直接光依旧包含了漫反射和镜面反射两个部分,所以我们需要加个权重给漫反射。下面采用了菲涅尔方程来计算漫反射因子:
由于环境光来自在半球内所有围绕着法线N的方向,没有单一的半向量去决定菲涅尔因子。为了仍然能模拟菲涅尔,这里采用了法线和视线的夹角。之前的算法采用了受表面粗糙度影响的微平面半向量,作为菲涅尔方程的输入。这里,我们并不考虑粗糙度,表面的反射因子被视作相当大。
非直接光照将沿用直接光照的相同的属性,所以,期望越粗糙的表面镜面反射越少。由于不考虑表面粗糙度,非直接光照的菲涅尔方程强度被视作粗糙的非金属表面(下图)。
notion image
为了缓解这个问题,可在Fresnel-Schlick方程注入粗糙度项(该方程的来源):
考虑了表面粗糙度后,菲涅尔相关计算最终如下:
如上所述,实际上,基于图片的光照计算非常简单,只需要单一的cubemap纹理采样。大多数的工作在于预计算或卷积环境图到辐射度图。
加入了IBL的渲染效果如下(竖向是金属度增加,水平是粗糙度增加):
notion image
本节所有代码可在这里找到。

3.2 镜面的IBL(Specular IBL)

3.1 描述的是IBL的漫反射部分,本节将讨论IBL的镜面反射部分先回顾一下反射方程:
上述的镜面反射部分(被相乘)不是恒定的,并且依赖于入射光方向和视线入射方向,尝试实时地计算所有入射光和所有入射视线的积分是几乎不可能的。Epic Games推荐折中地使用预卷积镜面反射部分的方法来解决实时渲染的性能问题,这就是分裂和近似法(split sum approximation)
分裂和近似法将镜面反射部分从反射方程分离出两个部分,这样可以单独地对它们卷积,后面在PBR的shader中为镜面的非直接IBL将它们结合起来。跟预卷积辐射度图类似,分裂和近似法需要HDR环境图作为输入。为了更好地理解分裂和近似法,下面着重关注反射方程的镜面部分:
出于跟辐射度图相同的性能问题的考虑,我们要预计算类似镜面IBL图的积分,并且用片元的法线采样这个图。辐射度图的预计算只依赖于,并且我们可以将漫反射项移出积分。但这次从BRDF可以看出,不仅仅是依赖于
如上方程所示,还依赖,并且我们不能用两个方向向量来采样预计算的cubemap。预计算所有的组合在实时渲染环境中不实际的。
Epic Games的分裂和近似法将镜面反射部分从反射方程分离出两个部分,这样可以单独地对它们卷积,后面在PBR的shader中为镜面的非直接IBL将它们结合起来。分离后的方程如下:
第一部分预过滤环境图(pre-filtered environment map),类似于辐射度图的预计算环境卷积图,但会加入粗糙度。随着粗糙度等级的增加,环境图使用更多的散射采样向量来卷积,创建出更模糊的反射。
对每个卷积的粗糙度等级,循环地在预过滤环境图的mimap等级存储更加模糊的结果。下图是5个不同粗糙度等级的预过滤环境图:
notion image
生成采样向量和它们的散射强度,需要用到Cook-Torrance BRDF的法线分布图(NDF),而其带了两个输入:法线和视线向量。当卷积环境图时并不知道视线向量,Epic Games用了更近一步的模拟法:假设视线向量(亦即镜面反射向量)总是等于输出采样向量。所以代码变成如下所示:
这种方式预过滤环境图卷积不需要关心视线方向。这就意味着当从某个角度看向下面这张图的镜面表面反射时,无法获得很好的掠射镜面反射(grazing specular reflections)。然而通常这被认为是一个较好的妥协:
notion image
第二部分镜面积分。假设所有方向的入射辐射率是全白的(那样),那就可以用给定的粗糙度和一个法线和光源方向之间的角度或来预计算BRDF的值。Epic Games存储了用变化的粗糙度来预计算每一个法线和光源方向组合的BRDF的值,该粗糙度存储于2D采样纹理(LUT)中,它被称为BRDF积分图(BRDF integration map)
2D采样纹理输出一个缩放(红色)和一个偏移值(绿色)给表面的菲涅尔方程式(Fresnel response),以便提供第二部分的镜面积分:
notion image
上图水平表示BRDF的输入,竖向表示输入的粗糙度。
有了预过滤环境图和BRDF积分图,可以在shader中将它们结合起来:

3.3 完整的IBL

首先是声明IBL镜面部分的两个纹理采样器:
接着用法线N和视线-V算出反射向量R,再结合MAX_REFLECTION_LOD和粗糙度等参数采样预过滤环境图:
然后用视线、法线的夹角及粗糙度采样BRDF查找纹理,结合预过滤环境图的颜色算出IBL的镜面部分:
自此,反射方程的非直接的镜面部分已经算出来了。可以将它和上一小节的IBL的漫反射部分结合起来:
此时可以算出由IBL的漫反射和镜面反射部分结合而成的环境光 ambient,渲染效果如下:
notion image
扩展一下,加入一些酷酷的材质
notion image
或者加载这些极好又免费的PBR 3D模型(by Andrew Maximov):
notion image
非常肯定地,加了IBL光照后,渲染效果更真实更加物理正确。下图展示了在未改变任何光照信息的情况下,在不同的预计算HDR图中的效果,它们看起来依然是物理正确的:
notion image
IBL的教程结束了,本节的代码可在球体场景纹理场景中找到。

移动游戏性能优化通用技法
移动游戏性能优化通用技法

想必玩过游戏的人会经常感叹:游戏怎么那么卡?手机怎么那么烫?耗电怎么那么快? 其实这些问题都归结于性能问题,性能优化对游戏的重要性不言而喻。 对新游戏项目,中前期可能一直在赶需求赶效果,忽略了性能优化的工作,到中后期性能瓶颈必然会凸显。如果前期做到有效组织资源,制定美术规范,完善开发流程和工具链,后期优化起来会顺手很多,少走很多弯路。


PBR基本认知和应用
PBR基本认知和应用

PBR(Physically Based Rendering)译成中文是基于物理的渲染。它是利用真实世界的原理和理论,通过各种数学方法推导或简化或模拟出一系列渲染方程,并依赖计算机硬件和图形API渲染出拟真画面的技术。