这篇文章讨论法线信息的压缩思路。归一化后直接抛弃Z分量是在切线空间的做法,照搬到世界空间下会永久丢失Z分量的正负性。与此同时,此种做法在Z值接近0时会造成极大的压缩误差,无法支撑后续 BRDF 输入所需的法线精度。
在寒霜引擎的延迟渲染环节,为了在中间阶段节省出一个 gbuffer 通道用以存储 PBR 相关材质属性,寒霜引擎将右乘 TBN 得到的世界空间法线坐标经历一次复杂重新编码,压缩到 RT0(R10G10B10A8格式) 中的 xy 通道,同时将基底的索引压缩到 RT1(albedo gbuffer,R8G8B8A8格式)。后续将在 lighting pass 中还原原始法线,完成光照计算。

前置资源

延迟绘制中的几何 pass 阶段,每个 shader 均包含这个怪异的 cubemap 纹理资源,视觉上呈现为 1*1*6 的 R8_UNORM 单通道立方体纹理,储存着 0-5 范围内的整数索引。这个 cubemap 巧妙地将“根据法线方向选择对应的正交基”这样的分支逻辑转换为“采样得到索引”的逻辑,避免了着色器中分支的产生。
提醒注意:对于 UNORM 格式的纹理采样将被映射在 0-1 的范围内,需要手动乘以255后向下取整。

与此同时,cbuffer1 中保存着6组 normalBasisTransform 变换矩阵(编号0-5),其实质是建立在立方体六面上的正交基。在此缓冲以索引26为头部开始。分别建立起用于将世界空间坐标将被转换到局部空间。

编码(方向分区)

为实现 PBR 可接受的法线精度还原,寒霜引擎使用6个方向分区压缩法线,对应六组 4*3 normalBasisTransform 基底。
在编码阶段,此时已经完整计算出归一化的世界空间坐标系下的模型法线。着色器的行为可归为根据法线方向选择一组最合适基底的索引编号,而后计算偏移量,在 cbuffer 中寻址找出这一组 4*3 的完整基底。(简单概括就是找出法线穿过立方体的哪一面,取这一面的正交基来编码)
世界空间法线坐标将被转换到以六组基底其一的局部空间,同时 z 分量将被抛弃,最后 xy 分量经 *0.5+0.5 映射到 0-1 空间之后,写入 RT0 的前两个通道。同时将 basis 的索引编号写入 RT1 的w通道。(下图仅显示前两通道的可视化)

解码

法线的解码还原在延迟管线的 lighting pass 进行,这里对 lighting pass 进行相关逆向分析。
光照阶段内部,唯一的 cbuffer 同样存在着6组基底矩阵用于还原世界空间中的法线。

着色器将首先从先前阶段的 RT 中采样 xy 通道,取得已压缩的法线信息。而后由 t1(对应几何 pass 中的 RT1)中采样取得 basis 编号。
对于获得到的,已压缩至2通道的法线向量,先通过 sqrt(1 - x^2 - y^2) 的方式还原基底空间下的 z 方向的分量,然后使用由 cb0 索引取得的对应正交基底的变换矩阵。将矩阵右乘于基底空间下的法线向量即可重建出足以应用于 PBR 材质高精度的法线信息。

结尾

按照寒霜的做法,节省出一个原本用于法线分量的十位通道,但与此同时却又占用了八位通道用以存放基底索引值。
所以到头来这一切真的值得么。。。听窝的,咱还是放过法线吧~