[cg][ds] 八面体映射编码Normal
UE 中的Normal 是通过UnitVectorToOctahedron将xyz,编码进二维xy中的,代码如下
OutGBufferA.rg = UnitVectorToOctahedron( normalize(GBuffer.WorldNormal) ) * 0.5f + 0.5f;float2 UnitVectorToOctahedron( float3 N )
{N.xy /= dot( 1, abs(N) );if( N.z <= 0 ){N.xy = ( 1 - abs(N.yx) ) * select( N.xy >= 0, float2(1,1), float2(-1,-1) );}return N.xy;
}
函数 float2 UnitVectorToOctahedron(float3 N)
的几何意义是将 单位球面(unit sphere)上的向量 映射到 二维平面上的八面体贴图(octahedral mapping)坐标。这种映射通常用于环境贴图(如天空盒)或压缩单位向量到 2D。
一、背景与动机
单位向量(即 ∥ N ∥ = 1 \|N\| = 1 ∥N∥=1)表示球面上的一点。为了节省空间(比如存储在纹理中),我们希望将这个三维方向压缩成二维表示。Octahedral Mapping 是一种将单位球面映射到二维平面的一种高效方法,相较于球坐标和立方体贴图更均匀、无死角。
二、函数逐步解析
float2 UnitVectorToOctahedron(float3 N)
{N.xy /= dot(1, abs(N));
- 将向量 N N N 的 xy 分量进行归一化,使得 ∣ N . x ∣ + ∣ N . y ∣ + ∣ N . z ∣ = 1 |N.x| + |N.y| + |N.z| = 1 ∣N.x∣+∣N.y∣+∣N.z∣=1,这是八面体映射的核心。
dot(1, abs(N))
表示 ∣ N . x ∣ + ∣ N . y ∣ + ∣ N . z ∣ |N.x| + |N.y| + |N.z| ∣N.x∣+∣N.y∣+∣N.z∣。- 得到的
N.xy
是初步映射到 2D 的坐标。
if (N.z <= 0){N.xy = (1 - abs(N.yx)) * select(N.xy >= 0, float2(1,1), float2(-1,-1));}
-
八面体映射将下半球( N . z ≤ 0 N.z \leq 0 N.z≤0)翻折映射到上半球的对应区域,以实现无缝 2D 映射。
-
N.yx
是N.xy
的交换,用于镜像处理。 -
select
是条件选择器(伪代码风格),表示:- 如果
N.xy >= 0
,则使用float2(1, 1)
- 否则使用
float2(-1, -1)
- 如果
-
这样可以确保下半球的方向也能映射到 2D 空间中。
return N.xy;
}
三、几何意义总结
-
输入:单位向量 N ⃗ ∈ S 2 \vec{N} \in S^2 N∈S2(球面上的一点)
-
输出:2D 平面坐标 v ⃗ ∈ [ − 1 , 1 ] 2 \vec{v} \in [-1,1]^2 v∈[−1,1]2,表示该方向在八面体展开中的位置
-
映射结构:
- 单位球体 → 八面体 → 展开成正方形平面
- 映射是连续且几乎均匀的(适合 GPU 中的纹理存储)
四、可视化帮助
想象一个单位球体被一个八面体包住,然后将八面体沿其边缘展平在二维平面中。这个函数就是计算球面上任意一点在展开后的平面图中的位置。