Skip to content

齐次坐标与透视投影 ​

把一根等长的杆移远,它在屏幕上会变短。这不是杆发生了缩放,而是投影(projection)改变了空间长度与屏幕长度的关系。理解这个过程,可以解释 gl_Position、视锥、裁剪以及屏幕拾取为何需要一条射线。

第四个分量先解决平移 ​

三维矩阵乘法能表示旋转和缩放,却不能单独表达“所有点都加同一个偏移”。齐次坐标(homogeneous coordinates)为位置增加一个分量:点写成 (x,y,z,1),方向写成 (vx,vy,vz,0)。四阶矩阵便能统一表示仿射变换(affine transformation):

M=(Rt0T1),M(p1)=(Rp+t1)M(v0)=(Rv0)

方向的 w=0,因此不受平移影响。

投影后 w 通常不再是 1。只要 w 非零,(x,y,z,w) 与 (x/w,y/w,z/w,1) 表达相同的有限三维位置。这次除法叫透视除法(perspective divide);w 不是“额外的物理空间方向”。

名词 / 英文在流程中的含义单位或范围
观察空间 / view space以相机为参考的坐标本例为 m
裁剪空间 / clip space投影矩阵输出的四维表示尚未除以 w
标准化设备坐标 / normalized device coordinates, NDC裁剪后除以 w 的坐标WebGL 可见范围通常为各轴 [-1,1]
视口 / viewport将 NDC 映射到绘图缓冲区的矩形pixel
视场角 / field of view, FOV相机能看到的张角公式中为 rad

手算:距离翻倍,屏幕尺寸减半 ​

采用相机朝向负 Z 轴的常见右手约定,设相机坐标中的点为 (x,y,z),前方距离 d = −z > 0。设垂直视场角为 α,宽高比为 a,则:

f=1tan⁡(α/2),xndc=faxd,yndc=fyd

画面为正方形、α=90° 时,f=a=1。点 (1,1,−2) 投影到 (0.5,0.5);点 (1,1,−4) 投影到 (0.25,0.25)。距离翻倍后,它与屏幕中心的偏移减半。

ts
function projectXY(x: number, y: number, z: number,
                   fovYRad: number, width: number, height: number) {
  const d = -z
  if (d <= 0) return null // 教学示例:不处理相机后方
  const f = 1 / Math.tan(fovYRad / 2)
  const ndcX = (f / (width / height)) * x / d
  const ndcY = f * y / d
  return {
    // 返回左上角为原点的绘图缓冲区坐标
    x: (ndcX + 1) * width / 2,
    y: (1 - ndcY) * height / 2,
  }
}
projectXY(1, 1, -2, Math.PI / 2, 800, 800) // {x:600,y:200}

这段代码只演示 XY 投影,不是完整渲染管线:没有裁剪三角形,没有计算深度,也没有处理 CSS 像素与绘图缓冲区像素之比。完整流程先在齐次裁剪空间处理视锥外的几何,再做透视除法和视口变换。Khronos 顶点后处理说明

为什么深度值不等于距离 ​

近远平面(near/far planes)限制可见深度范围。常规透视投影后的深度包含 1/d 项,因此非线性地分配精度。把深度缓冲中的 0.5 当作“近远距离的中点”,通常会得到错误位置。Cesium 还可能采用多视锥、对数深度等策略,应使用匹配其渲染路径的重建方法。

自检:一个屏幕点对应哪个三维点 ​

假设 x_ndc = 0.5,这是否唯一确定三维位置?将点的 w 从 2 改成 4 而保持裁剪坐标 x 不变,x_ndc 会怎样变化?

答案与理由

不能。多个深度上的点可落在同一屏幕位置,形成从相机出发的射线;还需要深度、地表或模型相交结果。第二问中 x_ndc 减半,因为它等于 x_clip / w_clip。

回到主课:相机、GPU 渲染管线、拾取。

气象学 · 数学基础 · 地理可视化