【NVIDIA】Mipmapping Normal Maps

1. 法线贴图是否需要mipmapping

今天被同学问到法线贴图是否需要mipmap,第一想法是当然需要,mipmap不但可以降低带宽内存消耗,还有助于减轻远景画面闪烁等质量问题,但是转念一想,法线贴图是方向,如果直接混合得到的结果貌似就不正确了,这样看来,mipmap可能会导致问题?

从理论角度来分析,颜色是线性数据,因此直接使用lerp线性混合即可,但是法线是方向数据,正确来说,应该采用球面混合slerp,如果直接使用lerp进行混合的话,其结果与slerp的结果应该会存在差别:

法线lerp与slerp结果角度差值

上面这张图给出了二维法线float2(1, 0)与float(0, 1)按照从0 ~ 1的权重进行lerp与slerp混合后的结果的角度差异,可以看到两者在数值上是存在角度差异的,不过角度差异不大,最大处为,而在实际使用中,使用slerp进行混合会增加很多工作量,因此通常会直接借用硬件的双线性混合特性使用lerp进行混合输出,在使用的时候只需要在PS中对混合后的法线进行归一化即可。

2. 法线贴图mipmapping与高光锯齿问题

在查阅资料的时候,无意中看到了NVIDIA此前的一篇利用法线线性混合后的长度来消除远景处的高光瑕疵的文章,觉得十分有意思,因此在这里将之分享给大家,这里是原文链接

前面说到,法线mipmapping会使用lerp对法线进行混合,而混合后的结果法线其长度通常不为1,因此在使用的时候需要进行归一化,而NVIDIA通过对混合后法线的长度进行分析发现,混合前两个法线的夹角越大,混合后的法线长度越小,下面输出法线夹角()与lerp后的长度关系图,可以看到绝对夹角(两个方向之间较小的夹角)越大,混合后的法线长度越短。

夹角与长度

那么这个结论可以用来做什么呢?可以看到,合并后法线长度越小,合并前法线夹角越大,说明此处法线突变越厉害,而根据这个信息可以消除由于高光锯齿导致的噪声问题,下图是从原文取出的方案实施效果对比图:

左图为原始效果,右图为优化效果

注意观察远景部分,左图中存在大量的高光噪声,而右图中相应区域的结果则较为平滑,下面一起来过一下具体的做法。

3. Estimate Normal Variation

根据前面的关系图,我们可以很容易按照下面的公式根据混合后的法线长度倒推出混合前两个法线的夹角:

如果混合后的法线长度为,说明两个法线的夹角为,在实际情况下,可能不只是两个法线参与混合,比如是两组法线,那么混合后得到这种长度,输入法线(与合并后的法线平均夹角为)可能是如下两种情况:

  1. 法线属于各向异性的,比如分别分布在合并后的法线的两侧
  2. 法线属于各项同性的,比如锥形分布在合并后的法线的四周。

光从长度,我们是很难分别是上述两种情况的哪一种。

一个比较能让人接受的假设是,输入法线服从正太/高斯分布,那么在这种情况下合并后的法线的长度与正太分布的标准差之间的规律,可以用如下的曲线来表示:

Red - 合并后法线长度与标准差之间的关系,Green - 拟合曲线

从上图可以看出,随着标准差的增加,合并后法线长度呈现一个快速下降后接近平滑(带轻微上升)的趋势,且从曲线的走势推断,合并后的法线长度是有一个下限的,而在实际情况中,合并后法线的长度却是经常得到非常短的结果。

为了更加的逼近现实规律,NVIDIA采用了如下的公式替代正太分布公式来进行拟合(见上图绿色曲线):

也就是说,借助这个公式,我们可以根据一个合并后的法线长度计算出输入法线分布的标准差。那么这个结论有什么用呢?

4. Eliminating Aliasing of Specular Highlights

这个结论的一个作用是,用于消除凹凸表面上的高光锯齿(如前面图片描述),这个问题对效果有着较大的影响,而此前解决这个问题的方案( [Fournier 92] ,[Shilling 97])都有着各自的局限性。

在传统的高光方案(比如Blinn-Phong)中,高光是通过使用归一化后的法线计算得到的,(Blinn-Phong)高光公式如下:

其中H是Half-Vector的缩写,而s是高光指数,但实际上,由于指数的累加并不等于累加的指数,而正确的做法,应该是使用合并前的法线分别计算高光,之后将之累加起来:

虽然我们无法获取到合并前的各个法线数据,但是我们可以通过前面的分布规律来对这个结果进行逼近模拟,即将前面的法线的正太分布函数与指数函数结合起来进行计算(为输入法线与输出法线的平均夹角):

如果直接将两个公式(按照卷积方式)结合起来(如上述积分)可能会使得输出的公式过于复杂甚至难以表达,因此NVIDIA这边的做法是使用一个新的指数形式来对其进行拟合。

新的指数函数的指数部分是通过在一个平面上使用一个新的高斯分布来逼近这两个函数的卷积结果而求得的(下面会详细介绍实施逻辑)。

要想使用高斯函数来逼近指数函数,这里根据泰勒展式做一个简化处理:

根据这个公式,我们可以推导出:

即完成了前面到高斯函数的转换,下面看下这个假设的精确性:

余弦函数与高斯函数的极坐标plot

上面图中共绘制了四条曲线,其中红色部分为余弦曲线,绿色部分为高斯曲线,当时,余弦曲线与高斯曲线比较宽,两者在一头一尾处存在较大的差异,而当时,两者就基本上完全重合了。

我们知道,两个处于同一平面上的高斯函数的卷积结果是一个新的高斯函数,且卷积后的高斯函数的标准差与两个输入高斯函数的标准差之间具有如下的关系:

利用前面的公式(4),我们可以得到如下的关系:

为了描述的方便,这里引入一个Toksvig因子(Toksvig是作者的名字),这个因子用于描述原始的指数因子被拉伸的比例:

可以看到,这个系数是小于1的,也就是新的高斯函数的指数系数是小于原始输入的高斯函数的指数系数的,这就导致对应的极坐标曲线变宽(参考上一张极坐标曲线图,高光覆盖范围变宽),为了维持能量守恒,就需要对高光的强度进行衰弱处理,根据公式

我们可以得到,为了保持新公式的积分结果与老公式一致,就需要在新公式前乘上一个缩放系数:

按照这个公式,原始的Blinn-Phong高光部分就被修正成了:

总的来看,就是在原始的Blinn-Phong公式做了轻微处理:缩小了指数系数,添加了一个缩放因子。

当平均后的法线长度比较小(意味着法线变化幅度较大),此时Toksvig因子会接近于0,导致高光部分变得宽而黯淡,即此时高光变得没那么锐利,反之高光就会维持锐利,这是符合现实逻辑的。

5. 实施要点

此前高光计算通常不是直接在shader中进行指数运算,而是通过一个1D贴图进行查表得到:

  1. 从法线贴图中获取当前点的法线
  2. 对法线进行归一化:
  3. 计算
  4. 根据结果,在1D LUT中进行查表得到指数结果

而更新后的计算方案则需要从2D LUT中进行查表,相关步骤给出如下:

  1. 从法线贴图中获取当前点的法线
    2.计算
  2. 计算
  3. 根据与结果,在2D LUT(分别对应与)中进行查表得到指数结果

6. 应用

这个技术除了可用于法线贴图采样得到的法线在远处的高光锯齿消除之外,还可以用于如下几个方面:

  1. 在法线贴图生成时,人为的将部分区域的法线长度缩短,以实现部分区域的高光衰减效果(给的一个公式没太看懂是什么意思,这里先忽略)
  2. 在法线插值时,对插值后的法线也可以同样适用这个技术方案来消除因此导致的锯齿问题
  3. 在环境贴图采样时,可以使用作为环境贴图的LOD来得到由于法线偏移而导致的采样结果模糊的效果

你可能感兴趣的:(【NVIDIA】Mipmapping Normal Maps)