
5.3 实践优化
5.2节给出了一个简单的实现,然而还有更多的问题需要解决,首先clipQuadToHorizon函数里有大量的代码分支,这对GPU的运行机制非常不友好;其次integrateEdge函数将会碰到数值计算精度的问题;最后需要把菲涅尔项考虑到整个计算过程中。
5.3.1 水平面裁切
之前提到的多边形在Cosine函数的积分是在上半球球面上求积的,所以需要将多边形裁切到上半球,Shader实现多边形的裁切会造成大量的代码分支,以下为代码示例。


考虑将积分的求解拆分成两个部分,即I=I′×ClippingFactor,其中,I′为不考虑水平面裁切时的积分结果,ClippingFactor=。然而即使如此,依然没有没有办法求解I′或ClippingFactor。
这时候就需要引入一些近似,文章[8]提议使用球作为几何代理,使得,但是正如刚刚提到的,无法计算出I′,所以同样无法推导出
。
这里引入另一个近似,如果将Cosine球面函数与多边形几何体积分解析式中的点乘移除,那么将会得到一个向量,记作I,这个向量与另一个单位向量T的点乘可以理解为对以T为z轴的Cosine球面函数的积分。I的长度为上述形式在T=normalize(I)的情况下的结果,得到
=length(I),参考文章[9]的推导,得到球的张角为

文章[10]给出了这样一个球,其对Isphere的解析式为

其中:

上述计算仍然过于复杂,所以将这个结果保存为贴图,运行时求得σ与ω后,对这两个参数查表来求解。
由于使用球来近似平面,无法直接解决平面的朝向问题,所以需要额外的代码来处理,将当前位置代入平面方程就可以得到当前位置处于平面的正面还是反面,通过对F向量取反即可处理双面光源。修改后的Shader代码如下。


5.3.2 16位浮点数的数值精度
让我们回顾多边形对Cosine积分的向量形式,这次将重点放在其中一条边的积分上。

对于给定的两个点pi与pj,可以通过cos与cross这两个函数对上面的表达式求解。

上面这段代码中包含了一个arccos函数,以及,这些都值得注意。首先arccos函数的开销较大,其次sinθ可能接近0从而引起inf/nan,最后需要尽量提高FP16运算在整个计算过程中的占比。
重新以t=cosθ参数化将得到
,其中arccost是关于点
的对称函数,
为偶函数,所以定义域为[-1,0]的值可以通过定义域为[0,1]的结果计算得出,可以把注意力集中在[0,1]的范围内。观察图5.8得到
函数在[0,1]区间内较平滑,低阶的Rational Function(有理函数)或Polynomial(多项式)拟合即可满足要求。

图5.8函数图象
如图5.9所示,使用多项式0.33735186x2-0.89665001x+,相对错误率小于1%,整个多项式计算可以在16位浮点数下进行,满足移动平台的精度要求。

图5.9 二次多项式拟合
5.3.3 菲涅尔(Fresnel)
在前面的实现中,并没有考虑BRDF中的F项,即菲涅尔项。下面介绍一个将菲涅尔项一起考虑的近似解。
在球面的积分恒等于1,是LTC的一个性质,但是因为BRDF中的遮挡项(Masking-Shadowing Function),BRDF在半球面的积分小于1,所以需要存储一个单独的归一化系数:

将菲涅尔项代入这个归一化系数得到将菲涅尔项一起考虑的近似解。

这个积分项与文章[11]中使用的Split Sum Approximation中的BRDF预积分项一致,从而可以使用相同的预积分表,并且这一项与光源无关,Shading过程只需要一次查表操作。
