物理渲染这个概念已经很多年了,由于UE4的原因,物理渲染在游戏行业这几年又火了起来,现在Unity也已经支持PBR,那么我们就来聊一下PBR。

什么是PBR

PBR(Physically Based Rendering)是一种渲染方式,它使用的材质是PBS(Physically Based Shader),中文名:基于物理的渲染技术。可以对光和材质之间的行为进行更加真实的建模。(之前我们已经聊过一种简单的建模,标准光照模型)PBS只考虑材质在真实物理环境下应该有的效果。PBR包围的范围会更广一些,比如GI/AO/SUN等复杂情况,这些东西加上PBS,才是PBR。

PBR并不意味着渲染出来的画面一定是像照片一样真实,虽然它原本的设计目的正是如此。程序员、TA都可以根据它的参数,设计出一些比较有意思的风格。

PBR的原理

既然PBR的优势是对光和材质之间的行为进行更加真实的建模,那么我们先来了解一下光,这个我们在标准光照模型一节中也说过。光在两种介质交接处,会发生三种行为:反射,吸收,进入物体。而进入物体的光又分为两种:吸收和离开物体。所以当时我们将离开物体的光分成两种:高光反射和漫反射。我们还用两个经验模型的公式来根据入射光的方向、法线、观察者的视角等信息,得到高光反射和漫反射的数值。而这个光照模型,其实只是最基本的光照模型。

在PBR中,我们也将离开物体的光分为两种,依然是高光反射和漫反射,但是我们计算它们的量的公式,会更加有说服力一些。而计算这个量的公式,就是著名的BRDF(Bidrectional Reflectional Reflectance Distribution Function双向反射分布函数),用于计算当一束光线沿着入射方向I到达表面某点时,有多少能量反射到了观察方向v上。BRDF也分为两项,一个是用于表示漫反射的次表面散射(SSS),还一个就是高光反射项。

根据物体的高光、漫反射属性,可以把材质分为金属(导电材质)和非金属(绝缘体)。金属材质的高光反射占据了绝大部分,非金属的漫反射占据了绝大部分。

PBS遵守能量守恒,也就是假如入射光强度为1,那么高光反射+漫反射的能量是不会超过1的。也就是说,不考虑自发光的情况,那么材质是不可能比入射光的强度更强。

BRDF

Lambert模型其实就是最简单、也是应用最广泛的漫反射BRDF。而Unity的PBS使用的是Disney的BRDF[2]的公式:

// Note: Disney diffuse must be multiply by diffuseAlbedo / PI. This is done outside of this function.
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;
}

这种学术界的公式看不懂也没关系,拿来主义,拿过来能用就行,更何况现在已经被Unity封装起来了(perceptualRoughness考虑了粗糙度,越粗糙漫反射越强,高光反射越低,能量守恒)。SSS也是一个大的课题,找个时间在单独聊。

下面是BRDF的高光反射项。在基于物理的渲染中,BRDF中的高光反射大多数都是建立在微面元理论(microfacet theory)的假设上的。微面元理论认为,物体表面实际上市由许多人眼看不到的微面元组成。虽然物体表面不是光学平滑的,但是这些微面元可以认为是光学平滑的,也就是它们具有完美的高光反射。

假设表面法线为n,这些微面元的法线m并不都等于n,因此,不同的微面元会把同一入射方向的光线反射到不同的方向上。而计算BRDF的时候,入射方向I和观察方向V都给定了,也就是说只有一部分光线可以被反射到我们眼中,而这些光线对应的微面元的法线为V+I/2,也就是我们之前说过的半角度矢量h(half-angle vector),如下图所示。

PBR

然而,这些m=h的微面元反射也并不会全部添加到BRDF的计算中,因为其中一部分会在入射方向I上被其他微面元挡住(shadowing),还有一部分会在反射方向V上被其他微面元挡住(masking),如下图。所有这些为挡住的微面元不会添加到高光反射项的计算中(虽然有一部分经过几次反射仍然能被看到,但是这个不在微面元的理论考虑范围。)

PBR

基于微面元理论,BRDF的高光反射项公式为:

PBR

公式中的F为菲涅尔反射函数,可以认为它所算出的就是法线为h的“绝对光滑”材质反射到眼睛上的光线强度,绝对光滑就表示这个像素中所有的子表面法线都为h,所以此时射到眼睛里的光强度是最强的。假如这个像素里只有一半的子表面法线等于h的话,自然光强就要减半。公式中的函数D干的就是这件事,官方名称为法线分布函数(normal distribution function,ndf),实际实现中会通过N dot H和粗糙度共同计算法线为h的面元所占的比例(比如我们之前学习的blinn-phong使用的ndf就是pow(saturate(dot(n,h)),gloss),然而其实blinn-phong并不能真实反映世界中物体的微面元法线反射分布,所以就有了更加复杂的函数,比如GGX、Beckmann)。G(I,V,H)是阴影-遮掩函数(shadowing-masking function),用于计算有多少没有被遮挡,从而可以被人眼看到的活跃的微面元所占得比例,实际实现的时候有的令G=(n•v)*(n•l)。也有的会选择接近物理真实的smith函数,大体来讲该函数求出的是给定光线和视线方向下,均匀粗糙表面的光线遮挡率。

好了,现在我们虽然完全没有讨论过BRDF的内部算法(实际上BRDF的实现方式有很多),但却已经明白了BRDF中的所有函数。实际上它的计算流程相当简单:先求出法线为h的绝对光滑反射量,再乘以法线为h的面在像素中所占的比例,最后再除去被遮挡的那些面元比例,就得到了最后的反射结果。

BRDF的具体实现算法都是为了更加接近使用光学测量仪器测量出来的真实物体的反射分布数据,然而在直观性和物理可信度之间需要一个平衡点,使得美术可以更直观的调试,又有一定的物理可信度。

刚才我们已经给出Unity漫反射的公式(Disney的BRDF[2]),目前Unity5.5的高光反射项中的法线分布函数D(h)用的是GGX模型:

inline half GGXTerm (half NdotH, half roughness)
{
	half a2 = roughness * roughness;
	half 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
}

高光反射项中的阴影-遮掩函数G(I,V,H)用的是GGX衍生出的Smith-Schlick模型:

// Ref: http://jcgt.org/published/0003/02/03/paper.pdf
inline half SmithJointGGXVisibilityTerm (half NdotL, half NdotV, half roughness)
{
    // Approximation of the above formulation (simplify the sqrt, not mathematically correct but close enough)
	half a = roughness;
	half lambdaV = NdotL * (NdotV * (1 - a) + a);
	half lambdaL = NdotV * (NdotL * (1 - a) + a);

	return 0.5f / (lambdaV + lambdaL + 1e-5f);
#endif
}

高光反射项中的菲尼尔反射函数F(I,h)就是图形学中经常使用的Schlick菲涅尔近似等式

inline half3 FresnelTerm (half3 F0, half cosA)	//half cosA = saturate(dot(light.dir, halfDir));
{
	half t = Pow5 (1 - cosA);	// ala Schlick interpoliation
	return F0 + (1-F0) * t;
}

还是那句话,公式看不懂没关系,反正这些公式也都是来源于对真实世界中各种物体的BRDF的分析,再使用不同的数学模型进行逼近。说不定什么时候又会有新的算法来更新,但是原理要明白(roughness是粗糙度,越粗糙,高光反射越低,漫反射越高,能量守恒)。

在使用Unity PBS的时候还有一点需要注意,虽然PBS材质球可以让开发者贴很多贴图(法线贴图、AO等)。但是能不贴就不贴,因为贴的话就会开启shader内部的shader feature,参考之前教程ShaderVariants这样就会让算法更复杂一些,如果只贴最基本的贴图,那么shader就会用一个比较简单的版本,节约大量计算。

PBR适合什么样的游戏

刚才我们提到blinn-phong并不能真实反映世界中物体的微面元法线反射分布,为什么?

首先,我们来看一下phong模型的高光的计算公式:pow(V•R, gloss)。即视线和宏观反射向量的点积再pow一下光滑度。

我们暂时抛开光滑度不管,那么控制反射的就是视线和宏观反射方向的夹角。换句话说,只要V和R的夹角不变,高光的强度就不会改变。那么这种结果真实吗?并不。

真实世界的反射遵照的是菲涅尔反射公式,简单的来讲就是光线和法线的夹角越大,反射越强。举个例子,我们垂直看脚下的水面时往往清澈见底(我们视线作为反射光线方向,对应的入射光线与谁的法线夹角基本为0),平视远方的水面时往往看到的更多是反射的倒影,这就是菲涅尔反射现象。(菲涅尔在美术上也用于实现边缘轮廓光)

phong光照模型从反射的基础算法开始就用了不正确的公式,更别说宏观反射向量R在真实世界中根本不存在,所以这个算法在一开始就已经不可理喻了。

而在基于物理的渲染中,前面提到的F函数所实现的就是菲涅尔反射。

接下来再来看看光滑度,在基于物理的渲染中我们一般是用和它相反的粗糙度作为参数。我们可以看出,在phong光照模型中,光照强度是随gloss单调递减的,也就是随粗糙度单调递增。换句话说,同样条件下越粗糙的表面反射的光越强。那么这种结果真实吗?并不。

我们先来大致看一下光滑表面和粗糙表面的子面法线分布情况,蓝色表示宏观法线,红色代表微面元法线:

光滑表面:

PBR

粗糙表面:

PBR

很容易理解,表面越光滑,微面元的法线越会朝着宏观法线聚拢,反之则越会远离宏观法线朝四周发散。

那么结论就很明显了,当我们的h法线和宏观法线夹角比较小时,粗糙度越小则聚拢过来的法线越多,光强就越大。当h法线和宏观法线夹角比较大时,粗糙度越小则四周的法线越会朝中间收缩,朝向四周的法线就会越少,光强也就越小。所以真实的光强并不是简单的随着粗糙度单调递增或递减,而是由h和n的夹角和粗糙度共同作用的结果。

以上表现在基于物理的光照中由D函数实现,虽然有很多种不同的实现方法,但总体的函数走势和上文所说的都是一致的。

通过以上两点的分析我们可以得出结论,phong光照模型从基础的反射到随粗糙度的光强变化关系,没有一样是照着现实世界的表现来做的。如果我们想通过phong模型调出照片级的渲染结果,那么在改变粗糙度的同时连specular颜色也要跟着作调整,而且即便如此最多也只能做到在某个角度达到与照片大概一致,一旦光源或视角发生变化,之前调的参数又会作废。这就是目前AAA大作纷纷转投基于物理光照的原因。

总的来说,PBR主要用于光源比较复杂的情况下,做出照片级的效果,同时要配合大量的光照探针、反射探针,需要HDR(因为PBS计算需要高精度,所以需要HDR,而HDR需要浮点纹理,只有OpenGL ES3.0才开始支持。由于HDR的原因MSAA也失效了,所以要在后处理中进行AA和tonemapping)。美术流程也有很大不同,不再是普通的法线+高光反射纹理,而是更细腻复杂的纹理,比如金属值纹理、高光反射纹理、粗糙度纹理、遮挡纹理、细节纹理等。

当然,假如作为美术并没有达到照片级渲染的追求,只想做点偏卡通或手绘风格的效果的话,那么使用基于物理的渲染也不会带来多少特别的好处。一切在于用户自己的选择。

PBR的优势

一个Shader走天下,方便合并DC。

能量守恒,在不同的光照条件下,都能得到一个比较正确的结果。参照下图是Unity的一个例子,白色面罩是塑料,上面有金属面纹。在如下几个不同的光照条件下(高曝光的雪地、光照变化非常强烈的晴天、比较暗的森林效果),面罩的效果相对来说都还比较正确。对比起来,如果是非PBR,美术在一个场景调整的参数,可能到另外一个场景,光照信息发生变化,那么材质球的参数就需要重新调整(比如一个场景亮,一个场景暗,那么高光就会一个亮一个暗)。所以这种情况下,PBR是省去了这个麻烦。甚至说是不同项目,美术风格类似,那么同一套资源甚至都可以复用。

PBR

下面再拿Unity文档中的一个图来解释能量守恒,从左到右,金属性从0到1。也就是D和G从小到大。高光反射率从小到大。解释一下,在这个球自身颜色保持不变的情况下,随着金属度越来越强,它的高光反射率越来越强,高光反射周围的环境越来越清晰。看上去虽然感觉上越来越亮,但是其实漫反射+高光反射出来的能量是相同的。高光反射率低的时候,反射出来一个比较粗糙的环境效果,但是能量是比较均匀的,当高光反射度比较高的时候,能量聚集在反射清晰的地方,周围就没有反射的能量了。(这个我们在上面也指出了公式中粗糙度的运用,说明了粗糙度对高光反射和漫反射的影响。粗糙度低,高光反射强,漫反射低,反之则反之。)整体上,这个球反射出来的能量一直是相同的。

PBR

IBL(image based lighting),对应Unity中的反射探针Reflect Probe,用于提供周围的环境高光效果,参与金属度高光计算,通过粗糙度来获取mipmap等级,粗糙度低的话使用清晰的mipmap,粗糙度搞的话,使用模糊的mipmap。使用图片来提供一个间接光照,与平行光、点光、聚光灯等的区别是,能最大限度模拟真实环境下的光照条件。这样美术也就更容易做出来金属感,因为金属感的制作主要靠反射和高光,所以只需要提供一个反射源,然后提高材质的金属度,就能反射出来周围环境,看上去物体的金属感会很强。(金属感是PBR的优势,如果完全不带金属效果,PBR的效果是出不来的,和原来的没太大优势)。间接光的漫反射是通过两部分,一个是ShadeSHPerPixel球面谐和基,对应Unity中的光照探针Light Probe,另外一个通过Lightmap。

美术的职责从感性到理性。之前觉得美术好不好,就说做的东西好看不好看,用了PBR之后,就要看做的东西是否正确,皮革是否像皮革,金属反射粗糙度对不对。这样就可以用客观的标准验收美术资源。制作流程也就拆分了,美术负责正确,灯光师负责好看。当正确的场景和正确的光照,就很容易出很好的效果。

Unity中的PBS

Unity已经把上面的算法封装起来了,用起来其实就是一个PBS,对这个PBS修改参数就行。但是Unity中的PBS还是分为了两类,一个叫做standard,一个叫做standard(specular)。

本质上它们没有任何区别,必须要说区别的话,它们的输入的贴图不太一样,但是结果是一样的,也就是通过这些贴图计算出两个值,一个是specColor高光,一个是diffColor漫反射。不同的工作流,不同的公式,最终都是为了这两个值。而由于这两个值都是计算出来的,金属的高光大,漫反射小,非金属的高光小,漫反射大。(如果非PBS的话,美术可以自己指定高光和漫反射颜色,可以使得高光和漫反射都大或者都小,这样就不符合能量守恒了。)

Unity中直接光的BRDF算法有3种,PC上使用第2种,手机上使用第3种,算法简单一些。

下面借用UWA的一张图,综述一下Unity中PBS的工作流程。如果把standard shader想成一个黑盒子,想要计算材质球在不同光照情况下的颜色。PBS最核心是BRDF函数,输入分为两类:第一类是材质的属性(几何信息、位置、法线等),材质的颜色信息(不管用金属工作流还是高光工作流,本质上都是为了得到漫反射颜色和高光颜色)。这样材质信息就准备好了。第二类输入是光照部分,光照部分分为两类,直接光照(平行光/点光/聚光灯的颜色和方向),间接光照。间接光照又分为两类,一类是漫反射光源(Unity的光照探针和lightmap),第二类是高光光源(Unity的反射探针),虽然这两个之间是有贡献的。

PBR

虽然并非全部原创,但还是希望转载请注明出处:电子设备中的画家|王烁 于 2017 年 8 月 22 日发表,原文链接(http://geekfaner.com/unity/blog5_PBR.html)