glTF(2.1): PBR

去年针对glTF的PBR材质,写过一篇文章。但重读这篇文章,觉得自己没讲到点子上。今天终于有时间,想着重新梳,重写一下。

Rasterization的不足

如上图,给你一个画布,任务是绘制这个多面体,你会怎么画呢?相信所有正常的人都是先绘制顶点,然后构成一个个的三角面,填充其中的颜色。

假如有100个物体呢,逻辑上并没有不同,一个一个的画就可以了。通常我们说的Rasterization也是这个思路。如图2,为了在屏幕上渲染物体,Rasterization有两个循环:

  • Loop objects
  • Loop pixels

Rasterization的好处是性能高,能够满足实时性的要求。它的首要问题是如何把一个3D的物体渲染到2D的平面,更擅长处理物体的几何形状,但在渲染效果上并不真实。比如一个鸟巢的模型,固然很好看,但一眼就能看出来是假的,相比一个倾斜摄影的模型,可能数据粗糙,但能看出来是真的。这里强调一下,不是说渲染的效果不好,而是不真实,是否真实的标准就是照片,Photorealism。

PBR的优势

我们之所以能看见一个物体,最大的功劳并不是物体本身,而是看不见的光。相机提供了一套系统的光学模型来捕捉光子成像,这是保证照片真实的理论基础。因此,为了更真实的渲染物体,于是便有了 ray tracing的渲染算法,也就是Physically based rendering(PBR)渲染模型。

Whitted最早的一篇论文《An Improved Illumination Model for Shaded Display》提供了第一版ray tracing algorithm,相信任何一个高中或初中生都能看懂,但想法很不简单。Whitted本来是研究原子弹的,正是这个基础,他能看到凡人看不到的光,并且还玩的很溜,在他的眼里,光就是由光子形成的射线,要想真实的渲染物体,我们要做的就是trace ray。

如上图,我们假设眼睛会放光,射线打到桌子上,然后在该点寻找光源,此处有两个光源,左侧的被苹果挡住了,所以不会有光,但右侧的光源是可见的,因此,该点是可见的,但强度略低。我们对可见范围内的每一个点都发送一条射线,然后判断是否和物体相交,如果相交,交点和光源之间是否有遮盖。这就是Ray tracing的简单原理,这个流程和Rasterization正好正好相反:

  • Loop pixels
  • Loop objects

PBR和Rasterization最大的不同,在于前者提供了一种可以量化光的算法和策略,而Rasterization只是对光的模拟,这个灯光满足这个场景,换一个场景就很难有同样的效果。

Light Equation

然而,Whitted-style ray tracing并不完美,出于性能的考虑,只考虑了一次漫反射,好比泰勒展开式,只考虑了第一阶导数,高阶导数忽略不计,所幸算法本身是客观的。现在,我们的任务就是更上一层楼,找到这个完美的光学公式。

如上图,假设在这个屋子里有一盏灯,不停的向四面发射光子,光子打在窗户上,可能折射出去了,也可能反射到墙壁,墙壁上可能也会有漫反射,可能会被吸收(热量)。图片中的任意一点主要有光源(incoming),一个是自发光,,一个是来自灯泡的direct ray或其他物体的indirect ray,可能是反射,也可能是折射的形式。而我们能看到这一点,就是它本身反射了一条指向相机的光线(outcoming),you cannot see it, but you can feel it. 于是,上面的这一段话,还有这张图片,最终不过就是如下的一个公式。

这里,L是光照的强度,G是Geometry的意思,比如是否有遮挡,光源和点x的夹角等。f是bidirectional reflectance distribution function,从ω(i)点射入的irradiance和反射到ω(0)方向的radiance之间的比例系数。请大家自行理解radiance和irradiance。

Image based light

简单回顾一下,首先,我们提到了Rasterization在渲染上的不足,PBR的优势,以及基于PBR推导出的光学公式。主要是针对理论上的阐述。但作为工程师,我不关心这是怎么来的,我只想知道是怎么用的。

何为Image based light,简单说就是有一张cube Mapping的纹理作为Skybox,每一个像素点的颜色,都算是一处光源。Rasterization无法量化光的强度,那就直接用照片预处理后的结果。光源的问题解决了,这积分符号,我看着别扭,整个人的心情都不好了,不着急,我们先举个例子。

还是屋子里有盏灯的例子,假设现在屋子漆黑一片,然后我们打开这盏灯,如果我们的相机足够快,能够比光速还快的那种快,我们就能监控到灯亮照亮整个屋子的这一刹那,灯泡随机的,均匀的向四面八方发射光子,光子打在墙上,随机的反射,折射或被吸收,请自行脑补这个瞬间。我们关灯,再开灯,继续重复这个过程,因为每一次都是随机的,所以这个过程每一次都不同,但只要有足够的积累,都会照亮这个屋子,结果是一致的。随机的不同,但在统计上趋于相同。

同样的道理,只要我们有足够的,随机的光线来模拟,最终的结果也是接近真实的,这就是Monte Carlo method。如因此我们可以推导出如下的公式,这里稍有不同的是分母中多了一个PDF(probability density function)的概念,用来做IS(Importance Sampling),作用是提高概率高的光线的权重,目的是在用尽可能少的N来获取尽可能正确的结果,reduce variance。

BRDF

上面的光学公式就剩下f这个函数的定义了,glTF里采用的是Disney提供的Microfacet(显微镜) model,简单说,就是这个材质可以模拟各种光学现象,fragment的朝向差别较大,漫反射;如果均匀一致,镜面反射;不均匀但相似,则是glossy。此处就不深入讨论这个函数了,之前那篇PBR谈到的energy conservation,roughness和fresnel等相关概念。

至于BRDF函数,有三点基本的准则。首先,f>0,能量必须为正吗,f(l,v) = f(v,l),光线可逆,最重要的是能量要守恒(曲面积分小于1)。

Environment Maps

至此,我们就可以通过采样的方式,获取每一个点对应的光照强度,然后结合材质的颜色和环境光的颜色,在fragment中计算每一个像素对应的颜色。但glTF基于《Unreal Engine's course notes on real-time PBR》进行了预处理,优化性能。

这个预处理的依据就是如上的公式。如果你的Cube Mapping的纹理是已知的,那前半部分的求和只跟roughness和cos(v)有关,而后半部分可以转化为一个kx+b的线性函数,这里,只有x未知,而k,b只跟roughness和cos(v)有关。而且roughness和cos(v)都在[0,1]之间,所以,我们可以生产一张如下的纹理,建立一张映射表,通过roughness和cos直接找到对应的k,b值,省去中间大量的采样计算。当然,纹理越大,越精确,但毕竟只是[0,1]之间的插值,所以是近似值。

总结

PBR材质是Rasterization和Ray tracing之间的一个很好的结合,严谨的数学推导以及实践中巧妙的近似。

天亮了,晚安

原文发布于微信公众号 - LET(LET0-0)

原文发表时间:2019-03-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券