在现代图形学和高阶渲染管线中,八面体法线编码(Octahedral Normal Encoding)是一种将三维单位向量(如表面法线)编码为二维坐标的极其高效的方法。它是对八面体环境贴图技术的扩展应用。本文我们将拆解其几何投射原理,并提供可直接用于实际渲染管线的 HLSL 实现。
这种编码方法的基本思想是将法线向量投影到一个八面体上,然后将八面体“剪开”并平铺折叠在一个正方形上。
编码和解码过程完全无需使用三角函数(sin/cos),仅使用基础的加减乘除、绝对值和符号判断,ALU 指令消耗极小。
相比球面坐标映射在极点造成的严重精度聚集和扭曲失真,八面体展开法在全空间的精度分布更为均匀。
将 3 通道法线完美压缩为 2 通道,在延迟渲染的 G-Buffer 中释放出宝贵通道,也是 BC5 (ATI2) 纹理格式的绝配。
我们可以通过交互面板与流水线推演来建立物理直觉:
* 操作提示:尝试将 Z 分量滑至负数,会直观地感受到映射点从菱形中心“跃迁”并向外四角对折的现象,这正是 1.0 - abs(result.yx) 逻辑在起作用。
HLSL 源码,以及针对 G-Buffer 读写的映射处理示例。
// 八面体法线编码函数 - 将单位向量编码为 2D 坐标 [-1, 1]
float2 EncodeNormalOctahedral(float3 normal)
{
normal = normalize(normal);
// 步骤1:计算 L1 范数并归一化 xy
float sum = abs(normal.x) + abs(normal.y) + abs(normal.z);
float2 result = normal.xy * (1.0 / sum);
// 步骤2:当 z 为负时进行下半球折叠
if (normal.z < 0.0)
{
// 硬件级并行技巧:result >= 0.0 返回的是 bool2 向量
float2 signedResult = (result >= 0.0) ? float2(1.0, 1.0) : float2(-1.0, -1.0);
result = (1.0 - abs(result.yx)) * signedResult;
}
return result;
}
// 八面体法线解码函数 - 将 2D 坐标还原为 3D 单位法线
float3 DecodeNormalOctahedral(float2 encoded)
{
float3 normal;
normal.xy = encoded;
// 基于 L1 范数为 1 的假设反推 Z 分量
normal.z = 1.0 - abs(encoded.x) - abs(encoded.y);
// 当 z 为负时,逆向还原 xy 分量的折叠
if (normal.z < 0.0)
{
float2 signedNormal = (normal.xy >= 0.0) ? float2(1.0, 1.0) : float2(-1.0, -1.0);
normal.xy = (1.0 - abs(normal.yx)) * signedNormal;
}
return normalize(normal);
}
// [GBuffer 写入通道]
float2 encoded = EncodeNormalOctahedral(worldNormal);
// [-1, 1] 映射至 [0, 1] 以存入 8-bit / 10-bit 纹理
float2 quantized = encoded * 0.5 + 0.5;
OutGBuffer1 = float4(quantized, roughness, metallic);
// ----------------------------------------------------
// [延迟光照通道读取]
float2 gbufferNormal = GBuffer1Texture.Sample(samplerState, uv).rg;
// 恢复至 [-1, 1] 空间
float2 encoded = gbufferNormal * 2.0 - 1.0;
float3 worldNormal = DecodeNormalOctahedral(encoded);
问:float2 result = ...; (result >= 0.0) 的判断基准是要求 x 和 y 分量同时都大于 0 吗?
答:不是的。在 HLSL/GLSL 中,向量的比较运算是逐分量(Component-wise)独立执行的。表达式 result >= 0.0 返回的是一个 bool2 值。三元运算符 ? : 也会据此生成对应的二维浮点向量(例如 x≥0, y<0 时返回 float2(1.0, -1.0))。利用硬件级的并行指令流,我们只需极简的代码即可计算出正确的符号修正矩阵(Sign Mask)。
八面体法线映射用极低开销的代数计算,置换了昂贵的三角函数解算。它通过在