定义

法线是指垂直于某一表面(如平面或曲面)在该点的单位向量。在三维图形中,法线用于描述表面朝向,常用于光照、碰撞等计算。

一般计算方法

  • 对于平面,法线可以用两个不平行的边的叉积得到。
  • 对于曲面(如高度图地形),常用相邻点的高度差近似切平面,再用叉积或类似 Vector3(u, _vertex_spacing, v) 这种方式近似法线,最后归一化为单位向量。

Terrain3D 中的法线计算示例

Godot Terrain3D 插件中计算法线函数是这样的:

Vector3 Terrain3DData::get_normal(const Vector3 &p_global_position) const {  
    if (get_region_idp(p_global_position) < 0 || is_hole(get_control(p_global_position))) {  
       return Vector3(NAN, NAN, NAN);  
    }  
    real_t height = get_height(p_global_position);  
    real_t u = height - get_height(p_global_position + Vector3(_vertex_spacing, 0.0f, 0.0f));  
    real_t v = height - get_height(p_global_position + Vector3(0.f, 0.f, _vertex_spacing));  
    Vector3 normal = Vector3(u, _vertex_spacing, v);  
    normal.normalize();  
    return normal;  
}

为什么 (u, _vertex_spacing, v) 能表示为法线?

1. 地形表面的参数表示 地形可以看作一个高度函数 y = f(x, z),其中:

  • x, z 是水平坐标
  • y 是高度值

2. 切向量的计算 在当前点,我们可以计算两个切向量:

  • X 方向切向量:T_x = (vertex_spacing, Δy_x, 0)
  • Z 方向切向量:T_z = (0, Δy_z, vertex_spacing)

其中:

  • Δy_x = get_height(x + vertex_spacing) - get_height(x)
  • Δy_z = get_height(z + vertex_spacing) - get_height(z)

3. 法向量 = 两个切向量的叉积

N = T_x × T_z = (vertex_spacing, Δy_x, 0) × (0, Δy_z, vertex_spacing)

计算叉积:

N = (Δy_x × vertex_spacing - 0 × Δy_z,
     0 × 0 - vertex_spacing × vertex_spacing,
     vertex_spacing × Δy_z - Δy_x × 0)
   = (Δy_x × vertex_spacing, -vertex_spacing², Δy_z × vertex_spacing)

4. 代码中的处理 代码中使用了相反的高度差:

real_t u = height - get_height(p_global_position + Vector3(_vertex_spacing, 0.0f, 0.0f));
real_t v = height - get_height(p_global_position + Vector3(0.f, 0.f, _vertex_spacing));

这相当于:

  • u = -Δy_x
  • v = -Δy_z

所以法向量变为:

N = (-Δy_x × vertex_spacing, -vertex_spacing², -Δy_z × vertex_spacing)
  = (-u × vertex_spacing, -vertex_spacing², -v × vertex_spacing)

如果我们将其归一化并调整符号(让法向量指向上方),最终得到的就是 (u, vertex_spacing, v) 的形式。

直观理解

想象一个倾斜的平面:

  • 如果地形在 X 方向上升,u 为负,法向量的 X 分量为负
  • 如果地形在 Z 方向上升,v 为负,法向量的 Z 分量为负
  • Y 分量(vertex_spacing)始终为正,确保法向量大致指向 ” 上方 ”

这样构成的向量确实垂直于地形表面!