深色模式
齐次坐标与透视投影
把一根等长的杆移远,它在屏幕上会变短。这不是杆发生了缩放,而是投影(projection)改变了空间长度与屏幕长度的关系。理解这个过程,可以解释 gl_Position、视锥、裁剪以及屏幕拾取为何需要一条射线。
第四个分量先解决平移
三维矩阵乘法能表示旋转和缩放,却不能单独表达“所有点都加同一个偏移”。齐次坐标(homogeneous coordinates)为位置增加一个分量:点写成 (x,y,z,1),方向写成 (vx,vy,vz,0)。四阶矩阵便能统一表示仿射变换(affine transformation):
方向的 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,则:
画面为正方形、α=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。