GameTech/URP 渲染实战
源码 / shaders/Ch07/NormalBlend.glsl
shaders/Ch07/NormalBlend.glsl
// 两张切线空间法线的混合方法。n1 = 基础法线,n2 = 细节法线,都已解码(z 朝外)。(GLSL 版,对应 Ch07/NormalBlend.hlsl)

// core 的 CommonMaterial.hlsl 里的 BlendNormalRNM(GLSL 版没有这个库,照抄过来)
vec3 BlendNormalRNM(vec3 n1, vec3 n2)
{
    vec3 t = n1.xyz + vec3(0.0, 0.0, 1.0);
    vec3 u = n2.xyz * vec3(-1.0, -1.0, 1.0);
    vec3 r = (t / t.z) * dot(t, u) - u;
    return r;
}

// ① 线性平均:把两个方向直接相加再归一化。最直觉、也最不对——两张图会互相“抹平”。
vec3 BlendNormalLinear(vec3 n1, vec3 n2)
{
    return normalize(n1 + n2);
}

// ② UDN(Unreal Developer Network 流传的写法):只叠加 xy,z 用基础法线的。
//    便宜,但细节在陡坡上会被压扁。
vec3 BlendNormalUDN(vec3 n1, vec3 n2)
{
    return normalize(vec3(n1.xy + n2.xy, n1.z));
}

// ③ Whiteout:xy 相加,z 相乘。相当于把两个“斜率”相加,平地和斜坡上的细节都比较可信。
vec3 BlendNormalWhiteout(vec3 n1, vec3 n2)
{
    return normalize(vec3(n1.xy + n2.xy, n1.z * n2.z));
}

// ④ RNM(Reoriented Normal Mapping):把细节法线当成“相对 +Z 的旋转”,
//    再把这个旋转施加到基础法线上。URP 的 Lit 细节法线用的就是它:
//    BlendNormalRNM(n1, n2),要求两个输入都已归一化。
vec3 BlendNormalReoriented(vec3 n1, vec3 n2)
{
    return normalize(BlendNormalRNM(normalize(n1), normalize(n2)));
}