无需切线空间应用凹凸贴图

凹凸贴图(Bump mapping) 最早由一名图形程序员发明(1978 James Blinn),它通过调整后的着色计算 来创建凹凸表面的假象,无需增加几何体。一个新法线取代了 表面法线进行着色。可通过 1d 函数对新法线进行调整(如 Perlin noise、 灰度纹理)。此方法比使用小毛病较多的真实置换贴图 (如轮廓、遮挡、阴影)更加迅速。

bump_none.png bump_only.png bump_withnormal.png
不带凹凸贴图 带凹凸贴图 带凹凸和法线贴图

在实时渲染中,我们通常使用称为 法线贴图 的凹凸贴图变种(偏蓝色纹理)。 法线贴图在纹理的每个像素中保存一个颜色,而像素实际上是长度为 1 的 3d 矢量。

有两种方法生成法线贴图:

为使生成的纹理在任何旋转下均可反复使用,存储的法线矢量必须在 切线空间 中。 切线空间通常包含 3 种矢量:法线、切线和副法线。它定义表面的朝向。 将所有法线转换进切线空间中后即可对其进行重复利用, 因为它们和表面之间被定义为相对关系。切线空间贴图取决于物体的 UV 贴图,因为纹理中的 X 和 Y 方向 定义了世界空间中切线空间的两个矢量(切线与副法线)。在生成优质的 UV 贴图的同时避免切线空间穿帮 较为困难,且耗时较长。

如果使用类似 Perlin noise 的 3D 灰度函数会怎样?函数不要求任何 UV 贴图, 可增强凹凸表面临近的细节渲染。无需切线空间应用凹凸贴图 使其成为现实。

ddx 和 ddy

为不需要切线空间应用凹凸贴图,我们在材质编辑器中添加了两个新 材质表现:ddxddy。每个表现将返回其输入导数的近似值。 图像硬件计算此近似导数的方式为对两个像素进行着色并减去结果 (ddx = 右 - 左, ddy = 下 - 上)。

这些函数只能在像素着色器中使用,通常只用于在材质函数中 应用较大的效果。

项目 描述

输入

Value 计算输入导数的值。

输出

Out 输入的近似导数。类型与输入匹配。例如标量输出中的标量结果,2d 输出中的 2d,诸如此类。

ddx 和 ddy 以 2x2 的块进行计算,因此和高频率输入共用时将出现一些块状穿帮。

凹凸贴图材质函数

可通过数个 材质函数 在材质中应用凹凸贴图,而无需依赖于切线空间法线贴图。

ComputeFilterWidth

images/FilterWidth.png

此函数利用 ddx 和 ddy 计算数值在屏幕上的变化速度。它可在 开始出现 noise 的距离中使程序化着色器淡出。淡出结果闪烁较少,在动态 下更为明显,对凹凸贴图而言及其重要,因为凹凸表面的高光可形成 严重的锯齿穿帮。

以下示例图表现的是在远处淡出的程序化凹凸贴图函数。

Not Using FilterWidth

Using FilterWidth

项目 描述

输入

In 计算过滤幅度的值。

输出

Result 输入从像素到像素的变化速度。

PerturbNormalLQ

PerturbNormalLQ 函数将灰度凹凸贴图函数输入转换为世界空间法线。 然而,因其使用的是 ddx 和 ddy (之前提及存在 2x2 块状穿帮的材质表现), 输出世界空间法线的精度较低。

Low Quality

High Quality

项目 描述

输入

Bump 计算世界空间法线的标量凹凸值(灰度)。

输出

WS Normal 计算出的世界空间法线。

如需使用此函数输出的世界空间法线,必须将材质节点上的 tangent-space normal 设 为 false

此函数只作为一个引用存在,不对材质函数库公开。 使用 PerturbNormalLQ 函数代替。

PerturbNormalHQ

PerturbNormalHQ 函数计算的导数比 ddx 和 ddy 更精确,可 达到更高的精度。它的原理是利用三个样本位置多次计算标量函数。

项目 描述

输入

Bump one pixel right 当前位置右方一个像素的标量凹凸值(灰度)。
Bump center 当前位置的标量凹凸值(灰度)。
Bump one pixel down 当前位置下方一个像素的标量凹凸值(灰度)。
WS Normal 可选。与凹凸贴图组合的世界空间法线。可通过 矢量变换 表现转换为世界空间法线的切线空间法线。

输出

WS Normal 组合的世界空间法线。

如需使用此函数输出的世界空间法线,材质节点上的 tangent-space normal 须为 false

PreparePerturbNormalHQ

PreparePerturbNormalHQ 函数计算出 PerturbNormalHQ 计算世界空间法线 所需的三个样本位置。

项目 描述

输入

Value 当前位置的标量凹凸值(灰度)。

输出

Value one pixel right 当前位置右方一个像素的标量凹凸值(灰度)。
Value 当前位置的标量凹凸值(灰度)。
Value one pixel down 当前位置下方一个像素的标量凹凸值(灰度)。
FilterWidth 计算用于淡出远处细节的过滤幅度。

单个函数替代三个函数

可创建包裹凹凸映射函数的材质函数,并在其他函数中 对其进行 3 次求值。此操作可在一定程度上隐藏复杂性。

使用纹理

纹理与凹凸映射材质函数共用可提高性能;然而 由于显卡处理过滤纹理的方式,可能出现穿帮。普通过滤的颜色 以线性内插法进行插值,其导数为一个常量。这意味着使用灰度 纹理可获得表面插值不平滑的法线。

注解

描述的方法出自 Morten S. Mikkelsen 的著作(见参考)。

性能

程序化着色器对性能的消耗较大,且难以消除锯齿(与 纹理贴图相比)。我们当前提供 Perlin noise,可通过此材质表现进行优化,工作量 较大。为 n 个等级使用等级功能需要完成 n 次 大部分计算。为凹凸贴图进行 3 次函数求值产生的计算量更大。需注意消耗和像素数量 成正比。可使用所有功能,但建议只用于原型制作或在受控情况下使用。

问题

工作展望

此法用于替代显式存储的切线空间。向此方向发展我们尚需更多经验。 当前添加的内容不仅为图形设计师提供了凹凸贴图,还提供了进行研究的方法。

参考